多线程操作的问题
临界区
- 一个程序运行多个方法本身没有问题
- 问题出现在多个线程访问共享资源
- 多个线程读共享资源其实也没问题
- 在多个线程读共享资源进行读写操作时发生指令交错,就会出问题
- 一段代码内如果存在对共享资源的多线程读写操作,称这段代码为临界区
变量的线程安全分析
1. 成员变量和静态变量是否线程安全?
- 如果它们没有共享,则线程安全
- 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况
- 如果只有读操作,则线程安全
- 如果有读写操作,则这段代码是临界区,需要考虑线程安全
2. 局部变量是否线程安全?
- 局部变量是线程安全的
- 但局部变量引用的对象则未必
- 如果该对象没有逃离方法的作用访问,它是线程安全的
- 如果该对象逃离方法的作用范围,需要考虑线程安全
ReentrantLock
特点
- 可重入
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
基本语法
//获取锁
reentrantLock.lock();
try {
//临界区
}finally {
//释放锁
reentrantLock.unlock();
}
注意事项
参考阿里巴巴的《JAVA开发手册》:
【强制】在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没 有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁。
- 说明一:在 lock 方法与 try 代码块之间的方法调用抛出异常,无法解锁,造成其它线程无法成功获取锁。
- 说明二:如果 lock 方法在 try 代码块之内,可能由于其它方法抛出异常,导致在 finally 代码块中,unlock 对未加锁的对 象解锁,它会调用 AQS 的 tryRelease 方法(取决于具体实现类),抛出 IllegalMonitorStateException 异常。
- 说明三:在 Lock 对象的 lock 方法实现中可能抛出 unchecked 异常,产生的后果与说明二相同。
正例:
Lock lock = new XxxLock();
// ...
lock.lock();
try {
doSomething();
doOthers();
} finally {
lock.unlock();
}
反例:
Lock lock = new XxxLock();
// ...
try {
// 如果此处抛出异常,则直接执行 finally 代码块
doSomething();
// 无论加锁是否成功,finally 代码块都会执行
lock.lock();
doOthers();
} finally {
lock.unlock();
}
【强制】在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。 锁的释放规则与锁的阻塞等待方式相同。
- 说明:Lock 对象的 unlock 方法在执行时,它会调用 AQS 的 tryRelease 方法(取决于具体实现类),如果当前线程不 持有锁,则抛出 IllegalMonitorStateException 异常。
正例:
Lock lock = new XxxLock();
// ...
boolean isLocked = lock.tryLock();
if (isLocked) {
try {
doSomething();
doOthers();
} finally {
lock.unlock();
}
}
应该场景
样例:
- 可重入
public class ReentrantLockTest {
private static final Logger logger = LoggerFactory.getLogger(ReentrantLockTest.class);
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
//获取锁
lock.lock();
try {
//临界区
logger.info("enter main");
m1();
}finally {
//释放锁
lock.unlock();
}
}
public static void m1(){
//在此发生锁重入
lock.lock();
try {
//临界区
logger.info("enter m1");
m2();
}finally {
lock.unlock();
}
}
public static void m2(){
//在此发生锁重入
lock.lock();
try {
//临界区
logger.info("enter m2");
}finally {
lock.unlock();
}
}
}
- 可打断
可以防止无限制的等待,减少死锁的发生
public class ReentrantLockTest {
private static final Logger logger = LoggerFactory.getLogger(ReentrantLockTest.class);
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread((()->{
try {
//如果没有竞争那么此方法就会获取lock对象锁
//如果有竞争或进入阻塞队列,可以被其它线程用interrupt方法打断
logger.info("尝试获得锁");
lock.lockInterruptibly();
}catch (InterruptedException e){
logger.info("没有获得锁,返回");
e.printStackTrace();
return;
}
try {
logger.info("获得到锁");
}finally {
lock.unlock();
}
}),"t1");
//主线程提前锁住
lock.lock();
//只能尝试获取锁
t1.start();
Thread.sleep(1000);
logger.info("打断t1");
t1.interrupt();
}
- 锁超时
public class ReentrantLockTest {
private static final Logger logger = LoggerFactory.getLogger(ReentrantLockTest.class);
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread((()->{
logger.info("尝试获得锁");
boolean isLocked = false;
try {
//尝试1秒获得锁
isLocked = lock.tryLock(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
if(!isLocked){
logger.info("获取锁失败");
return;
}
try {
logger.info("获取锁成功");
}finally {
lock.unlock();
}
}),"t1");
lock.lock();
logger.info("主线程获得锁");
t1.start();
}
- 条件变量 交替打印
ReentrantLock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Thread thread1 = new Thread(() -> {
try {
lock.lock();
for (int i = 65; i < 91; i++) {
System.out.println("----------thread1------- " + (char) i);
condition2.signal();
condition1.await();
}
condition2.signal();
} catch (Exception e) {
} finally {
lock.unlock();
}
});
Thread thread2 = new Thread(() -> {
try {
lock.lock();
for (int i = 0; i < 26; i++) {
System.out.println("----------thread2------- " + i);
condition1.signal();
condition2.await();
}
condition1.signal();
} catch (Exception e) {
} finally {
lock.unlock();
}
});
thread1.start();
thread2.start();
核心方法
/**
* 阻塞等待获取锁;不允许Thread.interrupt中断,即使检测到Thread.isInterrupted,一样会继续尝试 * 获取锁,失败则继续休眠。只是在最后获取锁成功后再把当前线程置为interrupted状态,然后再中断线程。
*/
public void lock() {
sync.lock();
}
/**
* 当前线程未被中断,则获取锁
* 允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回,这时 * 不用获取锁,而会抛出一个InterruptedException
*/
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
/**
*尝试申请一个锁,在成功获得锁后返回true,否则,立即返回false
*/
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
/**
* 在一段时间内尝试申请一个锁,在成功获得锁后返回true,否则,立即返回false
*/
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
/**
* 释放锁
*/
public void unlock() {
sync.release(1);
}
/**
* 条件实例
*/
public Condition newCondition() {
return sync.newCondition();
}
/**
* 获取当前线程持有此锁的次数
*/
public int getHoldCount() {
return sync.getHoldCount();
}
/**
* 是否被当前线程持有
*/
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
/**
* 查询此锁是否由任意线程持有
*/
public boolean isLocked() {
return sync.isLocked();
}
/**
*如果是“公平锁”返回true,否则返回false
*/
public final boolean isFair() {
return sync instanceof FairSync;
}
/**
* 获取目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null
*/
protected Thread getOwner() {
return sync.getOwner();
}
/**
* 查询是否有线程正在等待
*/
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
/**
*查询给定线程是否正在等待获取此锁。
*/
public final boolean hasQueuedThread(Thread thread) {
return sync.isQueued(thread);
}
/**
* 获取正等待获取此锁的线程数
*/
public final int getQueueLength() {
return sync.getQueueLength();
}
/**
* 正等待获取此锁的线程集合
*/
protected Collection<Thread> getQueuedThreads() {
return sync.getQueuedThreads();
}
/**
*是否存在正在等待并符合相关给定条件的线程
*/
public boolean hasWaiters(Condition condition) {
if (condition == null)
throw new NullPointerException();
if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
throw new IllegalArgumentException("not owner");
return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}
/**
* 正在等待并符合相关给定条件的线程数量
*/
public int getWaitQueueLength(Condition condition) {
if (condition == null)
throw new NullPointerException();
if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
throw new IllegalArgumentException("not owner");
return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
}
/**
* 正在等待并符合相关给定条件的线程集合
*/
protected Collection<Thread> getWaitingThreads(Condition condition) {
if (condition == null)
throw new NullPointerException();
if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
throw new IllegalArgumentException("not owner");
return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
}
拓展
读写锁
-
ReentrantReadWriteLock
当读操作远远高于写操作时,这时候使用
读写锁让读-读可以并发,提高性能。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TestReadWriteLock {
public static void main(String[] args) {
DataContainer dataContainer = new DataContainer();
new Thread((() -> {
dataContainer.read();
}),"t1").start();
new Thread((() -> {
dataContainer.read();
}),"t2").start();
}
}
class DataContainer {
private static final Logger logger = LoggerFactory.getLogger(DataContainer.class);
private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock r = rw.readLock();
private ReentrantReadWriteLock.WriteLock w = rw.writeLock();
private Object data;
public Object read() {
r.lock();
try {
logger.info("读取");
Thread.sleep(1000);
return data;
} catch (InterruptedException e) {
e.printStackTrace();
return null;
} finally {
logger.info("释放读锁...");
r.unlock();
}
}
public void write(Object data) {
w.lock();
try {
logger.info("写入");
this.data = data;
} finally {
logger.info("释放写锁...");
w.unlock();
}
}
}
应用之缓存
1. 查询:先从缓存中找,缓存没有走数据库
2. 更新:清空缓存
3. 策略:先更新库再清空缓存,加读写锁来保证一致性。
2. StampedLock
JDK8为了进一步优化读性能引入StampedLock,配合【戳】使用
- 不支持条件变量
- 不支持可重入
加解读锁
long stamp = lock.readLock();
lock.unlockRead(stamp);
加解写锁
long stamp = lock.writeLock();
lock.unlockWrite(stamp),
乐观读
long stamp = lock.tryOptimisticRead();
//验戳
if(!lock.validate(stamp)){
//锁升级
}
使用样例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.locks.StampedLock;
public class TestReadWriteLock {
public static void main(String[] args) {
DataContainerStamped dataContainer = new DataContainerStamped(1);
new Thread((() -> {
dataContainer.read(1000);
}),"t1").start();
new Thread((() -> {
dataContainer.write(1000);
}),"t2").start();
}
}
class DataContainerStamped{
private static final Logger logger = LoggerFactory.getLogger(DataContainerStamped.class);
private int data;
private StampedLock lock = new StampedLock();
public DataContainerStamped(int data) {
this.data = data;
}
public void write(int data){
long stamp = lock.writeLock();
try{
this.data = data;
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
logger.info("write unlock:{}",stamp);
lock.unlockWrite(stamp);
}
}
public int read(long readTime){
long stamp = lock.tryOptimisticRead();
logger.info("optimistic read lock:{}",stamp);
try {
Thread.sleep(readTime);
} catch (InterruptedException e) {
e.printStackTrace();
return -1;
}
if(lock.validate(stamp)){
logger.info("read finish:{}",stamp);
return data;
}
//锁升级 - 读锁
logger.info("update to read lock:{}",stamp);
try{
stamp = lock.readLock();
logger.info("read lock:{}",stamp);
Thread.sleep(readTime);
logger.info("read finish:{}",stamp);
return data;
} catch (InterruptedException e) {
e.printStackTrace();
return -1;
} finally {
logger.info("read unlock:{}",stamp);
lock.unlockRead(stamp);
}
}
}