内存可见性问题
多个线程操作同一个共享数据时,线程之间的操作是不可见的。
解决方案1 synchronized
- 优点
重量级锁,具备互斥性、原子性、内存可见性(在持有锁期间,会完全把数据修改到主存后才会释放锁)。
当一个线程已经访问共享数据时,该共享数据不可以被其他线程访问,需等当前线程释放锁以后,其他线程才可获取锁进行操作。
- 缺点
很明显,每个线程只能等待持有锁的线程释放锁,才可以操作被synchronized修饰的代码。这部分操作就是阻塞的,明显导致代码执行效率的降低。
解决方案2 volatile
- 优点
轻量级。但不是锁,不具备互斥性、原子性;只具备内存可见性。
- 当多个线程访问被volatile修饰的数据时,某个线程修改完该数据后,会立即将数据刷新到主存,从而解决内存可见性问题。
- 防止指令重排序。在JVM执行代码时,会对原先程序员写的代码进行优化可能调换原先的执行顺序,在单线程下,调换了代码顺序,不会造成安全问题,但是多线程环境下,就可能导致安全问题。加上volatile修饰变量后,jvm会在相关的代码上下加上内存屏障,禁止优化对应的代码。从而解决指令重排序的线程安全问题。
- 缺点
不具备互斥性、原子性。
原子性问题
多个线程操作同一个共享数据时,被线程修改过的共享数据无法立即被其他线程知道,而其他线程还在对旧值进行操作。从而造成了原子性问题。
解决方案1 synchronized
跟内存可见性问题一样,使用synchronized会锁修饰的那段代码,从而其他线程必须等持有锁的线程释放锁后,得到锁才可执行这段代码,这段代码中被修改的值肯定只会有一个线程在修改。所以没有原子性问题。
缺点就是synchronized太重量级了。性能下降
解决方案2 CAS Compare-And-Swap
CAS是一种算法,有三个参数:当前内存值V、预估内存值A、修改值B,只有当V==A时,才会去修改内存中的值为B。
在Java.util.concurrent.atomic包中,大量使用了CAS来解决原子性问题。
分段锁
运用在JDK1.8以前的ConcurrentHashMap中,在并发操作时,会锁掉Entry[]中的每个entry,而不是锁掉全部操作,这样当一个线程操作一个entry时,其他线程不能操作该entry,锁的细粒度提升了,性能也增加了。
闭锁
当其他线程都执行完毕后,才执行当前线程。
Java中的通过CountDonwLatch类,实现了闭锁。
同步锁 Lock
在jdk1.5之前,都使用synchronized来进行同步锁,要么锁整个方法,要么锁一个代码块。操作并不灵活。jdk1.5之后可以使用同步锁Lock,更加灵活。可以手动lock()和unlock()。
主要用到的是ReentranLock 可重入锁。