自己学习记录,如有错误请提出进行修改,谢谢!
一线程创建方式
1、Thread方式
@Test
public void test1(){
Thread thread = new Thread("t1"){
@Override
public void run() {
System.out.println("线程运行逻辑方法区域");
System.out.println("线程名称:"+Thread.currentThread().getName());
}
};
//启动线程
thread.start();
}
控制台输出结果:
线程运行逻辑方法区域
线程名称:t1
1.1、start()方法与run()方法的区别
start()方法是启动线程的方法,线程执行start()方法后,线程开始启动,然后执行run()方法。run()方法是线程的执行主体,线程中的所有逻辑都在run()方法中执行。
2、Runable方式
@Test
public void test2(){
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("线程执行逻辑主体");
System.out.println("线程名称:"+Thread.currentThread().getName());
}
};
Thread t2 = new Thread(runnable, "t2");
//启动线程
t2.start();
}
控制台输出结果:
线程执行逻辑主体
线程名称:t2
2.1、Thread与Runable的关系
根据源码可知,Thread实现了Runable,Thread和Runable方式创建线程执行的方法是相同的。那么这两种创建线程的方式有什么区别呢?个人感觉是Runable方式将线程执行的逻辑与线程的创建分开。意思是说线程的执行逻辑代码和线程的创建可以不在同一个类中。
3、Callable方式,该方式可以返回线程的运行结果
@Test
public void test3(){
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
//线程返回值
System.out.println(Thread.currentThread().getName()+"线程执行逻辑");
return "线程运行结果";
}
};
FutureTask futureTask = new FutureTask(callable);
//创建线程
Thread thread = new Thread(futureTask,"t3");
thread.start();
try {
System.out.println("获取线程运行结果:"+futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
控制台输出结果:
t3线程执行逻辑
获取线程运行结果:线程运行结果
二线程池创建线程
Executors工程提供的四种线程池
1、newCachedThreadPool线程池
newCachedThreadPool线程池:创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收则新建线程(线程最大并发数并不可控制)。 适用场景:适用于并发执行大量短期的小任务,或者是负载较轻的服务器。
构造方法源码 :参数 核心线程:0,最大线程数:Integer.MAX_VALUE=2147483647,
救急线程存活时间60,单位秒,(核心线程为0,意思是所有的线程都是救急线程了,线程空闲60秒后就回收了)
SynchronousQueue队列没有容量一手交钱,一手交货。一个生产线程,当它生产产品(即put的时候),如果当前没有人想要消费产品(即当前没有线程执行take),此生产线程必须阻塞
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
@Test
public void test4(){
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> result = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("线程执行主体");
return "线程返回结果";
}
});
try {
System.out.println("获取线程结果="+result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
控制台输出结果:
线程执行主体
获取线程结果=线程返回结果
2、newFixedThreadPool线程池
newFixedThreadPool线程池:创建一个固定大小的线程池,可控制线程最大并发数,并发量超出核心线程数后,超出的线程将被存放到队列中等待。 适用场景:用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量。
源码分析:nThreads参数将作为核心线程数,最大线程数,没有救急线程,
队列为无界队列
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
@Test
public void test5(){
//测试核心线程数设置为1
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程执行主体execute方法无返回值");
}
});
}
3、newScheduledThreadPool线程池
newScheduledThreadPool线程池:创建一个定时线程池,支持定时及周期性任务。 适用场景:适用于多个后台线程执行周期任务,同时需要限制线程数量的场景。
源码:核心线程数:corePoolSize,最大线程数为Integer.MAX_VALUE=2147483647,
无救急线程,队列DelayedWorkQueue优先级队列
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
测试中发现,使用的是测试@Test主线程不能停止,否则定时线程没打印数据,只用main方法测试则不会出现这个问题
@Test
public void test6(){
System.out.println(new Date() +Thread.currentThread().getName()+"创建线程");
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(new Date()+Thread.currentThread().getName()+"线程执行主体");
}
}, 3, TimeUnit.SECONDS);参数: Runnable,延迟时间,延迟时间单位
try {
//使主线程保持活着
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//下面方式也是可以的
// ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
// ScheduledFuture<String> scheduledFuture = executorService.schedule(new Callable<String>() {
// @Override
// public String call() throws Exception {
// return new Date()+"call";
// }
// }, 3, TimeUnit.SECONDS);
// try {
// System.out.println(scheduledFuture.get());
// } catch (InterruptedException e) {
// e.printStackTrace();
// } catch (ExecutionException e) {
// e.printStackTrace();
// }
}
控制台输出结果:注意时间相隔了3秒
Tue Mar 22 14:19:39 CST 2022main创建线程
Tue Mar 22 14:19:42 CST 2022pool-1-thread-1线程执行主体
scheduleAtFixedRate方法,以固定速率执行
System.out.println(new Date() +Thread.currentThread().getName()+"创建线程");
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@SneakyThrows
@Override
public void run() {
Thread.sleep(4000);//程序执行了4秒
System.out.println(new Date()+Thread.currentThread().getName()+"线程执行主体");
}
}, 2,3, TimeUnit.SECONDS);参数:延迟2秒执行,每3秒执行一次,线程运行时间大于3秒的话,就会以线成运行时间进行循环
控制台输出结果:
Tue Mar 22 14:39:31 CST 2022main创建线程
Tue Mar 22 14:39:37 CST 2022pool-1-thread-1线程执行主体
Tue Mar 22 14:39:41 CST 2022pool-1-thread-1线程执行主体
Tue Mar 22 14:39:45 CST 2022pool-1-thread-1线程执行主体
Tue Mar 22 14:39:49 CST 2022pool-1-thread-1线程执行主体
Tue Mar 22 14:39:53 CST 2022pool-1-thread-1线程执行主体
scheduleWithFixedDelay方法,以固定延迟时间执行
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@SneakyThrows
@Override
public void run() {
Thread.sleep(4000);
System.out.println(new Date()+Thread.currentThread().getName()+"线程执行主体");
}
}, 2,3, TimeUnit.SECONDS);参数:初始延迟两秒执行,以后以3秒延迟执行
控制台打印结果:
Tue Mar 22 14:36:04 CST 2022main创建线程
Tue Mar 22 14:36:10 CST 2022pool-1-thread-1线程执行主体
Tue Mar 22 14:36:17 CST 2022pool-1-thread-1线程执行主体
Tue Mar 22 14:36:24 CST 2022pool-1-thread-1线程执行主体
Tue Mar 22 14:36:31 CST 2022pool-1-thread-1线程执行主体
4、newSingleThreadExecutor线程池
newSingleThreadExecutor线程池:创建一个单线程的线程池,它会用唯一的工作线程来执行任务。保证所有任务按照指定顺序执行。 适用场景:用于串行执行任务的场景,每个任务按顺序执行,不需要并发执行。
@Test
public void test7(){
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程执行主体");
}
});
}
控制台打印结果:
线程执行主体
三线程的控制方法
1、sleep方法
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@SneakyThrows
@Override
public void run() {
//睡2秒
sleep(2000);
System.out.println(new Date()+this.getName()+"睡醒两秒执行");
}
};
//启动t1线程
t1.start();
System.out.println(new Date()+Thread.currentThread().getName()+"主线程执行");
}
控制台打印结果:
Wed Mar 23 10:53:10 CST 2022main主线程执行
Wed Mar 23 10:53:12 CST 2022t1睡醒两秒执行
方法解释:
- 在线程内使用sleep方法,会使当前线程进入阻塞状态。
- 睡眠时间过后,如果cpu紧张,当前线程未必会得到时间片执行代码
- 睡眠期间,线程不会释放锁。
2、yield方法
当前线程调用yield方法,会使当前线程从运行状态变成就绪状态,让出cpu的使用,但是,需要注意的是,让出的CPU并不是代表当前线程不再运行了,如果在下一次竞争中,又获得了CPU时间片当前线程依然会继续运行。另外,让出的时间片只会分配给当前线程相同优先级的线程。
3、join方法
static int a=2;
public static void main(String[] args) throws InterruptedException {
Thread t1= new Thread() {
@SneakyThrows
@Override
public void run() {
//防止先执行了t1线程
sleep(2000);
a=5;
}
};
t1.start();
System.out.println(new Date()+"线程ti还没来得及改a的值:"+a);
//等待ti执行结束
t1.join();
System.out.println(new Date()+"线程tiy已经修改a的值:"+a);
}
控制台打印结果:
Wed Mar 23 11:35:12 CST 2022线程ti还没来得及改a的值:2
Wed Mar 23 11:35:14 CST 2022线程tiy已经修改a的值:5
方法解释:
- t1线程调用join方法,表示等待t1线程执行结束再执行主线程。
- join方法可以设置等待时间,等待超时将不再等待。
4、interrupt方法
打断正常运行的线程
static int b=1;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@SneakyThrows
@Override
public void run() {
for(int i=0;i<10000;i++){
System.out.println(new Date()+this.getName()+"ti线程正常执行"+b);
}
}
};
t1.start();
t1.interrupt();
System.out.println(new Date()+"t1打断状态"+t1.isInterrupted()+b);
}
控制台打印结果:
Wed Mar 23 15:05:52 CST 2022Thread-0ti线程正常执行2
Wed Mar 23 15:05:52 CST 2022t1打断状态true2
Wed Mar 23 15:05:52 CST 2022Thread-0ti线程正常执行3
Wed Mar 23 15:05:52 CST 2022Thread-0ti线程正常执行4
....省略
Wed Mar 23 15:05:52 CST 2022Thread-0ti线程正常执行10000
Wed Mar 23 15:05:52 CST 2022Thread-0ti线程正常执行10001
通过控制台打印信息发现,interrupt方法打断正常运行的线程,只是给该线程一个打断标记,但是该线程还是会继续执行下去。
打断睡眠的线程
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@SneakyThrows
@Override
public void run() {
sleep(3000);
System.out.println(new Date()+this.getName()+"睡醒3秒执行");
}
};
t1.start();
Thread.sleep(1000);
t1.interrupt();
System.out.println(new Date()+"t1打断状态"+t1.isInterrupted());
}
控制台打印信息:
Exception in thread "Thread-0" java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.example.thread.test.CreatThread$8.run(CreatThread.java:205)
Wed Mar 23 14:56:11 CST 2022t1打断状态false
打断睡眠中的线程,打断标记会被清空
打断join的线程
static int b=1;
static int c=1;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@SneakyThrows
@Override
public void run() {
for(int i=0;i<10000;i++){
b++;
System.out.println(new Date()+this.getName()+"ti线程正常执行"+b);
}
}
};
Thread t2 = new Thread() {
@SneakyThrows
@Override
public void run() {
t1.join();
for(int i=0;i<10000;i++){
c++;
System.out.println(new Date()+this.getName()+"ti线程正常执行"+c);
}
}
};
t1.start();
t2.start();
//t2此刻正在join阻塞中
t2.interrupt();
System.out.println(new Date()+"t1打断状态"+t1.isInterrupted()+b);
System.out.println(new Date()+"t2打断状态"+t2.isInterrupted()+c);
}
控制台打印信息:
Exception in thread "Thread-1" java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Thread.join(Thread.java:1252)
at java.lang.Thread.join(Thread.java:1326)
at com.example.thread.test.CreatThread$9.run(CreatThread.java:217)
Wed Mar 23 15:14:14 CST 2022t1打断状态false2
Wed Mar 23 15:14:14 CST 2022t2打断状态false1
Wed Mar 23 15:14:14 CST 2022Thread-0ti线程正常执行2
Wed Mar 23 15:14:14 CST 2022Thread-0ti线程正常执行3
在t2中执行了t1.join方法,t2线程会等待t1执行完再执行,t2正在阻塞中,主线程在这期间,打断t2,会出现报错,t2的打断标记被清空。
方法总结:
- 打断正常运行的线程,会给线程加上打断标记,但是线程会继续执行下去
- 打断sleep,wait,join线程,会出现报错,并清空打断标记。
5、wait方法、notify方法、notifyAll方法
wait方法和notify方法不是线程Thread的方法,而是Object的方法。
- 必须配合锁使用,只有获得锁后才能是有这两个方法。
- wait方法的作用是使获得锁的线程进入WaitSet(休息室)变成等待状态,也即是线程阻塞,此时是释放了锁,不占用cpu资源。sleep方法不释放锁。
- notify方法是从WaitSet(休息室)中随机唤醒一个线程,竞争锁,竞争cpu。
- notifyAll方法是从WaitSet(休息室)中唤醒所有线程,竞争锁,竞争cpu。
public class ThreadLock {
volatile static boolean flag=false;
static Object obj=new Object();
public static void main(String[] args) throws InterruptedException {
new Thread() {
@SneakyThrows
@Override
public void run() {
synchronized (obj){
//竞争到锁
while (!flag){
//不满足条件,进入休息室
System.out.println("条件不允许,不想干活,休息会");
obj.wait();
}
//条件满足,执行程序
System.out.println("条件允许了,该干活了");
}
}
}.start();
new Thread(){
@SneakyThrows
@Override
public void run() {
sleep(500);
synchronized (obj){
flag=true;
System.out.println("修改flag:"+flag);
System.out.println("唤醒线程:");
obj.notifyAll();
}
}
}.start();
}
}
控制台打印信息:
条件不允许,不想干活,休息会
修改flag:true
唤醒线程:
条件允许了,该干活了
四 线程锁synchronized
1、synchronized个人理解:
- synchronized是对象锁,可重入锁,同步锁,互斥的,一个线程A持有对象锁,另一个线程B需等待A线程将锁释放才能获取该对象锁。
- 根据第一点,可以发现并发量打大的话,会有很多阻塞线程,影响执行效率。
- 锁住的区域称作临界区,保证被锁住的对象的原子性,临界区的代码是一个整体,不会因为线程切换打断执行。
2、synchronized使用位置
- 作用在普通方法上,锁住的是类的实例对象。
- 作用在静态方法上,锁住的是类。
- 作用在代码块synchronized(this)锁住的是实例对象。
- 作用在代码块synchronized(User.class)锁住的是类。
- 作用在实例对象上synchronized(Object)锁住的是该对象。 网上有线程八锁,加深理解。
3、synchronized应用例子
1、作用在普通方法上,验证互斥性
public class ThreadLock {
String name="张三";
public synchronized void saveA(String t,String newName) {
//对name进行修改
try {
name=newName;
Thread.sleep(1000);
System.out.println(new Date()+"="+t+"=="+name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void saveB(String t,String newName) {
//对name进行修改
try {
name=newName;
Thread.sleep(1000);
System.out.println(new Date()+"="+t+"=="+name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ThreadLock t = new ThreadLock();
for (int i=0;i<10;i++){
int finalI = i;
new Thread("t1"){
@Override
public void run() {
t.saveA(finalI +"","张三");
}
}.start();
new Thread("t2"){
@Override
public void run() {
t.saveB(finalI +"","李四");
}
}.start();
}
}
}
控制台打印信息:
Thu Mar 24 10:45:00 CST 2022=0==张三
Thu Mar 24 10:45:01 CST 2022=9==李四
Thu Mar 24 10:45:02 CST 2022=9==张三
Thu Mar 24 10:45:03 CST 2022=6==张三
Thu Mar 24 10:45:04 CST 2022=7==李四
Thu Mar 24 10:45:05 CST 2022=8==张三
Thu Mar 24 10:45:06 CST 2022=7==张三
Thu Mar 24 10:45:08 CST 2022=6==李四
Thu Mar 24 10:45:09 CST 2022=8==李四
Thu Mar 24 10:45:10 CST 2022=5==李四
Thu Mar 24 10:45:11 CST 2022=5==张三
Thu Mar 24 10:45:12 CST 2022=3==李四
Thu Mar 24 10:45:13 CST 2022=4==张三
Thu Mar 24 10:45:14 CST 2022=4==李四
Thu Mar 24 10:45:15 CST 2022=3==张三
Thu Mar 24 10:45:16 CST 2022=2==李四
Thu Mar 24 10:45:17 CST 2022=2==张三
Thu Mar 24 10:45:18 CST 2022=1==李四
Thu Mar 24 10:45:19 CST 2022=1==张三
Thu Mar 24 10:45:20 CST 2022=0==李四
代码及结果现象解释:
- saveA,saveB方法执行只需1秒。
- main方法中创建了1个对象,20个线程,10个调用saveA方法,10个调用saveB方法,20个线程争抢1个对象锁。
- 20个线程,按照以前的思维,觉得应该1秒就能执行完,但是根据打印结果显示是20秒执行完。
- 根据打印信息发现,20个线程是串行执行,印正了synchronized的互斥性,一个线程获得锁,其他线程必须等待线程释放了锁。
- 根据打印信息的顺序发现,线程执行顺序是随机的,谁先抢到锁谁先执行。
2、作用在普通方法上,验证原子性
public class ThreadLock {
int a=0;
public void incr(){
for (int i = 0; i <1000 ; i++) {
a++;
}
}
public void decr(){
for (int i = 0; i <1000 ; i++) {
a--;
}
}
public int getA(){
return a;
}
public static void main(String[] args) throws InterruptedException {
ThreadLock t = new ThreadLock();
Thread t1= new Thread() {
@Override
public void run() {
t.incr();
}
};
Thread t2= new Thread() {
@Override
public void run() {
t.decr();
}
};
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终结果a="+t.getA());
}
}
控制台打印结果:
最终结果a=393
代码及结果解释:
- main方法创建2个线程,分别调用incr()和decr()方法,这个个方法对a进行加减操作。
- a++和a--不是原子操作。
- 按照以前的思维觉得结果a=0,但是a却是其他值。说明代码执行顺序出现了混乱。 顺序混乱解释:
- 以a++为例,a++可以拆分成a=a+1,假如此时a=5在线程执行了a+1=6的操作后还没来得及将a=5改a=6,这时该线程的时间片到了,进行了线程切换.
- 另一个线程调用a--方法,此时a还是5,a--执行完后,a=4。然后时间片用完进行切换。
- 前一个线程将6赋值给a,继续执行a++。
- 以次上述现象,a最终的值就会无法确定。 问题解决:在incr()和decr()方法上加上synchronized字段即可,以此验证了synchronized保证了临界区域的原子性。
3、作用在静态方法上
public class ThreadLock {
public static synchronized void a() {
sleep(1);
System.out.println("1");
}
public synchronized void b() {
System.out.println("2");
}
public static void main(String[] args) {
ThreadLock t = new ThreadLock();
new Thread(()->{ t.a(); }).start();
new Thread(()->{ t.b(); }).start();
}
}
控制台打印结果:
2 1秒后打印1
结果分析:synchronized在静态方法a和普通方法b上,a和b锁住的对象不同,因此两个线程不存在锁竞争,u因此两个线程同时执行,a方法要睡1秒,因此输出的要慢一秒。因此要注意要竞争的锁对象是否是同一个。 4、作用在对象实例上
String a="张三";
public void a(){
synchronized (a){
}
}
五 volatile字段
volatile字段字段作用是保证可见性和禁止指令重排序
1、保证可见性
static boolean flag=true;
public static void main(String[] args) throws InterruptedException {
Thread t= new Thread() {
@Override
public void run() {
while (flag){
}
}
};
t.start();
Thread.sleep(1000);
flag=false;
}
根据运行结果发现线程t一直在运行没有停止。按照我们的想法是,主线程修改了flag的值,t线程应该会根据flag的值停止循环。但是实际情况并不是这样。
原理如下
jvm会对t线程的run方法代码进行重排,t线程第一次获取flag的值后,会将该值存到t线程的内存中,t线程每次是从自己线程内存中获取值。主线程修改flag值,该值并不会同步修改到t内存中的值,因此主线程修改的值对t线程来说并不保证可见性,也许可见,也许不可见。因此会出现程序无法停止的现象。为了解决这个问题,需要在flag字段添加volatile字段。由此可以验证可见性
2、禁止重排序
六 ReentrantLock可重入锁
1、加锁,释放锁
public class ThreadLock {
static ReentrantLock lock= new ReentrantLock();
public static void a(){
//加锁
lock.lock();
System.out.println("执行加锁操作");
//执行逻辑代码
try {
System.out.println("执行逻辑代码");
//故意制作错误,看看锁是不是释放了
int b=1/0;
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁,一定要有finally进行释放锁,方式程序运行出错锁无法释放
lock.unlock();
System.out.println("释放锁");
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(){
@Override
public void run() {
a();
}
}.start();
}
}
控制台打印信息:
执行加锁操作
执行逻辑代码
释放锁
java.lang.ArithmeticException: / by zero
at com.example.thread.test.ThreadLock.a(ThreadLock.java:23)
at com.example.thread.test.ThreadLock$1.run(ThreadLock.java:37)
2、可打断
static ReentrantLock lock= new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t= new Thread(){
@Override
public void run() {
try {
if(lock.tryLock()){
System.out.println("尝试获得锁,并获得了锁");
}else {
System.out.println("尝试获得锁,没有获得锁");
}
//等待锁时可打断
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("等待锁时被打断");
return;
}
try {
System.out.println("获得锁");
} finally {
lock.unlock();
}
}
};
//主线程获得锁
lock.lock();
System.out.println("主线程获得锁");
t.start();
try {
Thread.sleep(1000);
t.interrupt();
System.out.println("打断t线程");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
控制台打印结果:
主线程获得锁
尝试获得锁,没有获得锁
打断t线程
等待锁时被打断
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at com.example.thread.test.ThreadLock$1.run(ThreadLock.java:42)
if(lock.tryLock())改成while的话,会一直进行循环尝试
public static void main(String[] args) throws InterruptedException {
Thread t= new Thread(){
@Override
public void run() {
try {
while(!lock.tryLock()){
//睡会,降低cpu占用率
sleep(1000);
System.out.println(new Date()+"尝试获得锁,没有获得锁");
}
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("等待锁时被打断");
return;
}
try {
System.out.println("获得锁");
} finally {
lock.unlock();
}
}
};
//主线程获得锁
lock.lock();
System.out.println("主线程获得锁");
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
控制台打印信息:
主线程获得锁
Fri Mar 25 13:39:31 CST 2022尝试获得锁,没有获得锁
Fri Mar 25 13:39:32 CST 2022尝试获得锁,没有获得锁
Fri Mar 25 13:39:33 CST 2022尝试获得锁,没有获得锁
获得锁
3、条件变量
可以根据情况,唤醒某一个条件变量的线程
public class ThreadLock {
static ReentrantLock lock= new ReentrantLock();
static Condition aroom= lock.newCondition();
static Condition broom= lock.newCondition();
static volatile boolean a=false;
static volatile boolean b=false;
public static void main(String[] args) throws InterruptedException {
new Thread(){
@SneakyThrows
@Override
public void run() {
try {
System.out.println(new Date()+"a线程执行");
lock.lock();
while (!a){
System.out.println(new Date()+"进入aroom休息");
aroom.await();//会释放锁
}
System.out.println(new Date()+"条件a满足,可以干活了");
} finally {
System.out.println(new Date()+"a释放锁");
lock.unlock();
}
}
}.start();
new Thread(){
@SneakyThrows
@Override
public void run() {
try {
System.out.println(new Date()+"b线程执行");
lock.lock();
while (!b){
System.out.println(new Date()+"进入aroom休息");
broom.await();//会释放锁
}
System.out.println(new Date()+"条件b满足,可以干活了");
} finally {
System.out.println(new Date()+"b释放锁");
lock.unlock();
}
}
}.start();
try {
Thread.sleep(1000);
lock.lock();
a=true;
System.out.println(new Date()+"a状态改变:"+a);
aroom.signal();
Thread.sleep(1000);
b=true;
System.out.println(new Date()+"b状态改变:"+b);
broom.signal();
} finally {
lock.unlock();
}
}
}
控制台打印信息:
Fri Mar 25 14:12:43 CST 2022a线程执行
Fri Mar 25 14:12:43 CST 2022b线程执行
Fri Mar 25 14:12:43 CST 2022进入aroom休息
Fri Mar 25 14:12:43 CST 2022进入aroom休息
Fri Mar 25 14:12:44 CST 2022a状态改变:true
Fri Mar 25 14:12:45 CST 2022b状态改变:true
Fri Mar 25 14:12:45 CST 2022条件a满足,可以干活了
Fri Mar 25 14:12:45 CST 2022a释放锁
Fri Mar 25 14:12:45 CST 2022条件b满足,可以干活了
Fri Mar 25 14:12:45 CST 2022b释放锁