【JUC】并发编程初探
1、Java——天生的多线程
在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。
当前的main函数就是一个 JVM 进程。 打印出来的 6 条线程信息就是进程中的多条线程。
示例代码: ThreadPrint.java
这 6 条线程分别是干什么的?
public class ThreadPrint {
public static void main(String[] args) throws JsonProcessingException, InterruptedException {
// 获取Java线程管理MXBean
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
// 遍历线程信息,仅打印线程ID和线程名称信息
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.
getThreadName());
}
Thread.sleep(1000000);//使当前线程休眠,进入阻塞状态
}
}
1.1 main:主线程
1.2 Reference Handle
引用处理的线程
- 强,软,弱,虚四种引用,在GC过程中有不同表现
- JVM深入分析
1.3 Finalizer
JVM垃圾回收箱相关的内容
- 只有当开始一轮垃圾收集的时候,才会开始调用finalize方法(两轮标记:GC Roots引用链不可达,没有重写或已调用finalize方法没有必要执行finalize方法)
- daemon,prop=10 是一个高优先级的守护线程
- jvm在垃圾收集的时候,会将失去引用的对象封装到Fianlizer对象(Reference)中,放入到F-queue队列中,由Finalizer线程执行finalize方法
1.4 Signal Dispatcher
信号分发器
通过cmd 发送jstack,传到了jvm进程,这时候信号分发器就要发挥作用了
1.5 Attach Listenner
进程间的通信,附加监听器
简单来说,Attach Listenner是JDK中的一个工具类提供的jvm进程之间通信的工具。
进程中的通信:
- cmd中,java -version
- jvm中,jstack、jmap、dump
开启该线程的两个方式:
(1)通过JVM参数开启:-XX:StartAttachListenner
(2)延迟开启:cmd -- java -version ==》 jvm适时开启AL线程
1.6 Monitor Ctrl-Break
与JVM关系不大,IDEA通过反射的方式,开启一个随着我们运行的jvm进程开启与关闭的一个监听线程。
1.7 线程
1.7.1 查看线程的方法
1.7.2 上述代码线程的状态
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001a932800 nid=0x3bf8 runnable [0x000000001b96e000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:170)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x00000000d67070b8> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x00000000d67070b8> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000019a39000 nid=0x283c waiting on condition [0x0000000000000000] prio=5 延迟开启的问题。
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000019a38800 nid=0x1dd4 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
// Finalizer线程
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000017ae8800 nid=0x3708 in Object.wait() [0x0000000019e9f000] (Finalizer 专注垃圾收集,垃圾收集 -- 并行收集,不阻碍用户线程,低优先级线程。 prio=8 他是一个守护线程啊。而且这个线程目前并没有真正的开启,不足以发生minorgc或者是 full gc、)
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d6108e98> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x00000000d6108e98> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
// Reference Handler线程
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000017ae1800 nid=0x1ee0 in Object.wait() [0x000000001999f000] (引用处理线程-GC相关线程:GC 很重要啊,优先级还挺高)
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d6106b40> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000d6106b40> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
// main线程
"main" #1 prio=5 os_prio=0 tid=0x00000000028f4800 nid=0x25ac waiting on condition [0x00000000028ef000] (操作系统面向的是JVM 进程,JVM 进程里面向的是 我们的main函数,所以对于我们的操作系统如何看待我们的main函数优先级,无所谓。 只要os 给我们jvm进程足够公平的优先级就行。)
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.boot.jdk.ThreadPrint.main(ThreadPrint.java:20)
2、线程的优先级和守护线程
2.1 线程的优先级
在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10
在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配CPU时间片的数量要多于优先级低的线程。
设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。
示例代码: ThreadPrority.java
public class CodeTest {
public static void main(String[] args) throws JsonProcessingException {
System.out.println(Thread.currentThread().getName()
+"("+Thread.currentThread().getPriority()+ ")");
Thread t1=new MyThread("t1"); // 新建t1
Thread t2=new MyThread("t2"); // 新建t2
t1.setPriority(1); // 设置t1的优先级为1
t2.setPriority(10); // 设置t2的优先级为10
t1.start(); // 启动t1
t2.start(); // 启动t2
}
}
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@SneakyThrows
public void run(){
for (int i=0; i<5; i++) {
System.out.println(Thread.currentThread().getName()
+"("+Thread.currentThread().getPriority()+ ")"
+", loop "+i);
}
}
};
输出:
2.2 setPriority()
setPriority()方法,是JVM提供的一个方法,并且能够调用本地方法——setPriority0()。
我们发现优先级貌似没有起作用,为什么呢?
(1)现在的计算机都是多核的,t1,t2 会让哪个cpu处理不好说,虽然可能两个线程的优先级有高低,但是可能两个线程由不同的CPU同时提供资源执行
(2)优先级不代表先后顺序,优先级针对的是CPU时间片的长短问题。哪怕优先级低,也可能先拿到CPU时间片,只不过这个时间片比高优先级的线程的时间片短。
(3)目前工作中,实际项目中,不必要使用setPriority方法。我们现在都是用 hystrix, sential也好,一些开源的信号量控制工具,都能够实现线程资源的合理调度。这个 setPriority方法,很难控制。实际的运行环境太复杂。
2.2 守护线程
Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这 意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出(即使守护线程的代码还没有执行完成)。可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程。
示例代码: ThreadDaemon.java
public class ThreadDaemon {
public static void main(String[] args) {
Thread thread = new Thread(new DaemonThread(),"Daemon Thread!");
thread.setDaemon(true); // 设置为守护线程
thread.start();
// main 线程退出
}
static class DaemonThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally { //finally 不能够保证我们的守护线程的最终执行
System.out.println("FINISH!");
}
}
}
}
在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。
注意:必须先设置为守护线程再调用start()方法
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {
// 告诉我们,必须要先设置线程是否为守护线程,然后再调用start方法。如果你先调用start。 isAlive = true.
throw new IllegalThreadStateException();//非法线程状态异常
}
daemon = on;
}
3、线程的状态及状态之间的转化
3.1 线程上下文切换
线程上下文切换(Thread Context Switch)
当CPU不在执行当前的线程,转而执行另一个线程的代码时,要进行线程上下文的切换
有一下一些原因:
- 线程的CPU时间片用完了
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了sleep,yield,wait,join,park,synchronized,lock等方法
当Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条jvm指令的执行地址,是线程私有的
- 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
- Context Switch频繁切换会影响性能
3.1 线程状态及状态之间的转换
从Java API层面来描述,根据Thread.State
枚举,分为六种状态
示例源码: Thread.State.java
public enum State {
NEW,//线程还未开始
RUNNABLE,//调用start进入RUNABLE状态
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
-
NEW
线程刚被创建,但是还没有调用start()
方法,Thread.start 之后,他会进入一个就绪状态,还没有分配到 cpu的执行权。 当cpu的时间片切换到他的时候,他才会开始执行,进入running状态。 -
RUNABLE
当调用了start()
方法之后,注意,Java API层面RUNABLE
状态涵盖了操作系统层面的【可运行状态】READY
、【运行状态】RUNING
和【阻塞状态】(由于BIO导致的线程阻塞,在Java中无法区分,仍然认为是可运行的) -
BLOCKED
:只针对sync锁,如果有synchronized关键字(无论修饰在方法上还是在代码块中),如果竞争锁失败,都会进入BLOCKED状态 -
WAITING
:Objeck.wait()方法、Thread.join()方法、LockSupport.park()方法三个方法都会进入WAITING
状态 -
TIMED_WAITING
:Thread.sleep()、Object.wait(long)、Thread.join(long)、LockSupport.parkNanos()、LockSupport.parkUntil()方法BLOCKED
、WAITING
、TIMED_WAITING
都是Java API层面对【阻塞状态】的细分 -
TERMINATED
当前线程执行结束
3.2 实战——Stack log(堆日志)解读线程状态
示例代码 : ReadStackLog.Java
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.SneakyThrows;
public class ReadStackLog {
public static void main(String[] args) throws JsonProcessingException {
new Thread(new TimeWaiting (), "TimeWaitingThread").start();
new Thread(new Waiting(), "WaitingThread").start();
// 使用两个Blocked线程,一个获取锁成功,另一个被阻塞
new Thread(new Blocked(), "BlockedThread-1").start();
new Thread(new Blocked(), "BlockedThread-2").start();
}
}
// 该线程不断地进行睡眠
class TimeWaiting implements Runnable {
@SneakyThrows
@Override
public void run() {
while (true) {
Thread.sleep(1000000);
}
}
}
// 该线程在Waiting.class实例上等待
class Waiting implements Runnable {
@Override
public void run() {
while (true) {
synchronized (Waiting.class) {
try {
Waiting.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
// 该线程在Blocked.class实例上加锁后,不会释放该锁
class Blocked implements Runnable {
@SneakyThrows
public void run() {
synchronized (Blocked.class) {
while (true) {
Thread.sleep(1000000);
}
}
}
}
JPS + jstack 命令可进行查看上述代码线程的状态
"BlockedThread-2" #15 prio=5 os_prio=0 tid=0x000000001b956000 nid=0x22d8 waiting for monitor entry [0x000000001d0be000] (发现死锁,一直不会释放的话)
java.lang.Thread.State: BLOCKED (on object monitor)
at com.boot.jdk.Blocked.run(ReadStackLog.java:45)
- waiting to lock <0x00000000d67bca20> (a java.lang.Class for com.boot.jdk.Blocked)
at java.lang.Thread.run(Thread.java:745)
"BlockedThread-1" #14 prio=5 os_prio=0 tid=0x000000001b955000 nid=0x4c4c waiting on condition [0x000000001cfbf000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.boot.jdk.Blocked.run(ReadStackLog.java:45)
- locked <0x00000000d67bca20> (a java.lang.Class for com.boot.jdk.Blocked)
at java.lang.Thread.run(Thread.java:745)
"WaitingThread" #13 prio=5 os_prio=0 tid=0x000000001b954800 nid=0x39cc in Object.wait() [0x000000001cebf000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d67ba680> (a java.lang.Class for com.boot.jdk.Waiting)
at java.lang.Object.wait(Object.java:502)
at com.boot.jdk.Waiting.run(ReadStackLog.java:31)
- locked <0x00000000d67ba680> (a java.lang.Class for com.boot.jdk.Waiting)
at java.lang.Thread.run(Thread.java:745)
"TimeWaitingThread" #12 prio=5 os_prio=0 tid=0x000000001b951800 nid=0x3820 waiting on condition [0x000000001cdbe000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.boot.jdk.TimeWaiting.run(ReadStackLog.java:20)
at java.lang.Thread.run(Thread.java:745)
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001b6c9000 nid=0x1210 runnable [0x000000001c6be000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:170)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x00000000d67070b8> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x00000000d67070b8> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001a72b000 nid=0x4ea8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001a6d2800 nid=0x3d94 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001a6b1800 nid=0x4254 in Object.wait() [0x000000001ab8f000] (一直处于WAITING状态,只有进行垃圾收集的时候,才会被notify。 notify就会用到我们的 signal Dispatcher线程)
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d6108e98> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x00000000d6108e98> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x00000000187c1000 nid=0x48a8 in Object.wait() [0x000000001a68f000] (引用处理线程处于WAITING状态。加载新的类或引用时,才会再次开启该线程。)
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d6106b40> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000d6106b40> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
4、线程初始化
4.1 概述
一个新构造的线程对象是由其parent线程来进行空间分配的,而child线程继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时还会分配一个唯一的(sync)ID来标识这个child线程。(在下方源码的解读中可以很清晰地看清)。至此,一个能够运行的线程对象就初始化好了,在堆内存中等待着运行。
线程对象在初始化完成之后,调用start()方法就可以启动这个线程。线程start()方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程。
4.2 init()源码
/**
* Initializes a Thread. 初始化线程
*
* @param g 线程组
* @param target 调用run()方法的Runnable对象
* @param name 创建线程的名字
* @param stackSize 新线程所需的堆栈大小,或0表示该参数将被忽略。
* @param acc 继承AccessControlContext,如果为空,则使用AccessController.getContext()
* @param inheritThreadLocals 如果为true,从构造线程继承可继承线程局部变量的初始值
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();//防止group为null
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */直接得到security manager的ThreadGroup
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();//父类的ThreadGroup
}
}
//上述代码,尊重线程初始化传入的ThreadGroup;次选System security manager的ThreadGroup;再次选parent的ThreadGroup
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}//检查权限
}
g.addUnstarted();//NEW状态的线程,添加到ThreadGroup中
this.group = g;
this.daemon = parent.isDaemon();//是否为守护线程
this.priority = parent.getPriority();//优先级
//新的线程属性依赖于父类线程
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();//设置线程ID,该方法加锁了,以保证ThreadID的唯一性
}
4.3 start()源码
public synchronized void start() {//上锁,避免多线程同时启动一个线程
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)//不为NEW状态,抛出异常
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);//添加Thread至group中
boolean started = false;
try {
//start0 完全执行之前,线程处于READY状态
start0();
//完成后,只要CPU分配执行权,线程就进入RUNNGING状态
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
//start0 这个异常,会直接反馈给调用线程。即Main函数里面的thread.start方法,防止thread.start方法感知不到异常,导致程序的错误继续执行
}
}
}
private native void start0();
4.4 start()和run()的区别
方法名 | static | 功能说明 | 注意 |
---|---|---|---|
start() | 启动一个新线程,在新的线程运行run方法中的代码 | start方法只是让线程进入就绪,里面的代码不一定立刻执行(CPU时间片还没有分给他)。每个线程对象的start方法只能调用一次,如果调用多次会出现IllegalThreadStateException | |
run() | 新线程启动后会调用的方法 | 如果在构造Thread对象时传递了Runable参数,则线程启动后会调用Runable中的run方法,否则默认不执行任何操作。但可以创建Thread的子类对象,来覆盖默认行为 |
(1)调用run()方法
代码:
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug(Thread.currentThread().getName());
FileReader.read(Constants.MP4_FULL_PATH);
}
};
t1.run();
log.debug("do other things ...");
}
输出:
19:39:14 [main] c.TestStart - main
19:39:14 [main] c.FileReader - read [1.mp4] start ...
19:39:18 [main] c.FileReader - read [1.mp4] end ... cost: 4227 ms
19:39:18 [main] c.TestStart - do other things ...
分析:
程序仍在main线程执行,FileReader.read()
方法调用还是同步的
(2)调用start()方法
代码:将上述代码的t1.run()
改为t1.start()
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug(Thread.currentThread().getName());
FileReader.read(Constants.MP4_FULL_PATH);
}
};
t1.start();
log.debug("do other things ...");
}
输出:
19:41:30 [main] c.TestStart - do other things ...
19:41:30 [t1] c.TestStart - t1
19:41:30 [t1] c.FileReader - read [1.mp4] start ...
19:41:35 [t1] c.FileReader - read [1.mp4] end ... cost: 4542 ms
分析:
程序在t1线程执行,FileReader.read()
方法调用是异步的
(3)小结
- 直接调用run方法,是在主线程中执行了run方法,并没有启动新的线程
- 使用start方法,是启动新线程,通过新线程间接执行run方法中的代码
5、Thread.sleep()与Thread.yield()
方法名 | static | 功能说明 | 注意 |
---|---|---|---|
sleep(long n) | static | 让当前执行的线程休眠n毫秒,休眠时让出CPU的时间片给其他线程 | |
yield() | static | 提示线程调度器让出当前线程对CPU的使用 |
5.1 sleep
(1)调用sleep会让当前线程从Running
状态进入Timed Waiting
状态(阻塞)
(2)其他线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出InterruptedException
(3)睡眠结束后的线程未必会立刻得到执行
(4)建议用 TimeUnit 的 sleep 代替 Thread 的sleep 来获得更好的可读性
5.2 sleep()源码
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
* the precision and accuracy of system timers and schedulers. The thread
* does not lose ownership of any monitors.
翻译:根据系统计时器和调度器的精度和准确性,使当前正在执行的线程休眠(临时停止执行)指定的毫秒数。线程不会失去任何监视器的所有权。
*
* @param millis
* the length of time to sleep in milliseconds 休眠时长
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
翻译:如果有线程中断了当前线程。抛出此异常时,当前线程的中断状态将被清除。
*/
// native方法,本地方法,一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C
public static native void sleep(long millis) throws InterruptedException;
示例代码:
import com.fasterxml.jackson.core.JsonProcessingException;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
public class SleepRelaseCPU {
public static void main(String[] args) {
//创建100个线程,每个线程睡500s
for(int i = 0; i < 100; i++) {
Thread thread = new Thread(new SubThread(),"Daemon Thread!"+i);
thread.start();
}
}
static class SubThread implements Runnable {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName());
Thread.sleep(500000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("FINISH!");
}
}
}
}
jsp+jstack 查看线程的状态:线程处于TIMED_WAITING状态,不占用任何CPU
由上源码可知:
- 是否释放锁?—— 不释放锁
- 是否对中断敏感?—— 对中断敏感
- 是否释放CPU?—— 释放CPU
5.2 yield
(1)调用 yield 会让当前线程从Running
进入Runable
就绪状态,然后调度执行其他线程
(2)具体的实现依赖于操作系统的任务调度器
6、Object.wait()
只有锁对象才能调用 wait()、notify(),wait()、notify()方法之能在同步泰马快中使用
【重点】wait() 方法辨析
(1)为什么不是线程调用 wait() 方法,而是锁对象调用 wait()
由于每个对象都拥有 monitor对象(即锁),所以让当前线程等待获取某个对象的锁,就应该是通过这个锁对象来操作,而不是线程对象操作,当然线程对象也可以调用 wait() 方法,是因为其也可以作为锁对象
(2)锁对象调用 wait() 方法的含义
调用某个对象的 wait() 方法,相当于让当前线程释放此锁对象的 monitor,进入等待状态,等待后续再次获得此对象的锁。
(3)wait() 方法的调用要在同步代码块中
某个锁对象调用 wait方法,是指持有该锁的当前线程要等待,在 notify 之后竞争获取CPU时间片,所以当前线程必须拥有该对象的 monitor对象,因此 wait() 方法必须在同步块或者同步方法中进行,如果没有当前线程没有持有该锁对象就调用 wait() 方法,抛出 IllegaMontiorStateException。
6.1 wait()源码
/**
* The current thread must own this object's monitor. The thread
* releases ownership of this monitor and waits until another thread
* notifies threads waiting on this object's monitor to wake up
* either through a call to the {@code notify} method or the
* {@code notifyAll} method. The thread then waits until it can
* re-obtain ownership of the monitor and resumes execution.
翻译:当前线程必须拥有此对象的监视器。线程释放这个监视器的所有权并等待,直到另一个线程通过调用{@code notify}方法或{@code notifyAll}方法通知正在等待这个对象的监视器的线程苏醒。然后,线程等待,直到它能够重新获得监视器的所有权并继续执行。
* @throws InterruptedException if any thread interrupted the
* current thread before or while the current thread
* was waiting for a notification. The <i>interrupted
* status</i> of the current thread is cleared when
* this exception is thrown.
翻译:如果有线程中断了当前线程。抛出此异常时,当前线程的中断状态将被清除。
*/
public final void wait() throws InterruptedException {
wait(0);
}
public final native void wait(long timeout) throws InterruptedException;
可知:
- 是否释放锁? —— 释放
- 是否对中断敏感? —— 对中断敏感
- 是否释放CPU? —— 释放CPU
6.2 wait()方法的使用
让当前线程等待。wait方法会使当前线程释放锁,等到其他线程调用该锁的notify方法时再继续运行。
object.wati(); object 是以锁对象的含义而非以线程的名义调用 wait() 方法。
示例代码:
import java.util.Date;
public class WaitTest {
public static void main(String[] args) {
ThreadA t1=new ThreadA("t1");
System.out.println("t1:"+t1);
synchronized (t1) {
try {
//启动线程
System.out.println(Thread.currentThread().getName()+" start t1");
t1.start();
//主线程等待t1通过notify唤醒。
System.out.println(Thread.currentThread().getName()+" wait()"+ new Date());
t1.wait();// 不是使t1线程等待,而是当前执行该语句的线程等待
// t1在此处不是指线程,只是指锁对象
System.out.println(Thread.currentThread().getName()+" continue"+ new Date());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class ThreadA extends Thread{
public ThreadA(String name) {
super(name);
}
@Override
public void run() {
synchronized (this) {
System.out.println("this:"+this);
try {
Thread.sleep(2000);//使当前线程阻塞1秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" call notify()");
this.notify();
}
}
}
输出:
结论:
代码中t1.wait()
是让运行这行代码的线程等待,而不是让t1这个线程等待。哪条线程等待取决于 wait() 方法在哪个线程栈中。
7、Thread.join()
方法名 | static | 功能说明 | 注意 |
---|---|---|---|
join() | 等待线程运行结束 | ||
join(long n) | 等待线程运行结束,最多等待n毫秒 |
【重点】join() 方法辨析
(1)怎么理解join方法?
Thread.join 方法,他底层代码调用的是 Object的 wait方法。那么想要唤醒join方法,就需要使用 object的notify以及 notifyall,且需在持有 thread锁 的同步代码块中
t1.join() 底层是 t1.wait(),且 wait方法 在 t1 为锁对象的同步代码块中,即当前线程在执行 join() 方法的过程中,会持有 t1锁 运行至 wait() 方法之后,释放 t1锁,进入 t1锁 的等待池,直到再次持有 t1锁。
而在java中,Thread类线程执行完run()方法后,一定会自动执行notifyAll()方法。因为线程在die的时候会释放持用的资源和锁,自动调用自身的notifyAll方法。所以在 t1 线程运行完毕后,会 notifyAll 所有持有 t1线程对象为锁 的线程。
从整体上来看,就是 主线程(或是调用t1.join()方法的线程)须得在 t1线程 运行完毕后,才继续往下执行。
(2)怎么理解当前线程?
t1.start() 只有实现的 run() 方法在 t1线程栈 中运行,t1 以线程对象的身份调用 join(),以锁对象的身份调用 wait(),都是在 main线程栈 中运行。所以是当前线程等待
7.1 join()源码
join(): void
/**
* Waits for this thread to die. 等待该线程死亡
*
* <p> An invocation of this method behaves in exactly the same
* way as the invocation
*
* <blockquote>
* {@linkplain #join(long) join}{@code (0)}
* </blockquote>
*
* @throws InterruptedException 对中断敏感,中断抛出异常
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final void join() throws InterruptedException {
join(0);
}
join(long): void
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*/
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();// 时间戳
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {//线程是存活的
wait(0);
//这个wait是调用Object的wait方法,隐含的意义是:this.wait()——>更为准确的说法是,当前线程类调用了wait方法(Thread类有一个当前的线程,而Thread类是Object的一个子类,this是一个object类的引用指向了Thread实例对象),即当前线程释放了CPU,而且是当前线程(Thread类)的对象是锁对象被释放了。
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
由此,
- 是否释放锁?—— 具体要看当前的锁对象是谁。如果是调用join方法的锁对象,则释放(下面具体探究)
- 是否对中断敏感? —— 对中断敏感
- 是否释放了CPU? —— 释放了CPU
7.2 对join方法是否释放了锁的探究
join 方法是否释放了锁,取决于join方法的底层实现中,wait() 方法等待的是 以调用join() 方法的线程对象 为锁对象的锁,wait() 方法之后,释放了线程对象的 moinitor,如果当前线程仅持有 当前线程对象的monitor,则 join 之后也会释放锁
package com.boot.jdk;
import lombok.SneakyThrows;
public class JoinRelase {
static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
// 创建两个线程
for(int i = 0; i < 2; i++) {
Thread thread = new Thread(new SubThread(),"Daemon Thread!"+i);
thread.setName("thread-" + i);
thread.start();
Thread.sleep(100);
}
}
//synchronize(object),这种情况下,线程2被阻塞
//即如果当前锁对象不是调用join方法的锁对象,则不释放
//这里在底层调用当前线程thread对象的wait方法,当前线程thread释放了锁,所持有的锁对象是当前线程对象,也被释放了,而object锁对象并没有并没有释放
//object锁并没有释放
static class SubThread implements Runnable {
@SneakyThrows
@Override
public void run() {
synchronized (object)) {
System.out.println("获取到锁!!!ThreadName: " + Thread.currentThread().getName());
Thread.currentThread().join();//当前线程join
}
}
}
//synchronize(Thread.currentThread()),这种情况下,线程2未被阻塞
//如果当前对象是调用join方法的锁对象,则释放
//底层是调用当前线程thread的wait方法,thread对象释放了锁
//thread锁得到了释放
static class SubThread implements Runnable {
@SneakyThrows
@Override
public void run() {
synchronized (Thread.currentThread()) {
System.out.println("获取到锁!!!ThreadName: " + Thread.currentThread().getName());
Thread.currentThread().join();////当前线程join
}
}
}
}
8、线程中断与通讯方式
8.1 线程中断
方法名 | static | 功能说明 | 注意 |
---|---|---|---|
interrupt() | 打断线程 | 如果被打断的线程正在sleep(),wait(),join()会导致被打断的线程抛出 InterruptedException,并清除打断标记;如果打断的正在运行的线程,则会设置打断标记;park 的线程被打断,也会设置打断标记 | |
interrupted() | static | 判断当前线程是否被打断 | 会清除打断标记 |
中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。中断好比其他线程对该线程打了个招呼,其他线程通过调用该线程的interrupt()方法对其进行中断操作。
线程通过检查自身是否被中断来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。如果该线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted()时依旧会返回false。
从Java的API中可以看到,许多声明抛出InterruptedException的方法(例如Thread.sleep(long millis)方法)这些方法在抛出InterruptedException之前,Java虚拟机会先将该线程的中断标识位清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。
代码:
package com.boot.jdk;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.SneakyThrows;
import java.util.concurrent.TimeUnit;
public class ThreadInterrupted {
public static void main(String[] args) throws InterruptedException {
// sleepThread不停的尝试睡眠
Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");
sleepThread.setDaemon(true);
Thread busyThread = new Thread(new BusyRunner(), "BusyThread");
busyThread.setDaemon(true);
sleepThread.start();
busyThread.start();
// 休眠5秒,让sleepThread和busyThread充分运行
TimeUnit.SECONDS.sleep(5);
sleepThread.interrupt();
busyThread.interrupt();
//sleep方法响应中断,肯定会中断sleep。在抛出异常之前,会清理掉我们的 中断标志。 会返回false,因为当前线程已经停止了。
System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
// busy thread ,没有立即响应中断,只是他的中断标志位 显示 被中断,这个是isInterrupted会返回true。
System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
// 防止sleepThread和busyThread立刻退出
TimeUnit.SECONDS.sleep(5);
}
static class SleepRunner implements Runnable {
@SneakyThrows
@Override
public void run() {
while (true) {
try {
// 先清除标志,后抛异常、(sleep)
TimeUnit.SECONDS.sleep(100);
} catch (Exception e) {
System.out.println("======");
}
}
}
}
static class BusyRunner implements Runnable {
@Override
public void run() {
while (true) {
}
}
}
}
输出:
sleep先清除标志,后抛出异常
SleepThread interrupted is false
======
BusyThread interrupted is true
8.2 线程间的通讯方式
(1) volitate 、synchronize、lock。(都保证可见性)
(2)wait、notify、await() 、 signal
(3)管道输入、输出流(过时了)
- 管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。
- 管道输入/输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。
(4)Thread.join() : 隐式唤醒。等待其他线程执行完成,其他线程会发送唤醒信号。
(5)ThradLocal() ---》支持子线程集成的一种形式。埋点。
(6)线程中断