1. 同步
在多个线程并发访问共享数据时,保证共享数据在同一时刻只被一条(或是一些,当使用信号量的时候)线程使用。
-
互斥同步(阻塞同步)【悲观锁】
- 一种最常见最主要的并发正确性保证手段。
- 悲观的并发策略:
无论共享的数据是否真的会出现竞争,都会进行加锁。共享资源每次只给一个线程使用,其它线程阻塞。 - 主要问题是进行线程阻塞和唤醒所带来的性能开销。
-
非阻塞同步【乐观锁】
-
基于冲突检测的乐观并发策略
-
不需要把线程阻塞挂起,无锁编程
不会上锁,先进行操作,若没有其他线程争用共享数据,操作直接就成功了;如果共享数据的确被争用,产生了冲突,再进行其他的补偿措施。(最常用的补偿措施是不断重试,直到出现没有竞争的共享数据为止) -
CAS(Compare and Swap,比较并交换)
-
2. 可重入锁(ReentrantLock、Synchronized)
- 线程可以反复获得已拥有的锁。同一线程能够反复进入被它自己持有锁的同步块,不会出现自己把自己锁死。
- 同一线程,外层方法获取锁后,内层方法会直接获取锁。
- 如果持有锁的线程再次获得这个锁,锁关联的计数器值加1,每次释放锁时计数器的值减1,当计数器的值变为0,线程释放锁。
3. Synchronized
需要reference类型的参数来指明要锁定和解锁的对象。
-
同步方法(使用synchronized修饰方法,一次只有一个线程可以使用这个方法)
public synchronized void deposit(double amount)
首先先给对象加锁,然后执行该方法,最后解锁。在解锁之前,另一个调用这个对象中方法的线程将被阻塞,直到获取锁。-
实例方法
默认用代码所在的对象实例(this)【调用这个方法的对象】作为线程要持有的锁 -
静态方法(类方法)
默认用类型对应的Class对象作为线程要持有的锁。
-
-
同步代码块(修饰需要进行同步的代码)
synchronized(object){代码内容}- 不仅可对this对象加锁,而且可对任何对象加锁。
- 可以选择只同步会发生同步问题的部分代码而不是整个方法,大大增强了程序的并发能力。
4. 显式锁(Lock, eg:ReentrantLock)
- 给协调线程带来了更多的控制功能。一个锁是一个Lock接口的实例,定义了加锁和释放锁的方法。 可以通过调用Lock对象的newCondition()方法创建任意个数的绑定到该Lock实例的Condition对象,然后使用Condition对象的await()、signal()、signalAll()方法【条件】来实现线程间的相互通信。
- 如果条件不适合,线程可以调用条件的await()方法使线程进入等待状态,并自动释放条件上的锁;这样其他线程就可以获取锁,一旦条件合适,就可以调用signal()或signalAll()来通知一个或所有等待这个条件的线程;获得通知后,线程重新获取锁并继续执行。
5. volatile
- 被volatile修饰的变量对所有线程都具有可见性,即一个线程修改了这个变量的值,则其他线程可以立即得知这个新值。
- 禁止指令重排序优化
实现:在变量赋值后多执行了一个操作,这个操作的作用相当于一个内存屏障,指令重排序时不能把后面的指令重排序到内存屏障之前的位置。同时会把本处理器的缓存写入内存,该写入动作也会引起别的处理器或别的内核无效化其缓存,需要重新从内存中读取新的值。
注意:
- volatile不能完全保证多线程并发的安全性。因为运算操作符并非原子操作,例如自增,当读取变量原来的值时,volatile可以保证该值的正确性,但可能在执行加1之前,已经又有另一个线程对变量进行了修改,该值变为无效。