1、volatile保证可见性
public class MyVolatile { private static /*volatile*/ boolean running = true; private static void m(){ System.out.println("start"); while (running){ } System.out.println("end"); } public static void main(String[] args) throws InterruptedException { new Thread(MyVolatile::m,t1).start(); Thread.sleep(10000); running = false; } } 这段代码在打印出start后永远也不会打印end
因为running变量是放在内存中,每一个线程都会读这个变量并且把这个变量放到线程本地的缓存中
上面代码是修改了主线程缓存到本地的变量,但是此次修改t1线程依旧读的是t1本地的变量,所以t1调用的m方法读到的变量永远为true
解决方法:将变量修改为volatile
原理:用volatile修饰的变量保证了对每个线程读取到的数据同步
2、synchronized也可以保证可见性,但是不能保证有序性
1、程序在运行过程中如果上下两行代码之间没有依赖关系,可能会造成执行顺序不一致的情况
public class 可见性有序性 { private static boolean ready = false; private static int number; private static class ReaderThread extends Thread { @Override public void run() { while (!ready) { Thread.yield(); } System.out.println(number); } } public static void main(String[] args) throws InterruptedException { Thread t = new ReaderThread(); t.start(); number = 42; ready = true; t.join(); } }
如上代码有两个问题:有序性和可见性
可见性:主线程修改number变量为42后,ReaderThread线程读取自身线程缓存值依然为初始值0
有序性:number=42与ready=true这两行代码之间没有依赖关系,可能会先执行ready=true后执行number=42,这样就有可能导致输出结果为0
1、原子操作:线程操作时不能被其他线程打断,不能并发执行
2、如何保证原子性操作:synchronized
3、上锁本质:把并发操作编程了序列化操作(按照顺序执行)
4、悲观锁:悲观的认为操作会被其他线程打断。
5、乐观锁:乐观的认为操作不会被其他线程打断。
CAS:初始值为0,要将0变为1,在修改为1后去比较之前要修改的值是否还是0。如果是0则修改,否则重复执行cas操作
ABA问题:将0变为1后,拿着0回去比较修改值是否为0,发现还是0,但是这个0可能是别人把0变为8之后又变为0的情况。
解决ABA问题:加版本号
CAS是原子性的,在cpu底层就支持。入参有三个(修改的值,判断与未修改前的值是否一致,修改后的值)
6、乐观锁与悲观锁谁的效率更高
悲观锁:a线程抢到锁,之后会维持一个等待队列,队列中都是等待锁释放的线程。这些等待的线程不消耗cpu
乐观锁:a线程抢到锁,其他线程会不断循环去查看锁是否被释放。自旋现象会消耗cpu资源
综上所述,当a线程执行时间较长时,使用悲观锁,让其他线程到等待队列中等待。当a执行时间很短时,使用乐观锁