目录
5.2.3 newSingleThreadExecutor(常用)
5.2.4 newScheduleThreadPool(了解)
一、基本概念
JUC 就是 java.util .concurrent 工具包的简称。这是一个处理线程的工具包,JDK 1.5 开始出现的。
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程— —资源分配的最小单位。
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个 单元执行流。线程——程序执行的最小单位。
并发:同一时刻多个线程在访问同一个资源,多个线程对一个点。 例子:春运抢票 ...
并行:多项工作一起执行,互不干扰,之后再汇总。 例子:泡方便面,电水壶烧水,一边撕调料倒入桶中
用户线程:平时用到的普通线程,自定义线程
守护线程:运行在后台,是一种特殊的线程,比如垃圾回收
当主线程结束后,若用户线程还在运行,则JVM 存活;如果没有用户线程,都是守护线程,则JVM 结束
线程的状态:
NEW,(新建)、RUNNABLE,(准备就绪)、BLOCKED,(阻塞)、WAITING,(不见不散)、TIMED_WAITING,(过时不候)、TERMINATED;(终结)
wait/sleep 的区别
(1)sleep 是 Thread 的静态方法,wait 是 Object 的方法,任何对象实例都能调用。
(2)sleep 不会释放锁,它也不需要占用锁。wait 会释放锁,但调用它的前提是当前线程占有锁(即代码要在 synchronized 中)。
(3)它们都可以被 interrupted 方法中断。
管程(monitor)
是保证了同一时刻只有一个进程在管程内活动,即管程内定义的操作在同一时刻只被一个进程调用(由编译器实现).但是这样并不能保证进程以设计的顺序执行
JVM 中同步是基于进入和退出管程(monitor)对象实现的,每个对象都会有一个管程 (monitor)对象,管程(monitor)会随着 java 对象一同创建和销毁
执行线程首先要持有管程对象,然后才能执行方法,当方法完成之后会释放管程,方法在执行时候会持有管程,其他线程无法再获取同一个管程
二、Lock与synchronized
2.1 synchronized
synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{} 括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修饰一个静态方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修饰一个类,其作用的范围是 synchronized 后面括号括起来的部分,作用主的对象是这个类的所有对象。
对于synchronized 修饰的内容,线程释放锁只会有两种情况:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时 JVM 会让线程自动释放锁。
class Ticket{
private int num=50;
public synchronized void sale(){
if(num>0){
System.out.println(Thread.currentThread().getName()+":卖出:"+(num--)+",剩下:"+num);
}
}
}
/**
* 卖票的例子,A、B、C三者同时买50张票
*/
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket=new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<40;i++){
ticket.sale();
}
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<40;i++){
ticket.sale();
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<40;i++){
ticket.sale();
}
}
},"C").start();
}
}
2.2 Lock
Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对 象。Lock 提供了比 synchronized 更多的功能。
Lock与synchronized 的区别:
1. Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;
2. synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
3. Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,不能响应中断;
4. 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
5. Lock 可以提高多个线程进行读操作的效率。 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于 synchronized。
采用 Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一 般来说,使用 Lock 必须在 try{} catch{}块中进行,并且将释放锁的操作放在 finally 块中进行,以保证锁一定被被释放,防止死锁的发生。
Lock中的重要类与方法:
Lock():是用来获取锁。如果锁已被其他 线程获取,则进行等待。
Contition 类:返回 Condition 对象,Condition 类也可以实现等待/通知模式。(类似于使用 synchronized 时 wait()/notify()的用法。但用 notify()通知时,JVM 会随机唤醒某个等待的线程, 使用 Condition 类可以进行选择性通知)
• await()会使当前线程等待,同时会释放锁,当其他线程调用 signal()时,线程会重 新获得锁并继续执行。
• signal()用于唤醒一个等待的线程。
class LTicket {
private int num = 50;
private final ReentrantLock lock = new ReentrantLock();
public void sale() {
lock.lock();
try {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + ":卖出:" + (num--) + ",剩下:" + num);
}
} finally {
lock.unlock();
}
}
}
/**
* 卖票的例子,A、B、C三者同时买50张票
*/
public class LSaleTicket {
public static void main(String[] args) {
LTicket ticket=new LTicket();
new Thread(()->{
for(int i=0;i<40;i++){
ticket.sale();
}
},"A").start();
new Thread(()->{
for(int i=0;i<40;i++){
ticket.sale();
}
},"B").start();
new Thread(()->{
for(int i=0;i<40;i++){
ticket.sale();
}
},"C").start();
}
}
2.2.1 ReentrantLock
ReentrantLock,意思是“可重入锁”,关于可重入锁的概念将在后面讲述。 ReentrantLock 是唯一实现了 Lock 接口的类,并且 ReentrantLock 提供了更多的方法
2.2.2 ReadWriteLock
ReadWriteLock 也是一个接口,在它里面只定义了两个方法-返回写锁/读锁
也就是说将文件的读写操作分开,分成 2 个锁来分配给线程,从而使得多个线程可以同时进行读操作。
ReentrantReadWriteLock 实现了 ReadWriteLock 接口,最主要的有两个方法:
readLock() 和 writeLock() 用来获取读锁和写锁
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
... ...
rwl.readLock().lock();
... ...
rwl.readLock().unlock();
注意:
• 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
• 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。
三、线程间通信
线程间通信的模型有两种:共享内存和消息传递,以下方式都是基于这两种模型来实现的。
使用synchroniza关键字:
class Resource{
private int num=0;
public synchronized void inc() throws InterruptedException {
while (num!=0) this.wait();//判断,防止了虚假唤醒
num++;//干活
System.out.println(Thread.currentThread().getName()+"->"+num);
notifyAll();//通知
}
public synchronized void dec() throws InterruptedException {
while (num!=1) this.wait();//判断,防止了虚假唤醒
num--;//干活
System.out.println(Thread.currentThread().getName()+"->"+num);
notifyAll();//通知
}
}
/**
* A、B两个线程分别进行+1、-1操作,二者相互通信,交替执行
*/
public class ThreadCommunication {
public static void main(String[] args) {
Resource resource=new Resource();
new Thread(()->{
for(int i=1;i<=10;i++){
try {
resource.inc();//+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for(int i=1;i<=10;i++){
try {
resource.dec();//-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
使用Lock:
class LResource{
private int num=0;
private final ReentrantLock lock = new ReentrantLock();
private Condition condition=lock.newCondition();
public void inc() throws InterruptedException {
lock.lock();
try {
while (num!=0) condition.await();//判断,防止了虚假唤醒
num++;//干活
System.out.println(Thread.currentThread().getName()+"->"+num);
condition.signalAll();//通知
} finally {
lock.unlock();
}
}
public void dec() throws InterruptedException {
lock.lock();
try {
while (num!=1) condition.await();//判断,防止了虚假唤醒
num--;//干活
System.out.println(Thread.currentThread().getName()+"->"+num);
condition.signalAll();//通知
} finally {
lock.unlock();
}
}
}
/**
* A、B两个线程分别进行+1、-1操作,二者相互通信,交替执行
*/
public class LThreadCommunication {
public static void main(String[] args) {
LResource lResource = new LResource();
new Thread(()->{
for(int i=1;i<=10;i++){
try {
lResource.inc();//+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for(int i=1;i<=10;i++){
try {
lResource.dec();//-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
注意:
使用Lock时代码的判断条件部分必须用while语句,因为如果用if,会产生虚假唤醒的后果:假如一个线程不满足判断条件,处于等待状态,那它再次被唤醒时将跳过这个判断条件,直接向下进行。
while (num!=0) condition.await();//判断,防止了虚假唤醒 不建议用if
线程间还可进行定制化通信:
class LLResource{
private int flag=1;
private Lock lock=new ReentrantLock();
private Condition c1=lock.newCondition();
private Condition c2=lock.newCondition();
private Condition c3=lock.newCondition();
public void print5(int loop) throws InterruptedException {
lock.lock();
try {
while (flag!=1){
c1.await();
}
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"->"+i+"轮数"+loop);
}
flag=2;
c2.signal();
} finally {
lock.unlock();
}
}
public void print10(int loop) throws InterruptedException {
lock.lock();
try {
while (flag!=2){
c2.await();
}
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"->"+i+"轮数"+loop);
}
flag=3;
c3.signal();
} finally {
lock.unlock();
}
}
public void print15(int loop) throws InterruptedException {
lock.lock();
try {
while (flag!=3){
c3.await();
}
for(int i=0;i<15;i++){
System.out.println(Thread.currentThread().getName()+"->"+i+"轮数"+loop);
}
flag=1;
c1.signal();
} finally {
lock.unlock();
}
}
}
/**
* A 线程打印 5 次 A,B 线程打印 10 次 B,C 线程打印 15 次 C,按照此顺序循环 10 轮
*/
public class ThreadDemo {
public static void main(String[] args) {
LLResource resource= new LLResource();
new Thread(()->{
for(int i=1;i<=10;i++){
try {
resource.print5(i);//+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for(int i=1;i<=10;i++){
try {
resource.print10(i);//+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for(int i=1;i<=10;i++){
try {
resource.print15(i);//+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
}
}
四、集合的线程安全
4.1 ArryList
为什么不安全?查看其add方法的源码可知是不安全的,如果有多个线程同时add,并打印,则会报错。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
如何解决?
1.采用Vector 。它的add方法是线程安全的(但太老了,基于synchronized,效率低)
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
2.使用Collections 提供的方法 synchronizedList
List list = Collections.synchronizedList(new ArrayList<>());
3.使用CopyOnWriteArrayList 。
特点:
1. 独占锁效率低:采用读写分离思想解决
2. 写线程获取到锁,其他写线程阻塞
3.复制思想(写时复制技术):
当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行 Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。 这时候会抛出来一个新的问题,也就是数据不一致的问题。如果写线程还没来得及写入内存,其他的线程就会读到了脏数据(原来的数据)。
因此,该类型集合最适合于具有以下特征的应用程序:
List 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。
List list = new CopyOnWriteArrayList();
4.2 HashSet/HashMap
二者也是线程不安全的。有了上面的理解,直接给出二者对应的线程安全的集合:
Set<String> set = new CopyOnWriteArraySet<>();
Map<String,String> map = new HashTable<>();//synchronized(性能低)
Map<String,String> map = Collections.synchronizedMap(new HashMap<>); //装饰器模式,synchronized(性能低)
Map<String,String> map = new ConcurrentHashMap<>();//适合高并发
为啥ConcurrentHashMap并发性能好呢?
首先了解Segment。Segment本身就相当于一个HashMap对象。
ConcurrentHashMap与其他几种类型的区别在于它包含好多个Segment,可以说,ConcurrentHashMap是一个二级哈希表。在一个总的哈希表下面,有若干个子哈希表。
不同Segment的写入是可以并发执行的。
同一Segment的写和读是也可以并发执行。
Segment的写入是需要上锁的,因此对同一Segment的并发写入会被阻塞。
再回顾其他类型的集合,写、读操作都给整个集合加锁,降低了并发效率。
由此可见,ConcurrentHashMap当中每个Segment各自持有一把锁。在保证线程安全的同时降低了锁的粒度,让并发操作效率更高。
五、线程池
5.1 概念
线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销, 进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
优点:
• 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
• 提高响应速度: 当任务到达时,任务可以不需要等待线程创建就能立即执行。
• 提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池的优势: 线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量, 超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor,Executors, ExecutorService,ThreadPoolExecutor 这几个类
线程池常用参数:
• corePoolSize 线程池的核心线程数
• maximumPoolSize 能容纳的最大线程数
• keepAliveTime 空闲线程存活时间
• unit 存活的时间单位
• workQueue 存放提交但未执行任务的队列
• threadFactory 创建线程的工厂类
• handler 等待队列满后的拒绝策略
当提交任务数大于 corePoolSize 的时候,会优先将任务放到 workQueue 阻塞队列中。当阻塞队列饱和后,会扩充线程池中线程数,直到达到 maximumPoolSize 最大线程数配置。此时,再多余的任务,则会触发线程池的拒绝策略了。
CallerRunsPolicy: 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
AbortPolicy: 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
DiscardPolicy: 直接丢弃,其他啥都没有
DiscardOldestPolicy: 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入
5.2 线程池的种类与创建
5.2.1 newCachedThreadPool(常用)
作用:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.
特点:
• 线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
• 线程池中的线程可进行缓存重复利用和回收(回收默认时间为 1 分钟)
• 当线程池中,没有可用线程,会重新创建一个线程
ExecutorService pool=Executors.newCachedThreadPool();
场景: 适用于创建一个可无限扩大的线程池,服务器负载压力较轻,执行时间较 短,任务多的场景
5.2.2 newFixedThreadPool(常用)
作用:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这 些线程。在任意点,在大多数线程会处于处理任务的活动状态。如果在所有线 程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中 等待。
特征:
• 线程池中的线程处于一定的量,可以很好的控制线程的并发量
• 线程可以重复被使用,在显示关闭之前,都将一直存在
• 超出一定量的线程被提交时候需在队列中等待
ExecutorService pool=Executors.newFixedThreadPool(5)// 固定5个线程
场景: 适用于可以预测线程数量的业务中,或者服务器负载较重,对线程数有严格限制的场景
5.2.3 newSingleThreadExecutor(常用)
作用:创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该 线程。
特征: 线程池中最多执行 1 个线程,之后提交的线程活动将会排在队列中以此执行
ExecutorService pool= Executors.newSingleThreadExecutor();//一池一线程
场景: 适用于需要保证顺序执行各个任务,并且在任意时间点,不会同时有多个 线程的场景
5.2.4 newScheduleThreadPool(了解)
作用: 线程池支持定时以及周期性执行任务,创建一个 corePoolSize 为传入参数,最大线程数为整形的最大数的线程池**
特征:
(1)线程池中具有指定数量的线程,即便是空线程也将保留
(2)可定时或者 延迟执行线程活动
ScheduledExecutorService pool = Executors.newScheduledThreadPool(6);
场景: 适用于需要多个后台线程执行周期任务的场景
5.3 原理与注意事项
1. 在创建了线程池后,线程池中的线程数为0
2. 当调用 execute()方法添加一个请求任务时,线程池会做出如下判断:
2.1 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务; 2.2 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列; 2.3 如果这个时候队列满了且正在运行的线程数量还小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4 如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3. 当一个线程完成任务时,它会从队列中取下一个任务来执行
4. 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
4.1 如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。
4.2 所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
注意事项:
通常建议自定义线程池,原因如下(OOM-内存溢出):
/**
* 自定义线程池
*/
public class ThreadPoolDemo2 {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
for(int i=1;i<=10;i++){
pool.execute(()->{
System.out.println(Thread.currentThread().getName()+"办理业务中");
});
}
}
finally {
pool.shutdown();
}
}
}
pool-1-thread-1办理业务中
pool-1-thread-2办理业务中
pool-1-thread-3办理业务中
pool-1-thread-2办理业务中
pool-1-thread-1办理业务中
pool-1-thread-1办理业务中
pool-1-thread-2办理业务中
pool-1-thread-5办理业务中
pool-1-thread-4办理业务中
pool-1-thread-3办理业务中
六、LockSupport与线程中断
6.1 啥是线程中断?为什么引入?
首先 一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。
其次 在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的机制——中断。
中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
6.2 线程中断相关API
具体来说,当对一个线程,调用 interrupt() 时:
① 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。 被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
② 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法, 那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
Thread类的静态方法interrupted() 返回当前线程的中断状态(boolean类型)且将当前线程的中断状态设为false。此方法调用之后会清除当前线程的中断标志位的状态(false),返回当前值
一般情况下推荐interrupt()与isInterrupted()组合使用。
中断标识用于在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑。
//实现方式有三种:
private static volatile boolean isStop = false;
private final static AtomicBoolean atomicBoolean = new AtomicBoolean(true);
Thread.currentThread().isInterrupted()//线程是否被中断? (推荐)
Thread.currentThread().interrupt()//设置该线程的中断标志位为true
public static void main(String[] args)
{
Thread t1 = new Thread(() -> {
while(true)
{
if(Thread.currentThread().isInterrupted())
{
System.out.println("-----t1 线程被中断了,break,程序结束");
break;
}
System.out.println("-----hello");
}
}, "t1");
t1.start();
System.out.println("**************"+t1.isInterrupted());
//暂停5毫秒
try { TimeUnit.MILLISECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
t1.interrupt();
System.out.println("**************"+t1.isInterrupted());
}
**************false
-----hello
-----hello
... ...
-----hello
-----hello
**************true
-----t1 线程被中断了,break,程序结束
6.3 LockSupport
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语.
6.3.1 3种让线程等待和唤醒的方法
- 使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
- 使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
- LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
Object和Condition使用的限制条件:
线程先要获得并持有锁,必须在锁块(synchronized或lock)中,
必须要先等待后唤醒,线程才能够被唤醒
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit), permit只有两个值1和零,默认是零。 可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。
6.3.2 使用
阻塞
park() /park(Object blocker) //阻塞当前线程/阻塞传入的具体线程
唤醒
unpark(Thread thread) //唤醒处于阻塞状态的指定线程
public static void main(String[] args)
{
//正常使用+不需要锁块!!!
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName()+" "+"1111111111111");
LockSupport.park();
System.out.println(Thread.currentThread().getName()+" "+"2222222222222------end被唤醒");
},"t1");
t1.start();
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
LockSupport.unpark(t1);//唤醒
System.out.println(Thread.currentThread().getName()+" -----LockSupport.unparrk() invoked over");
}
//先唤醒后等待,LockSupport照样支持
public static void main(String[] args)
{
Thread t1 = new Thread(() -> {
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis());
LockSupport.park();//后阻塞
System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis()+"---被叫醒");
},"t1");
t1.start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
LockSupport.unpark(t1);//先唤醒
System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis()+"---unpark over");
}