线程跟进程的区别:进程是操作系统进行资源分配的最小单元。线程是操作系统进行任务分配的最小单元,进程隶属于线程
如何开启线程?
1.继承Thread,重写run方法;2.实现Runnable接口,实现run方法(常用)
3.实现Callable接口,实现call方法。通过FutureTask创建一个线程,获取到线程执行的返回值;4.通过线程池来开启线程。
怎么保证线程安全?核心:加锁
如何加锁:1.使用JVM提供的锁,也就是Synchronized关键字;2. JDK提供的各种锁lock
二.Volatile和Synchronized有什么区别?Volatile能不能保证线程安全?DCL(Double Check Lock)单例为什么要加Volatile?-
Synchronized关键字用来加锁,Volatile只是保持变量的线程可见性。通常适用于一个线程写多个线程读的场景
-
不能,Volatile关键字只能保证线程可见性,不能保证原子性
public class VolatileDemo2 { public static boolean flag = true; public static void main(String[] args) { new Thread(()->{ while(flag){ } System.out.println("========End of Thread1========="); }).start(); try { Thread.sleep( millis: 100); }catch (InterruptedException e) { e.printStackTrace(); } System.out.println("turn flag off"); flag=false; } }Volatile能使线程的改变立马传递给其他线程,保证可见性
3.作用 Volatile防止指令重排。在DCL中,防止高并发情况下指令重排造成的线程安全问题
指令重排:指在cup中 先分配内存 再初始化 再建立指针关系 多线程中当指针初始化过程中另一个线程访问,则会导致线程安全出现问题
public class SingleDemo1{ // private static SingleDemo1 singleDemo1=new SingleDemo1(); private static SingleDemo1 singleDemo1; private SingleDemo1(){} public static SingleDemo1 getInstance(){ if(null == singleDemo1){ synchronized(SingleDemo1.class){ if(null == singleDemo1){ //DCL单例 Double Check Lock singleDemo1 = new SingleDemo1(); } } } return singleDemo1; } }
- Java的锁就是再对象的Markword中记录一个锁状态,偏向锁、轻量级锁、重量级锁、无锁对应不同的锁状态
- Java的锁机制就是根据资源竞争的激烈程度不断进行锁升级的过程
1、AQS是一个JAVA线程同步的框架。是JDK中很多锁工具的核心实现框架。
2、在AQS中,维护了一个信号量state和一个线程组成的双向链表队列。其中,这个线程队列,就是用来给线程排队的,而state就像是一个红绿灯。用来控制线程排队或者放行的。在不同的场景下,有不用的意义。
3、在可重入锁这个场景下,state就用来表示加锁的次数。0标识无锁,每加一次锁,state就加1。释放锁state就减1。
如何在并发情况下保证三个线程依次执行?如何保证三个线程有序交错进行?
三种线程方式:
LountDownLatch:发令枪 开枪一起跑
CylicBarrier:定义一个座位 来一个线程就-1 满了再开始走
Semaphore:设置固定的权重信号数量,给每个线程分权重信号 到达权重才能走 走完返还权重信号
static volatile int ticket=1;//设置状态
Thread t1 = new Thread(()->{
while(true){
if(ticket ==1){
try {
Thread.sleep( millis: 100);
for (int i = 0; i< 10; i++) {
system.out.println("a"+i);
}
ticket=2;
//对状态+1 下一个进程判断ticket是否=2,=2则可以进行 否则不行
六、如何对一个字符串快速进行排序?
Fork/Join框架 对半拆分到两两比较 然后再根据指针进行汇总
public class mergeTest {
private static int MAX =100;
private static int inits[] = new int[MAX];
//随机队列初始化
static {
Random r = new Random();
for (int index = 1; index <= MAX; index++) {
inits[index - 1] = r.nextInt(1000);
}
}
public static void main(String[ ] args) throws Exception {
//正式开始
long beginTime =system.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
MyTask task = new MyTask(inits);
ForkJoinTask taskResult = pool.submit(task);
try {
int[] ints = taskResult.get();
system.out.println(Arrays.toString(ints));
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace(system.out);
}
long endTime = system.currentTimeMillis();
system.out.println("耗时=" +(endTime - beginTime));
}
static class MyTask extends RecursiveTask {
private int source[];
public MyTask(int source[]) { this.source = source; }
@Override
protected int[] compute() {
int sourceLen = source.length;
//如果条件成立,说明任务中要进行排序的集合还不够小
if (sourceLen > 2) {
int midIndex = sourceLen / 2;
//拆分成两个子任务
MyTask task1 = new MyTask(Arrays.copy0f(source, midIndex));
task1.fork();
MyTask task2 = new MyTask(Arrays.copy0fRange(source,midIndex,sourceLen));
task2.fork(O;
//将两个有序的数组,合并成一个有序的数组
int result1[] = task1.join();
int result2[] = task2.join();
int mer[] = joinInts(result1,result2);
//system.out.println( "----——-"+Thread .currentThread ( ) .getName() );
return mer;
}
//否则说明集合中只有一个或者两个元素,可以进行这两个元素的比较排序了
else {
//如果条件成立,说明数组中只有一个元素,或者是数组中的元素都已经排列好位置了
if (sourceLen == 1 || source[0] <= source[1]) {
return source;
}else {
int targetp[] = new int[sourceLen];
targetp[0] = source[1];
targetp[1] = source[0];
return targetp;
}
}
}
七、并发的三大特性
1.原子性:原子性是指在一个操作中CPU不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。就好比转账,从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。2个操作必须全部完成。
2.可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
若两个线程在不同的CPU,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
3.虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按照我们写的代码的顺序来执行,有可能将他们重排序。实际上,对于有些代码进行重排序之后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题。
八、说说对线程安全的理解 不是线程安全、应该是内存安全,堆是共享内存,可以被所有线程访问
当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的
堆是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部+孙走用厂刀配工1问Jo堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏。
在Java中,堆是Java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
栈是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显式的分配和释放。
目前主流操作系统都是多任务的,即多个进程同时运行。为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的。
在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。



