# 进程和线程
==进程==:是程序的一次执行,是系统进行资源分配和调度的独立单位,每一个进程都有它自己的内存空间和系统资源
==线程==:在同一个进程内又可以执行多个任务,而这每一个任务我们就可以看做是一个线程
# Monitor
==管程 (英语:Monitors,也称为监视器) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。== 这些共享资源一般是硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中。(把信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。
Monitor其实是一种同步机制,他的义务是保证只有一个线程可以访问被保护的数据和代码。JVM中的同步是基于进入和退出监视器对象来实现的,每一个对象实例都会有一个Monitor对象,Monitor对象会和Java对象一同创建并销毁,底层由C++实现
Java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor,更常见的是直接将它称为“锁”)来实现的。 方法级的同步是隐式的,无须通过字节码指令来控制,它实现在方法调用和返回操作之中。虚拟机可以从方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否被声明为同步方法。当方法调用时,调用指令将会检查方法ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程就要求先成功持有管程,然后才能执行方法,最后当方法完成((无论是正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的管程将在异常抛到同步方法边界之外时自动释放。 同步一段指令集序列通常是由Java语言中的synchronized语句块来表示的,Java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义,正确实现synchronized关键字需要Javac编译器与Java虚拟机两者共同协作支持,
在HotSpot虚拟机中,monitor采用ObjectMonitor实现。ObjectMonitor.java→ObjectMonitor.cpp→objectMonitor.hpp
ObjectMonitor中有几个关键属性: _owner:指向持有ObjectMonitor对象的线程 _WaitSet:存放处于wait状态的线程队列 _EntryList:存放处于等待锁block状态的线程队列 _recursions:锁的重入次数 _count:用来记录该线程获取锁的次数
# 用户线程和守护线程
Java线程分为用户线程和守护线程,线程的daemon属性为true表示是守护线程,false表示是用户线程
守护线程:是一种特殊的线程,在后台默默的完成一些系统性的任务,比如垃圾回收线程 用户线程:是系统的工作线程,它会完成这个程序需要完成的业务操作
public class DaemonDemo
{
public static void main(String[] args)
{
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t 开始运行,"+(Thread.currentThread().isDaemon() ? "守护线程":"用户线程"));
while (true) {
}
}, "t1");
//线程的daemon属性为true表示是守护线程,false表示是用户线程
t1.setDaemon(true);
t1.start();
//3秒钟后主线程再运行
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("----------main线程运行完毕");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
当程序中的所有用户线程执行完毕之后,不管守护线程是否结束,系统都会自动退出--->如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出了。所以当系统只剩下守护进程的时候,java虚拟机会自动退出
设置守护线程需要在start()方法之前进行
# 乐观锁和悲观锁
悲观锁:认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改
Synchronized关键字和Lock的实现类都是悲观锁
适合写操作多的场景,先加锁可以保证写操作时数据正确,显示的锁定之后再操作同步资源
//=============悲观锁的调用方式
public synchronized void m1()
{
//加锁后的业务逻辑......
}
// 保证多个线程使用的是同一个lock对象的前提下
ReentrantLock lock = new ReentrantLock();
public void m2() {
lock.lock();
try {
// 操作同步资源
}finally {
lock.unlock();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
乐观锁:乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。
如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作
乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋+volatile实现的。
适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
乐观锁则直接去操作同步资源,是一种无锁算法,得之我幸不得我命,再抢
乐观锁一般有两种实现方式:采用版本号机制;CAS(Compare-and-Swap,即比较并替换)算法实现
# 公平锁和非公平锁
⽣活中,排队讲求先来后到视为公平。程序中的公平性也是符合请求锁的绝对时间的,其实就是 FIFO,否则视为不公平
按序排队公平锁,就是判断同步队列是否还有先驱节点的存在(我前面还有人吗?),如果没有先驱节点才能获取锁; 先占先得非公平锁,是不管这个事的,只要能抢获到同步状态就可以
![image-20230711185734563](/assets/img/image-20230711185734563.a4afd095.png)
为什么会有公平锁和非公平锁的设计(默认为非公平锁)?
1.恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间。
2.使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。
公平锁保证了排队的公平性,非公平锁霸气的忽视这个规则,所以就有可能导致排队的长时间在排队,也没有机会获取到锁,这就是传说中的 “锁饥饿”
如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了; 否则那就用公平锁,大家公平使用。
# 可重入锁(递归锁)
可:可以。重:再次。入:进入。锁:同步锁。一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。自己可以获取自己的内部锁
可重入锁种类:
隐式锁(即synchronized关键字使用的锁)默认是可重入锁 。指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。 简单的来说就是:在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的与可重入锁相反,不可重入锁不可递归调用,递归调用就发生死锁。
//同步块
public class ReEntryLockDemo
{
public static void main(String[] args)
{
final Object objectLockA = new Object();
new Thread(() -> {
synchronized (objectLockA)
{
System.out.println("-----外层调用");
synchronized (objectLockA)
{
System.out.println("-----中层调用");
synchronized (objectLockA)
{
System.out.println("-----内层调用");
}
}
}
},"a").start();
}
}
//同步方法
public class ReEntryLockDemo
{
public synchronized void m1()
{
System.out.println("-----m1");
m2();
}
public synchronized void m2()
{
System.out.println("-----m2");
m3();
}
public synchronized void m3()
{
System.out.println("-----m3");
}
public static void main(String[] args)
{
ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
reEntryLockDemo.m1();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
显式锁(即Lock)也有ReentrantLock这样的可重入锁。
public class ReEntryLockDemo
{
static Lock lock = new ReentrantLock();
public static void main(String[] args)
{
new Thread(() -> {
lock.lock();
try
{
System.out.println("----外层调用lock");
lock.lock();
try
{
System.out.println("----内层调用lock");
}finally {
// 这里故意注释,实现加锁次数和释放次数不一样
// 由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。
lock.unlock(); // 正常情况,加锁几次就要解锁几次
}
}finally {
lock.unlock();
}
},"a").start();
new Thread(() -> {
lock.lock();
try
{
System.out.println("b thread----外层调用lock");
}finally {
lock.unlock();
}
},"b").start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Synchronized的重入的实现机理:每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。
# Synchronized
Synchronized有三种应用方式
- 作用于实例方法,当前示实例加锁进入同步代码前要获得当前实例的锁,即synchronized普通同步方法,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置。 如果设置了,执行线程会将先持有monitor然后再执行方法, 最后在方法完成(无论是正常完成还是非正常完成)时释放 monitor
- 作用于代码块,对括号里面配置的对象加锁,即synchronized同步代码块,实现使用的是monitorenter和monitorexit指令
- 作用于静态方法,当前类加锁,进去同步代码前要获得当前对象的锁,即synchronized静态同步方法,ACC_STATIC, ACC_SYNCHRONIZED访问标志区分该方法是否静态同步方法
synchronized同步代码块里面一定是一个enter两个exit吗?
不一定,方法里面添加异常的话会是1-1
![image-20230711183855349](/assets/img/image-20230711183855349.c38efa86.png)
java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在户态与核心态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。
在Java早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,挂起线程和恢复线程都需要转入内核态去完成,阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态切换需要耗费处理器时间,如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行的时间还长”,时间成本相对较高,这也是为什么早期的synchronized效率低的原因 Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁
Monitor与java对象以及线程是如何关联 ? 1.如果一个java对象被某个线程锁住,则该java对象的Mark Word字段中LockWord指向monitor的起始地址 2.Monitor的Owner字段会存放拥有相关联对象锁的线程id
synchronized用的锁是存在Java对象头里的Mark Word中 锁升级功能主要依赖MarkWord中锁标志位和释放偏向锁标志位
# 死锁
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
产生死锁主要原因:系统资源不足;进程运行推进的顺序不合适;资源分配不当
如何排查死锁:jps -l jstack 进程编号 或者jconsole
//在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时,是永远可以得到锁的
public class DeathLock {
public static void main(String[] args) {
Object objectA = new Object();
Object objectB = new Object();
new Thread(()->{
synchronized (objectA) {
System.out.println(Thread.currentThread().getName() + "\t"+"自己持有A锁希望获得B锁");
try{
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
System.out.println(e);
}
synchronized (objectB){
System.out.println("A获得B锁成功");
}
}
},"A").start();
new Thread(()->{
synchronized (objectB){
System.out.println(Thread.currentThread().getName()+"\t"+"自己持有B锁希望获得A锁");
synchronized (objectA){
System.out.println("成功获得A锁");
}
}
},"B").start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
PS D:\IDEAProject\JUC> jps -l
11316
11524 sun.tools.jps.Jps
14308 org.jetbrains.idea.maven.server.RemoteMavenServer36
14468 org.jetbrains.jps.cmdline.Launcher
18092 com.example.juc.MultithreadedLock.DeathLock
PS D:\IDEAProject\JUC> jstack 18092
2023-07-11 17:13:37
Full thread dump OpenJDK 64-Bit Server VM (13+33 mixed mode, sharing):
Threads class SMR info:
_java_thread_list=0x0000022b64575730, length=13, elements={
0x0000022b63a7f000, 0x0000022b63a83800, 0x0000022b63aa2000, 0x0000022b63aa3000,
0x0000022b63aa5000, 0x0000022b63aa6000, 0x0000022b63aba000, 0x0000022b64340800,
0x0000022b645e7800, 0x0000022b645e8000, 0x0000022b645d5000, 0x0000022b645d8000,
0x0000022b40930000
}
"Reference Handler" #2 daemon prio=10 os_prio=2 cpu=0.00ms elapsed=152.63s tid=0x0000022b63a7f000 nid=0x4670 waiting on condition [0x000000b9c2afe000]
java.lang.Thread.State: RUNNABLE
at java.lang.ref.Reference.waitForReferencePendingList(java.base@13/Native Method)
at java.lang.ref.Reference.processPendingReferences(java.base@13/Reference.java:241)
at java.lang.ref.Reference$ReferenceHandler.run(java.base@13/Reference.java:213)
"Finalizer" #3 daemon prio=8 os_prio=1 cpu=0.00ms elapsed=152.63s tid=0x0000022b63a83800 nid=0x1bd4 in Object.wait() [0x000000b9c2bfe000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(java.base@13/Native Method)
- waiting on <0x000000071950aed8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(java.base@13/ReferenceQueue.java:155)
- locked <0x000000071950aed8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(java.base@13/ReferenceQueue.java:176)
at java.lang.ref.Finalizer$FinalizerThread.run(java.base@13/Finalizer.java:170)
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 cpu=0.00ms elapsed=152.62s tid=0x0000022b63aa2000 nid=0x3790 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Attach Listener" #5 daemon prio=5 os_prio=2 cpu=31.25ms elapsed=152.62s tid=0x0000022b63aa3000 nid=0x2b30 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 cpu=62.50ms elapsed=152.62s tid=0x0000022b63aa5000 nid=0x2584 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
No compile task
"C1 CompilerThread0" #8 daemon prio=9 os_prio=2 cpu=93.75ms elapsed=152.62s tid=0x0000022b63aa6000 nid=0xc5c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
No compile task
"Sweeper thread" #9 daemon prio=9 os_prio=2 cpu=0.00ms elapsed=152.62s tid=0x0000022b63aba000 nid=0x1348 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Common-Cleaner" #10 daemon prio=8 os_prio=1 cpu=0.00ms elapsed=152.58s tid=0x0000022b64340800 nid=0x3a24 in Object.wait() [0x000000b9c31ff000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(java.base@13/Native Method)
- waiting on <0x00000007195b9d18> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(java.base@13/ReferenceQueue.java:155)
- locked <0x00000007195b9d18> (a java.lang.ref.ReferenceQueue$Lock)
at jdk.internal.ref.CleanerImpl.run(java.base@13/CleanerImpl.java:148)
at java.lang.Thread.run(java.base@13/Thread.java:830)
at jdk.internal.misc.InnocuousThread.run(java.base@13/InnocuousThread.java:134)
"Monitor Ctrl-Break" #11 daemon prio=5 os_prio=0 cpu=15.63ms elapsed=152.51s tid=0x0000022b645e7800 nid=0xc98 runnable [0x000000b9c32fe000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.SocketDispatcher.read0(java.base@13/Native Method)
at sun.nio.ch.SocketDispatcher.read(java.base@13/SocketDispatcher.java:46)
at sun.nio.ch.NioSocketImpl.tryRead(java.base@13/NioSocketImpl.java:262)
at sun.nio.ch.NioSocketImpl.implRead(java.base@13/NioSocketImpl.java:313)
at sun.nio.ch.NioSocketImpl.read(java.base@13/NioSocketImpl.java:351)
at sun.nio.ch.NioSocketImpl$1.read(java.base@13/NioSocketImpl.java:802)
at java.net.Socket$SocketInputStream.read(java.base@13/Socket.java:919)
at sun.nio.cs.StreamDecoder.readBytes(java.base@13/StreamDecoder.java:297)
at sun.nio.cs.StreamDecoder.implRead(java.base@13/StreamDecoder.java:339)
at sun.nio.cs.StreamDecoder.read(java.base@13/StreamDecoder.java:188)
- locked <0x000000071921bc50> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(java.base@13/InputStreamReader.java:185)
at java.io.BufferedReader.fill(java.base@13/BufferedReader.java:161)
at java.io.BufferedReader.readLine(java.base@13/BufferedReader.java:326)
- locked <0x000000071921bc50> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(java.base@13/BufferedReader.java:392)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:55)
"Service Thread" #12 daemon prio=9 os_prio=0 cpu=0.00ms elapsed=152.51s tid=0x0000022b645e8000 nid=0x1558 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"A" #13 prio=5 os_prio=0 cpu=0.00ms elapsed=152.51s tid=0x0000022b645d5000 nid=0x1ac0 waiting for monitor entry [0x000000b9c35fe000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.juc.MultithreadedLock.DeathLock.lambda$main$0(DeathLock.java:19)
- waiting to lock <0x00000007193b77a0> (a java.lang.Object)
- locked <0x00000007193b7790> (a java.lang.Object)
at com.example.juc.MultithreadedLock.DeathLock$$Lambda$14/0x0000000800ba4840.run(Unknown Source)
at java.lang.Thread.run(java.base@13/Thread.java:830)
"B" #14 prio=5 os_prio=0 cpu=0.00ms elapsed=152.51s tid=0x0000022b645d8000 nid=0x2384 waiting for monitor entry [0x000000b9c36fe000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.juc.MultithreadedLock.DeathLock.lambda$main$1(DeathLock.java:28)
- waiting to lock <0x00000007193b7790> (a java.lang.Object)
- locked <0x00000007193b77a0> (a java.lang.Object)
at com.example.juc.MultithreadedLock.DeathLock$$Lambda$15/0x0000000800ba4c40.run(Unknown Source)
at java.lang.Thread.run(java.base@13/Thread.java:830)
"DestroyJavaVM" #15 prio=5 os_prio=0 cpu=125.00ms elapsed=152.51s tid=0x0000022b40930000 nid=0x2f7c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"VM Thread" os_prio=2 cpu=0.00ms elapsed=152.63s tid=0x0000022b63a7e800 nid=0x30f4 runnable
"GC Thread#0" os_prio=2 cpu=15.63ms elapsed=152.64s tid=0x0000022b409a0000 nid=0x34b8 runnable
"G1 Main Marker" os_prio=2 cpu=15.63ms elapsed=152.64s tid=0x0000022b409c1800 nid=0x3728 runnable
"G1 Conc#0" os_prio=2 cpu=0.00ms elapsed=152.64s tid=0x0000022b409c3000 nid=0xee8 runnable
"G1 Refine#0" os_prio=2 cpu=0.00ms elapsed=152.64s tid=0x0000022b63979800 nid=0x3d2c runnable
"G1 Young RemSet Sampling" os_prio=2 cpu=0.00ms elapsed=152.64s tid=0x0000022b6397b800 nid=0x1cb4 runnable
"VM Periodic Task Thread" os_prio=2 cpu=0.00ms elapsed=152.51s tid=0x0000022b645ea000 nid=0x11a4 waiting on condition
JNI global refs: 15, weak refs: 0
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# 线程中断
中断只是一种协同机制,修改中断标识位仅此而已,不是立刻stop打断
一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。 所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。
在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。 因此,Java提供了一种用于停止线程的机制——中断。
中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。 若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true; 接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断, 此时究竟该做什么需要你自己写代码实现。
每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断; 通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
==public void interrupt()== 实例方法, 实例方法interrupt()仅仅是设置线程的中断状态为true,不会停止线程
==public static boolean interrupted()== 静态方法,Thread.interrupted(); 判断线程是否被中断,并清除当前中断状态 这个方法做了两件事: 1 返回当前线程的中断状态 2 将当前线程的中断状态设为false这个方法有点不好理解,因为连续调用两次的结果可能不一样。
==public boolean isInterrupted()== 实例方法, 判断当前线程是否被中断(通过检查中断标志位)
# 如何使用中断标识停止线程?
- 通过volatile变量实现
- 通过AtomicBoolean
- 通过Thread类自带的中断api方法实现
public class Interupt {
public static volatile Boolean flag1=true;
public static AtomicBoolean flag2=new AtomicBoolean();
public static void main(String[] args) {
Thread t5= new Thread(()->{
while (true){
if(!Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName());
}else{
System.out.println(Thread.currentThread().getName()+"被中断了");
break;
}
}
});
t5.start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t5.interrupt();
System.out.println("t5状态"+t5.isInterrupted());
},"t6").start();
}
public void m2() {
flag2.set(true);
new Thread(()->{
while(true){
if(flag2.get()){
System.out.println(Thread.currentThread().getName());
}else {
break;
}
}
},"t3").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName());
flag2.set(false);
},"t4").start();
}
public void m1() {
new Thread(()->{
while(true){
if(flag1){
System.out.println(Thread.currentThread().getName());
break;
}
}
},"t1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
System.out.println(e);
}
System.out.println(Thread.currentThread().getName());
flag1=false;
},"t2").start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
==当对一个线程,调用 interrupt() 时:== ① 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。 被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。 ② 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法, 那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
# 线程等待唤醒机制
三种让线程等待和唤醒的方法:
1.使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
wait和notify方法必须要在同步块或者方法里面,且成对出现使用,先wait后notify才OK
public static void m1(String[] args) {
Object object=new Object();
new Thread(()->{
synchronized (object){
System.out.println("线程启动,进入等待");
try {
object.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("线程恢复");
}
},"t1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (object) {
object.notify();
System.out.println("成功通知");
}
},"t2").start();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2.使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
Condtion中的线程等待和唤醒方法之前,需要先获取锁,一定要先await后signal,不要反了
public static void main(String[] args) {
Lock lock=new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
lock.lock();
try{
System.out.println("线程启动");
System.out.println("线程等待");
condition.await();
System.out.println("线程恢复");
}catch (Exception e){
System.out.println(e);
}finally {
lock.unlock();
}
},"t3").start();
new Thread(()->{
lock.lock();
try{
TimeUnit.SECONDS.sleep(2);
condition.signal();
System.out.println("完成通知");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
},"t4").start();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
3.LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作 LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit), permit只有两个值1和零,默认是零。 可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。n多个unpark(), 一个park就消耗完了
public static void m3(String[] args) {
Thread t5 = new Thread(() -> {
System.out.println("线程启动,进入等待");
LockSupport.park();
System.out.println("线程恢复");
},"t5");
t5.start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
LockSupport.unpark(t5);
System.out.println("完成通知");
},"t6").start();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
==1,2和3方法的对比== ==Object和Condition使用的限制条件==:线程先要获得并持有锁,必须在锁块(synchronized或lock)中;必须要先等待后唤醒,线程才能够被唤醒 ==LockSupport类中的park等待和unpark唤醒==:正常+无锁块要求;之前错误的先唤醒后等待,LockSupport照样支持