package com.dongguo.thread;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo {
public static void main(String[] args) {
// 构造方法的参数是给线程指定名字,推荐
Thread t1 = new Thread("t1") {
@Override
// run 方法内实现了要执行的任务
public void run() {
System.out.println(Thread.currentThread().getName()+" run");
}
};
t1.start();
}
}
运行结果
t1 run
方法二,使用 Runnable 配合 Thread
把【线程】和【任务】(要执行的代码)分开 Thread 代表线程 Runnable 可运行的任务(线程要执行的代码)
package com.dongguo.thread;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo1 {
public static void main(String[] args) {
// 创建任务对象
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" run");
}
};
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t1 = new Thread(task, "t1");
t1.start();
//Lambda表达式
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " run");
}, "t2");
t2.start();
}
}
运行结果
t1 run
t2 run
方法三,FutureTask 配合 Thread
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况
package com.dongguo.thread;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建任务对象
FutureTask task = new FutureTask(() -> {
System.out.println(Thread.currentThread().getName() + " run");
return 100;
});
// 参数1 是任务对象; 参数2 是线程名字,推荐
new Thread(task, "t1").start();
// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task.get();
System.out.println("结果是:"+ result);
}
}
运行结果:
t1 run
结果是:100
方法四,线程池
package com.dongguo.thread;
import java.util.concurrent.*;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建自定义线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
pool.execute(()->{
System.out.println(Thread.currentThread().getName() + " run");
});
} catch (Exception e) {
e.printStackTrace();
} finally {
pool.shutdown();
}
}
}
运行结果
pool-1-thread-1 run
观察多个线程同时运行
package com.dongguo.thread;
/**
* @author Dongguo
* @date 2021/9/10 0010-22:58
* @description: 演示多个线程并发交替执行
*/
public class MultiThreadDemo {
public static void main(String[] args) {
new Thread(()->{
while (true){
System.out.println(Thread.currentThread().getName() + " run");
}
},"t1").start();
new Thread(()->{
while (true){
System.out.println(Thread.currentThread().getName() + " run");
}
},"t2").start();
}
}
运行结果
可以看到两个线程交替执行
交替执行 谁先谁后,不由我们控制 ,是由操作系统的任务调度器决定
查看进程线程的方法 windows 1.任务管理器可以查看进程和线程数,也可以用来杀死进程
2.cmd命令行
tasklist 查看进程
taskkill 杀死进程
/F 强制
ps -fe 查看所有进程
ps -fT -p 查看某个进程(PID)的所有线程 kill 杀死进程
top 动态的显示CPU使用的情况 ,按大写 H 切换是否显示线程 top -H -p 查看某个进程(PID)的所有线程
Javajps 命令查看所有 Java 进程 jstack 查看某个 Java 进程(PID)的所有线程状态
jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)
jconsole 本地监控配置
直接启动
jconsole 远程监控配置(监控linux环境) 需要以如下方式运行你的 java 类
java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -
Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=是否安全连接 -Dcom.sun.management.jmxremote.authenticate=是否认证 java类
修改 /etc/hosts 文件将 127.0.0.1 映射至主机名
如果要认证访问,还需要做如下步骤 复制 jmxremote.password 文件 修改 jmxremote.password 和 jmxremote.access 文件的权限为 600 即文件所有者可读写 连接时填入 controlRole(用户名),R&D(密码)
这里就不设置认证和安全连接。 不同输入用户名和口令就可以直接连接
注意关闭linux防火墙
Java Virtual Machine Stacks (Java 虚拟机栈) 我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟 机就会为其分配一块栈内存。 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
查看一个线程执行的情况
package com.dongguo.thread;
/**
* @author Dongguo
* @date 2021/9/11 0011-7:51
* @description:
*/
public class FramesDemo {
public static void main(String[] args) {
m1(1);
}
private static void m1(int x) {
int y = x + 1;
Object object = m2();
System.out.println(object);
}
private static Object m2() {
Object o = new Object();
return o;
}
}
在m1(1)出打上断点以debug方式运行
Frames就是栈帧 此时main线程就是一个栈帧入栈
点击下一步
m1栈帧入栈
运行进入m2,m2栈帧入栈
当m2执行完后,跳回m1,m2出栈
m1执行完,跳回main,m1出栈
代码执行完 main出栈
方法的调用就是一个个栈帧入栈到出栈的过程
查看多个线程执行的情况(t1、main)
package com.dongguo.thread;
/**
* @author Dongguo
* @date 2021/9/11 0011-7:51
* @description:
*/
public class FramesDemo {
public static void main(String[] args) {
new Thread(()->{
m1(2);
},"t1").start();
m1(1);
}
private static void m1(int x) {
int y = x + 1;
Object object = m2();
System.out.println(object);
}
private static Object m2() {
Object o = new Object();
return o;
}
}
在main、t1调用m1方法打上断点
将debug模式改为线程模式 右键断点处,不然就无法看到线程间的调用过程
debug走起
运行到main线程的m1()
点开下拉列表
选择t1线程,也在m1方法处 。两个线程都处于running(就绪、运行都属于running)且互不影响
栈是线程私有的,栈帧以线程为单位,不同线程的栈帧之间相互独立。
线程上下文切换(Thread Context Switch)因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码
线程的cpu 时间片用完 垃圾回收 有更高优先级的线程需要运行 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法 当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念 就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等 Context Switch 频繁发生会影响性能
即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是几十毫秒(ms)。CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。
这就像我们同时读两本书,当我们在读一本英文的技术书时,发现某个单词不认识,于是便打开中英文字典,但是在放下英文技术书之前,大脑必须先记住这本书读到了多少页的第多少行,等查完单词之后,能够继续读这本书。这样的切换是会影响读书效率的,同样上下文切换也会影响多线程的执行速度。
常见方法 方法名static功能说明注意start()启动一个新线 程,在新的线程 运行 run 方法 中的代码start 方法只是让线程进入就绪,里面代码不一定立刻 运行(CPU 的时间片还没分给它)。每个线程对象的 start方法只能调用一次,如果调用了多次会出现 IllegalThreadStateExceptionrun()新线程启动后会 调用的方法如果在构造 Thread 对象时传递了 Runnable 参数,则 线程启动后会调用 Runnable 中的 run 方法,否则默 认不执行任何操作。但可以创建 Thread 的子类对象, 来覆盖默认行为join()等待线程运行结 束join(long n)等待线程运行结 束,最多等待 n 毫秒getId()获取线程长整型 的 idid 唯一getName()获取线程名setName(String)修改线程名getPriority()获取线程优先级setPriority(int)修改线程优先级java中规定线程优先级是1~10 的整数,较大的优先级 能提高该线程被 CPU 调度的机率getState()获取线程状态Java 中线程状态是用 6 个 enum 表示,分别为: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATEDisInterrupted()判断是否被打 断,不会清除 打断标记isAlive()线程是否存活 (还没有运行完 毕)interrupt()打断线程如果被打断线程正在 sleep,wait,join 会导致被打断 的线程抛出 InterruptedException,并清除 ;如果打断的正在运行的线程,则会设置 ;park 的线程被打断,也会设置 打断标记interrupted()static判断当前线程是 否被打断会清除 打断标记currentThread()static获取当前正在执 行的线程sleep(long n)static让当前执行的线 程休眠n毫秒, 休眠时让出 cpu 的时间片给其它 线程yield()static提示线程调度器 让出当前线程对 CPU的使用主要是为了测试和调试 start 与 runstart表示启动一个线程,run则是启动线程后,执行的代码
直接调用 run 是在主线程中执行了 run,没有启动新的线程 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
一个线程 不能多次调用start 方法。
直接调用 runpackage com.dongguo.thread;
import java.util.concurrent.*;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo4 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " run1");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t1.run();
System.out.println(Thread.currentThread().getName() + " run2");
}
}
运行结果
main run1
main run2
这里是main线程调用了run方法而非t1线程(t1还没有start创建)
调用 start将上述代码的 t1.run() 改为 t1.start()
package com.dongguo.thread;
import java.util.concurrent.*;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo4 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " run1");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t1.start();
System.out.println(Thread.currentThread().getName() + " run2");
}
}
运行结果
main run2
t1 run1
t1线程和main线程并行执行
sleep 与 yield sleep-
调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
-
其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
-
睡眠结束后的线程未必会立刻得到执行
-
建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
package com.dongguo.thread;
import java.util.concurrent.TimeUnit;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo5 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " run1");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t1.start();
System.out.println(t1.getState());//获取t1线程的状态
try {
TimeUnit.SECONDS.sleep(1);//睡眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " run2");
System.out.println(t1.getState());//获取t1线程的状态
t1.interrupt();//打断正在睡眠的线程
System.out.println(t1.getState());//获取t1线程的状态
}
}
运行结果
RUNNABLE
t1 run1
main run2
TIMED_WAITING
TERMINATED
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.dongguo.thread.ThreadDemo5.lambda$main$0(ThreadDemo5.java:15)
at java.lang.Thread.run(Thread.java:748)
RUNNABLE 运行状态
TIMED_WAITING 超时等待
TERMINATED 终止
yield-
调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
-
具体的实现依赖于操作系统的任务调度器 。 线程放弃执行的机会,有可能任务调度器还会调度这个线程
线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器会根据情况决定调度哪个线程, 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1//最小优先级
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5; //默认优先级
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10; //最大优先级
/**
* Changes the priority of this thread.
*
* First the checkAccess
method of this thread is called
* with no arguments. This may result in throwing a
* SecurityException
.
*
* Otherwise, the priority of this thread is set to the smaller of
* the specified newPriority
and the maximum permitted
* priority of the thread's thread group.
*
* @param newPriority priority to set this thread to
* @exception IllegalArgumentException If the priority is not in the
* range MIN_PRIORITY
to
* MAX_PRIORITY
.
* @exception SecurityException if the current thread cannot modify
* this thread.
* @see #getPriority
* @see #checkAccess()
* @see #getThreadGroup()
* @see #MAX_PRIORITY
* @see #MIN_PRIORITY
* @see ThreadGroup#getMaxPriority()
*/
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
创建两个线程,每个线程死循环count++。理想情况下输出count的值应该是差不多的
package com.dongguo.thread;
import java.util.concurrent.TimeUnit;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo6 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
int count = 0;
for (; ; ) {
System.out.println(Thread.currentThread().getName() + "----" + count++);
}
}, "t1");
Thread t2 = new Thread(() -> {
int count = 0;
for (; ; ) {
// Thread.yield();
System.out.println(Thread.currentThread().getName() + "----" + count++);
}
}, "t2");
// t1.setPriority(Thread.MIN_PRIORITY);
// t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
如果t2线程Thread.yield();让出运行的机会,
package com.dongguo.thread;
import java.util.concurrent.TimeUnit;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo6 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
int count = 0;
for (; ; ) {
System.out.println(Thread.currentThread().getName() + "----" + count++);
}
}, "t1");
Thread t2 = new Thread(() -> {
int count = 0;
for (; ; ) {
Thread.yield();
System.out.println(Thread.currentThread().getName() + "----" + count++);
}
}, "t2");
// t1.setPriority(Thread.MIN_PRIORITY);
// t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
设置两个线程不同的优先级
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
package com.dongguo.thread;
import java.util.concurrent.TimeUnit;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo6 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
int count = 0;
for (; ; ) {
System.out.println(Thread.currentThread().getName() + "----" + count++);
}
}, "t1");
Thread t2 = new Thread(() -> {
int count = 0;
for (; ; ) {
// Thread.yield();
System.out.println(Thread.currentThread().getName() + "----" + count++);
}
}, "t2");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
}
为什么需要 join 下面的代码执行,打印 r 是什么?
package com.dongguo.thread;
import java.util.concurrent.TimeUnit;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo7 {
static int r = 0;
public static void main(String[] args) {
try {
method1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void method1() throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " run1");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 10;
}, "t1");
t1.start();
// t1.join();
System.out.println(Thread.currentThread().getName() + "结果为:" + r);
}
}
运行结果
main结果为:0
t1 run1
分析 因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10 而主线程一开始就要打印 r 的结果,所以只能打印出 r=0 解决方法 main线程也 sleep 行不行?为什么?
可以,但是main线程不知道t1执行结束到底需要多长时间,所以不建议使用sleep
用 join,加在 t1.start() 之后即可
package com.dongguo.thread;
import java.util.concurrent.TimeUnit;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo7 {
static int r = 0;
public static void main(String[] args) {
try {
method1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void method1() throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " run1");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 10;
}, "t1");
t1.start();
t1.join();
System.out.println(Thread.currentThread().getName() + "结果为:" + r);
}
}
运行结果
t1 run1
main结果为:10
t1.join(); main线程必须等待t1线程运行结束才可以执行接下来的任务
等待多个结果问,下面代码 cost 大约多少秒?
package com.dongguo.thread;
import java.util.concurrent.TimeUnit;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo8 {
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) {
try {
method1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void method1() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
r1 = 10;
});
Thread t2 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
r2 = 20;
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
long end = System.currentTimeMillis();
System.out.println("r1:" + r1 + " r2:" + r2);
System.out.println("总共花费" + (end - start));
}
}
运行结果
r1:10 r2:20
总共花费2001
分析如下 第一个 join:main线程等待 t1 时, t2 并没有停止, 而在运行 第二个 join:1s 后, t1线程执行完毕, t2 也运行了 1s, 因此main也只需再等待 1s
所以main线程一共等待了2s
如果颠倒两个 join 呢?
package com.dongguo.thread;
import java.util.concurrent.TimeUnit;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo8 {
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) {
try {
method1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void method1() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
r1 = 10;
});
Thread t2 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
r2 = 20;
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
t2.join();
t1.join();
long end = System.currentTimeMillis();
System.out.println("r1:" + r1 + " r2:" + r2);
System.out.println("总共花费" + (end - start));
}
}
运行结果
r1:10 r2:20
总共花费2001
分析如下 第一个 join:main线程等待 t12时, t1 并没有停止, 而在运行 第二个 join:2s 后, t2线程执行完毕, t1 在1s之前就已经执行完毕了, 因此main只需等待 2s
所以main线程一共等待了2s
有时效的 join(time)等够时间
package com.dongguo.thread;
import java.util.concurrent.TimeUnit;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo9 {
static int r = 0;
public static void main(String[] args) {
try {
method1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void method1() throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " run1");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 10;
}, "t1");
t1.start();
t1.join(2000);//等待两秒
System.out.println(Thread.currentThread().getName() + "结果为:" + r);
}
}
运行结果
t1 run1
main结果为:10
没等够时间
package com.dongguo.thread;
import java.util.concurrent.TimeUnit;
/**
* @author Dongguo
* @date 2021/9/10 0010-21:55
* @description:
*/
public class ThreadDemo9 {
static int r = 0;
public static void main(String[] args) {
try {
method1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void method1() throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " run1");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 10;
}, "t1");
t1.start();
t1.join(200);//等待200毫秒
System.out.println(Thread.currentThread().getName() + "结果为:" + r);
}
}
运行结果
t1 run1
main结果为:0
原理
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis 0
while (isAlive()) {
long delay = millis - now;
if (delay {
//isDaemon()判断该线程是否是一个守护线程
System.out.println(Thread.currentThread().getName() + "---" + Thread.currentThread().isDaemon());
while (true){
//死循环
}
},"thread1");
thread.start();
System.out.println(Thread.currentThread().getName()+"执行结束");
}
}
用户自定义的线程默认是用户线程,
可以用Thread.currentThread().isDaemon()判断 true为守护线程 false为用户线程
因为写了一个死循环,所以thread1线程没有退出执行,则jvm作为守护线程也仍在运行。
package com.dongguo.n2;
/**
* @author Dongguo
* @date 2021/8/23 0023-18:34
* @description: 用户线程和守护线程
*/
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
//isDaemon()判断该线程是否是一个守护线程
System.out.println(Thread.currentThread().getName() + "---" + Thread.currentThread().isDaemon());
while (true){
//死循环
}
},"thread1");
thread.setDaemon(true);//设置为守护线程,需要在start()方法之前进行
thread.start();
System.out.println(Thread.currentThread().getName()+"执行结束");
}
}
thread.setDaemon(true)将thread1线程设置为守护线程,设置守护线程,需要在start()方法之前进行
因为thread1和jvm都是守护线程 ,此时没有其他的用户线程,所以thread1线程和jvm会相继退出
当程序中所有用户线程执行完毕之后,不管守护线程是否结束,系统都会自动退出
如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出 了。所以当系统只剩下守护进程的时候,java虚拟机会自动退出
这是从 操作系统 层面来描述的
【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行 【运行状态】指获取了 CPU 时间片运行中的状态 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换 【阻塞状态】 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入 【阻塞状态】 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑 调度它们 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
六种这是从 Java API 层面来描述的
Java中线程的状态分为6种
-
初始(NEW):新创建了一个线程对象(new一个实例)出来,线程就进入了初始状态,还没有调用start()方法。
-
运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
2.1. 就绪状态(RUNNABLE之READY) 就绪状态只是说你资格运行,如果调度程序没有挑选到你,你就永远是就绪状态。 1调用线程的start()方法,该状态的线程位于可运行线程池中,等待被线程调度选中获取CPU的使用权(CPU时间片),此时处于就绪状态(ready)。 2当前线程sleep()方法结束或其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。 3当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。 2.2. 运行中状态(RUNNABLE之RUNNING) 线程调度程序从可运行池中选择一个线程作为当前线程 时线程所处的状态。
就绪状态的线程在获得CPU时间片后变为运行中状态(running)。这也是线程进入运行状态的唯一的一种方式。
-
阻塞(BLOCKED):表示线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入**“锁池”**(同步队列)中。
-
等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”(等待队列)中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,(不见不散),否则会处于无限期等待的状态。
-
超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(过时不候)
-
终止(TERMINATED):当线程的run()方法完成时,或者主线程的main()方法完成或者因异常退出了run()方法,该线程结束生命周期。
线程一旦终止了,就不能复生。在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
这6种状态定义在Thread类的State枚举中,可查看源码进行一一对应。
Thread.java
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
*
* - {@link Object#wait() Object.wait} with no timeout
* - {@link #join() Thread.join} with no timeout
* - {@link LockSupport#park() LockSupport.park}
*
*
* A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called Object.wait()
* on an object is waiting for another thread to call
* Object.notify() or Object.notifyAll() on
* that object. A thread that has called Thread.join()
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
*
* - {@link #sleep Thread.sleep}
* - {@link Object#wait(long) Object.wait} with timeout
* - {@link #join(long) Thread.join} with timeout
* - {@link LockSupport#parkNanos LockSupport.parkNanos}
* - {@link LockSupport#parkUntil LockSupport.parkUntil}
*
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的 【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为 是可运行) BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分
等待队列WaitSet就是等待队列
EntryList就是同步队列
Owner就是对象锁持有者
当前线程想调用对象A的同步方法时,发现对象A的锁被别的线程占有,此时当前线程进入同步队列。简言之,同步队列里面放的都是想争夺对象锁的线程。 当一个线程1被另外一个线程2唤醒时,1线程进入同步队列,去争夺对象锁。 同步队列是在同步的环境下才有的概念,一个对象对应一个同步队列。 线程等待时间到了或被notify/notifyAll唤醒后,会进入同步队列竞争锁,如果获得锁,进入RUNNABLE状态,否则进入BLOCKED状态等待获取锁。
- 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。
- 与等待队列相关的步骤和图
1.线程1获取对象A的锁,正在使用对象A。 2.线程1调用对象A的wait()方法。 3.线程1释放对象A的锁,并马上进入等待队列。 4.锁池里面的对象争抢对象A的锁。 5.线程5获得对象A的锁,进入synchronized块,使用对象A。 6.线程5调用对象A的notifyAll()方法,唤醒所有线程,所有线程进入同步队列。若线程5调用对象A的notify()方法,则唤醒一个线程,不知道会唤醒谁,被唤醒的那个线程进入同步队列。 7.notifyAll()方法所在synchronized结束,线程5释放对象A的锁。 8.同步队列的线程争抢对象锁,但线程1什么时候能抢到就不知道了。
几个方法的比较Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,睡眠millis后线程自动苏醒进入就绪状态。
作用:给其它线程执行机会的最佳方式。
Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS操作系统再次选择线程。
作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。
thread.join()/thread.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程一般情况下进入RUNNABLE状态,也有可能进入BLOCKED状态(因为join是基于wait实现的)。
obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。
obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
LockSupport.park()/LockSupport.parkNanos(long nanos),LockSupport.parkUntil(long deadlines), 当前线程进入WAITING/TIMED_WAITING状态。对比wait方法,不需要获得锁就可以让线程进入WAITING/TIMED_WAITING状态,需要通过LockSupport.unpark(Thread thread)唤醒。
大佬问我: notify()是随机唤醒线程么?
Java等待唤醒机制wait/notify深入解析
线程状态的转换假设有线程 Thread t
情况 1 NEW --> RUNNABLE当调用 t.start() 方法时,由 NEW --> RUNNABLE
情况 2 RUNNABLE WAITINGt 线程用 synchronized(obj) 获取了对象锁后 调用 obj.wait() 方法时,t 线程从 RUNNABLE --> WAITING
调用 obj.notify() , obj.notifyAll() , t.interrupt() 时被唤醒 竞争锁成功,t 线程从 WAITING --> RUNNABLE 竞争锁失败,t 线程从 WAITING --> BLOCKED
package com.dongguo.waitnotify;
import java.util.concurrent.TimeUnit;
/**
* @author Dongguo
* @date 2021/9/12 0012-13:36
* @description:
*/
public class WaitNotifyDemo {
static Thread t1, t2;
public static void main(String[] args) {
Object obj = new Object();
t1 = new Thread(() -> {
synchronized (obj) {
try {
obj.wait();
System.out.println("-----------");
System.out.println(t1 + "被notifyAll后 main线程释放锁后,状态" + t1.getState());
System.out.println(t2 + "被notifyAll后 main线程释放锁后,状态" + t2.getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
t1.start();
t2 = new Thread(() -> {
synchronized (obj) {
try {
obj.wait();
System.out.println("-----------");
System.out.println(t2 + "被notifyAll后 main线程释放锁后,状态" + t2.getState());
System.out.println(t1 + "被notifyAll后 main线程释放锁后,状态" + t1.getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t2");
t2.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t1 + "wait后状态" + t1.getState());
System.out.println(t2 + "wait后状态" + t2.getState());
synchronized (obj) {
obj.notifyAll();
System.out.println("-----------");
System.out.println(t1 + "被notifyAll后 main线程没有释放锁,状态" + t1.getState());
System.out.println(t2 + "被notifyAll后 main线程没有释放锁,状态" + t2.getState());
}
}
}
运行结果
Thread[t1,5,main]wait后状态WAITING
Thread[t2,5,main]wait后状态WAITING
-----------
Thread[t1,5,main]被notifyAll后 main线程没有释放锁,状态BLOCKED
Thread[t2,5,main]被notifyAll后 main线程没有释放锁,状态BLOCKED
-----------
Thread[t2,5,main]被notifyAll后 main线程释放锁后,状态RUNNABLE
Thread[t1,5,main]被notifyAll后 main线程释放锁后,状态BLOCKED
-----------
Thread[t1,5,main]被notifyAll后 main线程释放锁后,状态RUNNABLE
Thread[t2,5,]被notifyAll后 main线程释放锁后,状态TERMINATED
t1、t2执行obj.wait()后状态都是WAITING状态
当主线程执行 obj.notifyAll(),由于main线程没有释放obj锁,t1、t2线程都处于BLOCKED状态
当main线程释放obj锁时,t1、t2竞争obj锁,t2线程竞争锁成功处于RUNNABLE状态,t1线程竞争失败处于BLOCKED状态
当t2线程释放锁,t1线程就获得锁。此时t1线程处于RUNNABLE状态,t2线程已经执行完毕处于TERMINATED状态
情况 3 RUNNABLE WAITING当前线程调用 t.join() 方法时,当前线程从 RUNNABLE --> WAITING 注意是当前线程在t 线程对象的监视器上等待 t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING --> RUNNABLE
情况 4 RUNNABLE WAITING当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE --> WAITING 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING --> RUNNABLE
情况 5 RUNNABLE TIMED_WAITINGt 线程用 synchronized(obj) 获取了对象锁后 调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE --> TIMED_WAITING t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时 竞争锁成功,t 线程从 TIMED_WAITING --> RUNNABLE 竞争锁失败,t 线程从 TIMED_WAITING --> BLOCKED
情况 6 RUNNABLE TIMED_WAITING当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE --> TIMED_WAITING 注意是当前线程在t 线程对象的监视器上等待 当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 TIMED_WAITING --> RUNNABLE
情况 7 RUNNABLE TIMED_WAITING当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE --> TIMED_WAITING 当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING --> RUNNABLE
情况 8 RUNNABLE TIMED_WAITING当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线 程从 RUNNABLE --> TIMED_WAITING 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从 TIMED_WAITING–> RUNNABLE
情况 9 RUNNABLE BLOCKEDt 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE --> BLOCKED 持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争 成功,从 BLOCKED --> RUNNABLE ,其它失败的线程仍然 BLOCKED
情况 10 RUNNABLE TERMINATED当前线程所有代码运行完毕,进入 TERMINATED
wait/sleep的区别sleep() 方法使当前线程进入阻塞状态,线程休眠但是不释放同步锁
当 sleep() 休眠时间期满后,该线程不一定会立即执行,因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。
wait() 方法是 Object 类的,当一个线程执行到 wait() 方法时就进入到一个和该对象相关的等待池中,同时释放对象的锁(对于 wait(long timeout) 方法来说是暂时释放锁,因为超时时间到后还需要返还对象锁),其他线程可以访问。wait() 使用 notify() 或 notifyAll() 或者指定睡眠时间来唤醒当前等待池中的线程。wait() 必须放在 synchronized 块中使用,否则会在运行时抛出 IllegalMonitorStateException 异常。
区别
(1)sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用。
(2)sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中)。
(3)sleep可在 任何地方使用,wait()方法依赖synchronized必须放在同步方法或者同步代码块中使用