目录
使用volatile关键字
public volatile int a = 88;
public static volatile int b = 99;
对于某个field 如果只有一个线程读写 其它线程只是读,使用volatile能保证 其它线程读这个field时读出来的值是最新的,不使用则不能保证;
对于某个field 如果有多个线程读写,则不能使用volatile。
使用线程安全的数据结构
-
AtomicInteger:i++ 不是线程安全的(这里的i为int类型),atomicInteger.getAndIncrement()等 是线程安全的;
-
CopyOnWriteArrayList
-
ConcurrentHashMap
-
……
使用ThreadLocal
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
... ...
threadLocal.set()
threadLocal.get()
加锁
阻塞锁 & 非阻塞锁
阻塞锁:死等,获取到锁才往下走。
非阻塞锁:获取不到锁的话,返回说没获取到锁。。
synchronized method
public synchronized void someMethod() {
}
public static synchronized void someMethod() {
}
instance synchronized method用this进行加锁,static synchronized method用currentClazz进行加锁,能实现线程安全。
Question:这样有什么问题呢?
适合一个对象/类里只有一个方法需要加锁时用。
synchronized block
那上面的问题如何解决呢?可以使用synchronized block。
synchronized (lockObject) {
code block
}
我们先来复现一下上面的问题:
比如说某个类里有两个方法A和B,其中各有一个synchronizedBlock A 和 B,它们的锁都是 "this" for instance method 或者是 "currentClazz" for static method,然后有100个线程,那么某个线程在执行方法A时,其它99个线程就不能执行方法B。
Question:如何解决这个问题呢?
lockObject的选择:期望加锁的话,lockObject各个线程看起来得是一样的。
公平锁 & 非公平锁
- 公平锁:讲究先来后到,先来的先得锁
- 非公平锁:抢锁,谁抢到锁算谁的
可重入锁 & 不可重入锁
可重入就是说某个线程已经获得了某个锁,可以再次获取这个锁而不会出现死锁,e.g.
- synchronized是可重入的,加锁和解锁自动进行,不必担心最后是否释放锁;
- ReentrantLock是可重入的,加锁和解锁手动进行,且次数需要一样,否则其他线程无法获得锁;
- ReentrantLock相对于synchronized有个很大的优势是 可以实现公平锁(new ReentrantLock(true))。
分布式锁
针对多台机器的情况,需要在多台机器都能看到的一个公共的地方来实现锁,常见的有基于数据库的、基于缓存的、基于zk的等。
Question:finally段一定会执行吗?
分布式锁需要给锁加一个生命周期。
推荐使用tair的expireLock/expireUnlock。
双检锁
尽量避免加锁导致的并发降低:
public class Singleton {
private static Singleton uniqueSingleton;
private Singleton() {}
public Singleton getInstance() {
if (null == uniqueSingleton) {
synchronized (Singleton.class) {
if (null == uniqueSingleton) {
uniqueSingleton = new Singleton();
}
}
}
return uniqueSingleton;
}
}