多线程并发基础巩固
多线程并发基础巩固
synchronized和volatile区别
1 synchronized 用于对对象加锁,其他线程便无法访问此对象的synchronized 方法。造成其他线程阻塞。直到拥有锁的对象释放锁,然后再去抢锁。synchronized 保证变量的可见性和原子性,所谓可见性:当一个线程对共享资源加锁时,其他线程依然持有对该变量可读的权限。所谓原子性:当一个线程对共享变量修改时,不会受到其他线程的干扰,也就是说,写操作必须执行时段,其他线程无法打断该操作。
2 volatile 使得共享资源的修改被其他线程知晓。及保证线程可以读到最近被修改的值。
jvm底层和场景分析:
这里先说jvm,jvm中所有程序在堆中分配内存。包括线程,每个线程在堆中存在线程私有的区域,这个区域叫TLAB(Thread Local Allocation Buffer)线程局部缓冲区,这块区域是线程私有,TLAB存在的意义是,当有线程需要操作共享区域的变量时,会把线程共享的资源拷贝(不是对象的引用)到自己的TLAB,直接对线程私有的资源读写操作,这样提高效率。可是如果对这个共有资源的变量修改没有通知其他需要操作此共享变量的线程。会导致其他线程使用到错误的数据(注意:线程并不是拷贝完共享资源后一直都不在去共享资源区同步资源,如果cpu空闲,会隔段时间去重新拷贝一下的。)。这里使用synchronized和volatile可以使得这样的情况不会发生。volatile主要实现方法是,在对共享变量修改时,通知其他拷贝该变量的线程重新拷贝,替换那个已经被修改的变量的值。当然synchronized也可以保证被读到的变量的最新状态。它主要使用到锁,锁机制效率比volatile低,synchronized修饰的共享变量不会被拷贝到线程私有的区域,而是让访问它的线程排队访问,当然这样做效率是低下的。
注:volatile放在变量前面,synchronized放在方法上,也可以放在一段代码块外。
下面的程序线程1永远不会结束,但解除对volatile后,线程1会在两秒后结束。
public class TestVolatile {
private static /*volatile*/ boolean flag = true;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" start");
while (TestVolatile.flag){}
System.out.println(Thread.currentThread().getName()+" end");
}
},"Threa1").start();
new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" start");
TestVolatile.flag = false;
System.out.println(Thread.currentThread().getName()+" end");
}
},"Threa2").start();
}
}
sleep(),wait(),notify().notifyAll()区别
sleep()
该方法表示让当前正在执行的线程休眠一段时间,休眠时间通过参数传入。(休眠时间到了程序继续向下执行。)
wait()
让当前线程阻塞,并且释放锁(如果有获取锁)。意思是线程进入等待,直到有其他线程把它唤醒才继续执行。
notify()
唤醒一个阻塞的线程(具体会唤醒哪个阻塞的线程由cpu算法指定,java程序无法指定)。
notifyAll()
唤醒所有阻塞的线程。