一、基础机制
Executor
在上一篇《Java线程之状态及创建》
里有提到过,Executor管理多个异步任务执行,用户无需直接进行线程的管理。在这里就不做讲解了。
Daemon Thread
jvm的GC就是一个典型的守护线程,守护线程和用户线程最大的区别在于:
1)用户线程时高优先级线程,jvm将在终止任务前等待用户线程执行完毕。
2)守护线程时低优先级线程,为用户线程提供服务。
当jvm发现没有用户线程后,守护线程会随着jvm一同结束。
声明守护线程:
@Slf4j
public class JunitTest {
@Test
public void test() throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("test......");
});
// 将线程设置为守护线程
thread.setDaemon(true);
// 启动线程
thread.start();
Thread.sleep(3000);
}
}
注意:
1)thread.setDaemon(true);
必须在线程调用thread.start();
之前设置,否则会抛出异常IllegalThreadStateException
。
2)在守护线程中产生的新线程也都是守护线程。
Thread.sleep(long)
使当前正在执行的线程以指定的毫秒数暂停,线程状态进入Timed Waiting
,如果该线程持有锁,则锁不释放!Object.wait()
线程进入Wating
状态,线程如果持有锁,则释放锁!
Thread.yield()
会告知线程调度器放弃对处理器的占用,但是调度器可以忽略这个通知,该方法主要时为了保障线程间调度的连续性,防止某个线程长期占用处理器,说白了,该方法只是对线程调度器的一个建议而且也只是建议具有相同优先级的其它线程可以运行。
二、中断
线程在运行完成后会自动结束,在发生异常后也会自动结束(程序员手动try catch异常且没有抛出此异常不会导致线程结束)。
InterruptedException
通过调用一个线程的interrupt()
方法来中断这个线程,如果线程处于Timed Waiting
、Waiting
状态,那么就会抛出InterruptedException
异常,从而提前结束异常,此时线程状态为Terminated
。但是不能中断IO阻塞。
interrupted()
如果一个线程的run()
方法在执行一个无限循环,并且没有Thread.sleep()
之类会导致线程抛出InterruptedException
异常的操作,那么调用线程的inerrupted()
方法无法是线程提前结束。
Executor的中断
调用Executor的shutdown()
方法会在线程都执行完毕后再关闭,但是如果调用Executor的shutdownNow()
方法,相当于调用每个线程的interrupt()
方法。
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
try {
Thread.sleep(2000);
System.out.println("线程aaa执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
executorService.shutdownNow();
System.out.println("主线程执行完毕");
}
主线程执行完毕
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at cn.xx.xx.test.JunitTest.lambda$main$0(JunitTest.java:27)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
上面说到了,可以使用shutdown()
和shutdownNow()
方法来中断线程,但是这两个方法将会关闭池子里的所有线程。如果向中断某一个线程,可以使用submit()
方法来提交一个线程,该方法可以返回一个Future<?>
对象,调用该对象的cancel(true)
可以中断线程。
Future<?> future = executorService.submit(() -> {
// ..
});
future.cancel(true);
三、互斥同步
可以参考我之前写过的一篇文章《Java 锁》
。
四、通信协作
2017年,记得刚刚入职现在这家公司的时候,领导给我指派了一个,开发基于销售订单
、销售退单
及其它单据为数据蓝本,经过一系列聚合计算,得出某项数值的任务,我最终建模为四个数据存储模型,对应数据库里的四张表,简称为A、B、C、D
D数据是通过A、B、C数据计算好后通过一系列聚合计算产生的,但是A、B、C数据的计算无需互相依赖。当时上线的第一个版本采用的同步形式,即A -> B -> C -> D,计算时间大约为2~4分钟(o)后来随着学习的深入,我发现可以使用多线程进行上述计算。
过程:
创建四个线程分别对应A、B、C、D的数据计算任务,A、B、C三个任务可以同时进行,但是D数据的计算需要等待前面三个线程计算完毕才能计算,在线程D里,使用threadA.join()
、threadB.join()
、threadC.join()
或者使用CountDownLatch
,最后将计算时间缩短为原来的1/2,虽然这段业务代码随着后期系统重构改造消失不见~但是对我的启发是真的大。
join()
在线程中调用另外一个线程的join()
方法,当前线程被挂起进入Waiting
状态,直到被调用线程执行完毕。
Object - wait()、notify()、notifyAll()
调用wait()
方法,会使线程挂起进入Waiting
状态,其它线程可以使用notify()
、notifyAll()
方法将这个线程唤醒,这三种方法都是属于Object
的方法。而且这三个方法只能在被synchronized
修饰的方法和代码块中执行,否则抛出IllegalMonitorStateException
异常。
在调用wait()
方法后,线程被挂起,锁被交出,如果不交出锁,也不可能有其它线程进入方法或者代码块来唤醒这个线程,岂不就是死锁了?
在使用synchronized
关键字时,配合使用Object的wait()
、notify()
、notifyAll()
方法达到线程间通信协作的目的。
Condition - await()、awaitNanos(long)、awaitUntil(Date)、awaitUninterruptibly()、signal()、signalAll()
Condition Java 1.5 才出的,用以配合使用Lock
锁时的线程间通信协作,Condition必须要Lock实现类去创建。
而且Condition的await指定等待的条件,更加的灵活。
@Slf4j
public class JunitTest {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
@Test
public void test() throws InterruptedException {
Thread thread1 = new Thread(() -> {
lock.lock();
try {
log.info("线程1启动,但是需要等待线程2通知,我先休息休息");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("线程2通知我干活了,去打扫战场喽~");
lock.unlock();
});
Thread thread2 = new Thread(() -> {
lock.lock();
log.info("线程2启动,开始做任务刷副本~");
log.info("副本做完了,准备通知线程1打扫战场,我先休息休息");
condition.signal();
log.info("通知线程1完毕");
lock.unlock();
});
thread1.start();
Thread.sleep(2000);
thread2.start();
Thread.sleep(10000);
}
}
16:12:40.661 [Thread-0] INFO cn.xx.x.test.JunitTest - 线程1启动,但是需要等待线程2通知,我先休息休息
16:12:42.660 [Thread-1] INFO cn.xx.x.test.JunitTest - 线程2启动,开始做任务刷副本~
16:12:42.660 [Thread-1] INFO cn.xx.x.test.JunitTest - 副本做完了,准备通知线程1打扫战场,我先休息休息
16:12:42.660 [Thread-1] INFO cn.xx.x.test.JunitTest - 通知线程1完毕
16:12:42.661 [Thread-0] INFO cn.xx.x.test.JunitTest - 线程2通知我干活了,去打扫战场喽~