AQS和JUC-202509120206
AQS和JUC
ReentrantLock重⼊锁
重⼊锁可以完全替代synchronized关键字。在JDK5.0的早期版本中,重⼊锁的性能远远好于
synchronized,但从JDK6.0开始,JDK在synchronized上做了⼤量的优化,使得两者的性能差距
并
不⼤。重⼊锁对逻辑控制的灵活性要远远好于synchronized。
重⼊锁常⽤⽅法
void lock():获得锁,如果锁已经被占⽤,则等待。
void lockInterruptibly():获得锁,但优先响应中断。
boolean tryLock()
⾏等待,⽴即返回。尝试获得锁,如果成功,返回true;如果失败则返回false;获得不到锁,则
不进
boolean tryLock(long time, TimeUnit unit):在给定时间内尝试获得锁。
boolean isHeldByCurrentThread():判断担⼼线程是否持有锁。
void unlock():释放锁。
package locktest;
import java.sql.SQLOutput;
import java.util.concurrent.locks.ReentrantLock;
/**
到锁,则不进
*
*/
public class ReentrantLockTest {
之所以称之为重⼊锁,就是⼀个线程允许反复进⼊。当然,这⾥的反复仅仅局限于⼀个线程;如
果同⼀个线程多次获锁,那么在释放锁的时候,也必须释放相同次数。如果释放锁的次数多,那
么
会得到⼀个java.lang.IllegalMonitorStateException异常,反之,如果释放锁的次数少,那么相当
于线程还持有这个锁。如下所示:
public static int num;
public static void main(String[] args) throws InterruptedException
{
myTest myTest = new myTest();
Thread t1 = new Thread(myTest);
Thread t2 = new Thread(myTest);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("num = "+num);
}
static class myTest implements Runnable{
ReentrantLock reentrantLockTest = new ReentrantLock();
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执
行");
for (int i = 0; i < 10000000 ; i++) {
reentrantLockTest.lock();
try {
num++;
} finally {
reentrantLockTest.unlock();
}
}
System.out.println(Thread.currentThread().getName()+"执行完
毕");
}
}
}
package locktest;
中断响应 lockInterruptibly()
import java.util.concurrent.locks.ReentrantLock;
/**
*
*/
public class ReentrantLockTest1 {
public static void main(String[] args) {
new Thread(new task()).start();
}
static class task implements Runnable {
public ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
reentrantLock.lock();
System.out.println(Thread.currentThread().getName() + "获得
锁");
reentrantLock.lock();
System.out.println(Thread.currentThread().getName() + "再次
获得锁");
try {
System.out.println(Thread.currentThread().getName() +
"执行任务");
} finally {
reentrantLock.unlock();
System.out.println(Thread.currentThread().getName() +
"释放锁");
reentrantLock.unlock();
System.out.println(Thread.currentThread().getName() +
"再次释放锁");
reentrantLock.unlock();
System.out.println(Thread.currentThread().getName() +
"再次释放锁");
}
}
}
}
如果使⽤synchronized,要么获得锁,要么保持等待。⽽如果使⽤了重⼊锁,则提供了另⼀种可
能,那就是线程可以被中断。也就是在等待锁的过程中,程序可以根据需要取消对锁的请求。
即:
如果⼀个线程正在等待锁,那么它依然可以收到⼀个通知,被告知⽆须再等待,可以停⽌⼯作
了。
可以很好的应对死锁问题。示例如下所示:
package locktest;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import static java.lang.Thread.sleep;
/**
*
*/
public class lockInterruptiblyTest {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
public static void main(String[] args) throws InterruptedException
{
Thread t1 = new Thread(new ReenteruptiblyTest(lock1, lock2));
Thread t2 = new Thread(new ReenteruptiblyTest(lock2, lock1));
t1.start();
t2.start();
TimeUnit.MILLISECONDS.sleep(100);
System.out.println("t1.interrupt()...");
TimeUnit.MILLISECONDS.sleep(1000);
System.out.println("t2.interrupt()...");
t1.interrupt();
}
static class ReenteruptiblyTest implements Runnable {
private ReentrantLock lock1, lock2;
public ReenteruptiblyTest(ReentrantLock lock1, ReentrantLock
lock2) {
this.lock1 = lock1;
this.lock2 = lock2;
锁申请等待限时 tryLock(long time, TimeUnit unit)
除了等待外部通知之外,要避免死锁还有另外⼀种⽅式,就是限时等待。以下⾯为例,线程尝试
获得锁,如果没有获得锁,则等待5秒钟。如果5秒钟之后依然没有获得锁,则返回false,表示获
得
锁失败。
ryLock()⽅法也可以不带参数直接运⾏。在这种情况下,当前线程会尝试获得锁,如果锁并未被
其
他线程占⽤,则申请锁会成功,并⽴即返回true。如果锁被其他线程占⽤,则当前线程不会进
⾏等待,⽽是⽴即返回false。这种模式不会引起线程等待,因此也不会产⽣死锁
}
@Override
public void run() {
try {
//对lock1加锁
lock1.lockInterruptibly();
//获得1ock1的可中断锁
System.out.println(Thread.currentThread().getName()+",
加锁成功1-2!");
//等待1ock1和Lock2分别被两个线程获取。产生死锁现象
TimeUnit.MILLISECONDS.sleep(100);
//对lock2加锁
lock2.lockInterruptibly();
//获得1ock2的可中断锁
System.out.println(Thread.currentThread().getName()+",
加锁成功2-2!");
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+",
被中断!");
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
if (lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
System.out.println(Thread.currentThread().getName()+",
释放锁!");
}
}
}
}
package locktest;
import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
*/
public class TrtLockTest {
public static void main(String[] args) {
Task task = new Task();
new Thread(task).start();
new Thread(task).start();
}
static class Task implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
String name = Thread.currentThread().getName();
try {
//if (lock.tryLock(1, TimeUnit.SECONDS)) {
if (lock.tryLock()) {
System.out.println(System.currentTimeMillis() + name +
"获取锁成功");
Thread.sleep(3000);
} else {
System.out.println(System.currentTimeMillis() + name +
"获取锁失败");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
公平锁和⾮公平锁
在⼤多数情况下,锁的申请都是⾮公平锁。系统只是会从这个锁的等待线程中随机选择⼀个。
当⼊参为true时,则采⽤公平锁⽅式。要求系统维护⼀个有序队列,因此公平锁的实现成本⽐较
⾼,性能相对也⾮常低下。因此,默认情况下,锁是⾮公平的。如果没有特别的需求,也不需要
使
⽤公平锁。
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println(System.currentTimeMillis() +
name + "释放锁成功");
}
}
}
}
}
package locktest;
import java.util.concurrent.locks.ReentrantLock;
/**
*
*/
public class FairLockTest {
public static void main(String[] args) {
FairLockTask fairLockTask = new FairLockTask();
new Thread(fairLockTask).start();
new Thread(fairLockTask).start();
new Thread(fairLockTask).start();
new Thread(fairLockTask).start();
new Thread(fairLockTask).start();
new Thread(fairLockTask).start();
new Thread(fairLockTask).start();
new Thread(fairLockTask).start();
}
static class FairLockTask implements Runnable {
//开启公平锁
ReentrantLock lock = new ReentrantLock(true);
//开启非公平锁
Condition重⼊锁的搭配类
相同点
Condition和Object的wait()、notify()⽅法的作⽤是⼤致相同的,对应关系如下所示:
● Condition.await()--->Object.wait()
● Condition.signal()--->Object.notify()
● Condition.signalAll()--->Object.notifyAll()
不同点
Object.wait()和Object.notify()⽅法是和Synchronized关键字合作使⽤的;⽽Condition是与
ReentrantLock相关联的。
Condition常⽤⽅法
void await():
会使当前线程等待,同时释放当前锁,当其他线程中使⽤signal()或者signalAll()⽅法时,线程会
重新获得锁并继续执⾏。或者当线程被中断是,也能跳出等待。这和Object.wait()⽅法很相似。
void awaitUninterruptibly():
与await()⽅法基本相同,区别是它不会在等待过程中响应中断。
long awaitNanos(long nanosTimeout):
如果nanosTimeout时间内,没有被执⾏signal,则解除等待状态。
boolean await(long time, TimeUnit unit):
如果time时间内,没有被执⾏signal,则解除等待状态。
boolean awaitUntil(Date deadline):
如果deadline时间内,没有被执⾏signal,则解除等待状态。
//ReentrantLock lock = new ReentrantLock(false);
@Override
public void run() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"获得锁");
} finally {
lock.unlock();
}
}
}
}
void signal():
⽤于唤醒⼀个正在等待中的线程。
void signalAll():
⽤于唤醒所有正在等待中的线程。
package locktest;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
*
*/
public class ConditionTest {
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException
{
new Thread(() -> {
try {
lock.lock();
System.out.println("子线程"+
Thread.currentThread().getName() + "获得锁");
condition.await();
System.out.println( "子线程"+
Thread.currentThread().getName() + "被唤醒");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("子线程"+
Thread.currentThread().getName() + "释放锁");
}
}).start();
//主线程休眠1秒
Thread.sleep(1000);
System.out.println("主线程"+ Thread.currentThread().getName() +
"休眠");
lock.lock();
Semaphore信号量
⼴义上说,Semapore信号量是对锁的⼀种扩展;因为⽆论是内部锁synchronized,还是重⼊锁R
eentrantLock,⼀次都只允许⼀个线程访问某⼀资源,⽽信号量却可以指定多个线程同时访
问某⼀个资源。
信息量主要提供了⼀下构造函数,必须要指定信号量的准⼊数,即:同时能申请多少个许可
public Semaphore(int permits); // permits:准⼊数
public Semaphore(int permits, boolean fair); // permits:准⼊数,fair:是否公平获得锁
信息量主要⽅法如下所示
public void acquire();
尝试获得⼀个准⼊的许可。若⽆法获得,则线程会等待,直到有线程释放
⼀个许可或者当前线程被中断。
public void acquireUninterruptibly();
具有acquire⼀样的功能,但是不响应中断。
public void tryAcquire(); 尝试获得⼀个许可,如果成功就返回true,失败则返回false。
public void tryAcquire(long timeout, TimeUnit unit); 在指定时间内,尝试获得⼀个许可,如果成
功
就返回true,失败则返回false。
public void release();
资源访问结束后,释放⼀个许可。
申请了4个准⼊,循环10个线程,那么将会以4个线程⼀组为单位进⾏执⾏输出
System.out.println("主线程"+ Thread.currentThread().getName() +
"获得锁");
condition.signal();
System.out.println("主线程"+ Thread.currentThread().getName() +
"唤醒");
lock.unlock();
System.out.println("主线程"+ Thread.currentThread().getName() +
"释放锁");
}
}
package locktest;
import java.util.concurrent.Semaphore;
ReadWriteLock读写锁
ReadWriteLock是JDK5中提供的读写分离锁。它允许多个线程同时读。但是考虑到数据的完整
性,
写写操作和读写操作间依然是需要相互等待和持有锁的。读写锁的访问约束情况如下所示:
下⾯例⼦中,我们使⽤普通锁,执⾏读写操作:
(我们使⽤读写锁(只需要将上⾯例⼦中openRWLock=true即可)执⾏读写操作)
所以,如果在系统中,读操作的次数远远⼤于写操作,那么读写锁就可以发挥最⼤的效果,提升
系
统的性能。
/**
package locktest;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
*
*/
public class ReadWriteLockTest {
private static final Lock lock = new ReentrantLock();
private static final ReentrantReadWriteLock readWriteLock = new
ReentrantReadWriteLock();
private static final Lock readLock = readWriteLock.readLock();
private static final Lock writeLock = readWriteLock.writeLock();
public static void main(String[] args) {
boolean flag = true;
//开启三个线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
if (flag) {
excute(readLock, "读取数据");
} else {
excute(lock, "读取数据");
}
}).start();
}
for (int i = 0; i < 3; i++) {
new Thread(() -> {
if (flag) {
excute(writeLock, "写入数据");
} else {
excute(lock, "写入数据");
}
}).start();
}
CountDownLatch倒计时器
CountDownLatch是⼀个多线程控制⼯具。⽤来控制线程的等待。设置需要countDown的数量
num,然后每⼀个线程执⾏完毕后,调⽤countDown()⽅法,⽽主线程调⽤await()⽅法执⾏等
待,
直到num个⼦线程执⾏了countDown()⽅法 ,则主线程开始继续执⾏。
}
private static void excute(Lock lock, String msg) {
try {
lock.lock();
System.out.println(System.currentTimeMillis() / 1000 + " "
+ Thread.currentThread().getName() + msg);
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
package locktest;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
*
*/
public class CountDownLatchTest {
public static final CountDownLatch countDownLatch = new
CountDownLatch(3);
public static void main(String[] args) throws InterruptedException
{
ExecutorService executorService =
Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {
executorService.submit(new CountDownLatchTask(i,
countDownLatch));
}
countDownLatch.wait();
System.out.println("所有任务执行完毕");
executorService.shutdown();
}
static class CountDownLatchTask implements Runnable {
private Integer i;
private CountDownLatch countDownLatch;
public CountDownLatchTask(Integer i, CountDownLatch
countDownLatch) {
this.i = i;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
Random random = new Random();
Integer sleepTime = random.nextInt(1000);
TimeUnit.MILLISECONDS.sleep(sleepTime);
if (i == 1) {
System.out.println(Thread.currentThread().getName() + "子任务发生异常");
throw new RuntimeException("子任务发生异常");
}
System.out.println(Thread.currentThread().getName() +
"执行完成" + "休眠" + sleepTime);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "执行
完成");
}
}
}
CyclicBarrier循环栅栏
CyclicBarrier与CountDownLatch⾮常类似,它⽀持计数器的反复使⽤,CyclicBarrier可以
理解为循环栅栏。CyclicBarrier可以接收⼀个参数作为Runnable barrierAction,每当计数器
⼀次计数完成后——CyclicBarrier.await()时,系统会执⾏的动作。
CyclicBarrier.await()⽅法可能会抛出两种异常:⼀个是InterruptedException,也就是
在等待过程中,线程被中断,应该说这是⼀个⾮常通⽤的异常,⼤部分迫使线程等待的⽅法都可
能
会抛出这个异常,使得线程在等待时依然可以响应外部紧急事件。另外⼀个异常则是CyclicBarrie
r
特有的BrokenBarrierException,⼀旦遇到这个异常,则表示当前的CyclicBarrier已经破损
了,可能系统已经没有办法等待所有线程到⻬了。如果继续等待,可能就是徒劳⽆功的,因此就
此
结束吧。
package locktest;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
*
*/
public class CyclicBarrierTest {
public final static Integer NUMS = 3;
public static CyclicBarrier cyclicBarrier = new
CyclicBarrier(NUMS, new Teacher());
public static void main(String[] args) {
for (int i = 0; i < NUMS; i++) {
new Thread(new Student(cyclicBarrier, i)).start();
}
}
static class Student implements Runnable {
private CyclicBarrier cyclicBarrier;
LockSupport线程阻塞⼯具类
LockSupport是⼀个⾮常⽅便实⽤的线程阻塞⼯具,它可以在线程内任意位置让线程阻塞。和
Thread.suspend()相⽐,它弥补了由于resume()在前发⽣,导致线程⽆法继续执⾏的情况。和
Object.wait()⽅法相⽐,它不需要先获得某个对象的锁,也不会抛出InterruptedException
private volatile Integer studentNo;
public Student(CyclicBarrier cyclicBarrier, Integer studentNo)
{
this.cyclicBarrier = cyclicBarrier;
this.studentNo = studentNo;
}
@Override
public void run() {
try {
/* if (studentNo ==1) {
Thread.currentThread().interrupt();
}*/
System.out.println("学生" + studentNo + "开始准备考试");
cyclicBarrier.await();
System.out.println("学生" + studentNo + "准备交卷");
cyclicBarrier.await();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
static class Teacher implements Runnable {
private Integer nums = 1;
@Override
public void run() {
if (nums == 1) {
System.out.println("老师开始发试卷");
} else {
System.out.println("老师开始检查试卷");
}
nums++;
}
}
}
异常。
park()可以阻塞当前线程,其中每⼀个线程都有⼀个许可,该许可默认为[不可⽤]。如果该许可
是[可⽤]状态,那么park()⽅法会⽴即返回,消费这个许可,将该许可变更为[不可⽤]状态,流
程代码可以继续执⾏。如果该许可是[不可⽤]状态,那么park()⽅法将会阻塞;
unpark⽅法,将指定线程的⼀个许可变为[可⽤]状态
package locktest;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import static java.lang.Thread.sleep;
/**
*
*/
public class lockInterruptiblyTest {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
public static void main(String[] args) throws InterruptedException
{
Thread t1 = new Thread(new ReenteruptiblyTest(lock1, lock2));
Thread t2 = new Thread(new ReenteruptiblyTest(lock2, lock1));
t1.start();
t2.start();
TimeUnit.MILLISECONDS.sleep(100);
System.out.println("t1.interrupt()...");
TimeUnit.MILLISECONDS.sleep(1000);
System.out.println("t2.interrupt()...");
t1.interrupt();
}
static class ReenteruptiblyTest implements Runnable {
private ReentrantLock lock1, lock2;
public ReenteruptiblyTest(ReentrantLock lock1, ReentrantLock
lock2) {
this.lock1 = lock1;
this.lock2 = lock2;
}