线程同步的几种方式
- synchronized修饰
- volatile实现同步(只能保证可见性,不能保证原子性)
- 使用局部变量ThreadLocal
- 使用原子类(AtomicInteger、AtomicBoolean……)
- 使用Lock
- 使用容器类(BlockingQueue、ConcurrentHashMap)
产生死锁的四个条件
- 互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
- 占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
- 不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
- 循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
synchronized 与 lock 的区别
- Lock锁是对象,而synchronized是java关键字
- Lock锁需要手动加锁释放锁,synchronized的该操作是全自动的。对于异常前者需要手动释放锁,不然容易导致死锁。而后者由jvm自动管理。
- Lock锁的属性可以配置,而synchronized只能是可重入的、非公平的、不可中断的。
- 在实现原理上synchronized主要是基于对象的monitor。而Lock的实现是java层面的,基础是AQS
volatile 实现原理
作用:使用valatile修饰的成员变量,就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。
原理:
- 强制把修改的数据写回内存。
- 在多处理器情况下使多处理器缓存的数据失效。
wait和sleep的区别
- wait是Object类的方法,sleep是Thread类的静态方法。也就是说前者任何对象都可以调用。
- wait会释放锁,而sleep不释放锁。也可以理解为sleep方法只是释放cpu时间片,而没有释放同步资源。当线程休眠完指定时间后,会自动重新去竞争时间片进行执行。而wait则需要其他线程调用notify、notifyAll来唤醒。
- sleep可以使用在任何地方,而wait只能用到同步代码块和方法中。
CAS的缺点
- ABA问题:解决方案 加入版本信息,版本只能递增
- 循环时间长开销很大:自旋CAS(不成功,就一直循环执行,直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。
- 只能保证一个共享变量的原子操作。