JUC基础教程阶段一
线程与进程
进程
- 程序由指令和数据组成,但是这些指令要运行,数据要读写,就必须将指令加载到cpu,数据加载至内存。在指令运行过程中还需要用到磁盘,网络等设备,进程就是用来加载指令管理内存管理IO的
- 当一个指令被运行,从磁盘加载这个程序的代码到内存,这时候就开启了一个进程
- 进程就可以视为程序的一个实例,大部分程序都可以运行多个实例进程(例如记事本,浏览器等),部分只可以运行一个实例进程(例如360安全卫士)
线程
- 一个进程之内可以分为一到多个线程。
- 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
- Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作 为线程的容器(这里感觉要学了计算机组成原理之后会更有感觉吧!)
二者对比
进程基本上相互独立的,而线程存在于进程内,是进程的一个子集 进程拥有共享的资源,如内存空间等,供其内部的线程共享 进程间通信较为复杂 同一台计算机的进程通信称为 IPC(Inter-process communication) 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低
友情提示:了解进程首先了解程序(程序是静态的),他是由指令和数据组成,进程就是在操作系统里开辟一个区域加载程序,管理程序和管理IO的,线程就是程序里面的一条条指令。要让进程执行起来就需要这一条条指令都完成。进程是相互独立的,线程是在进程内部的,所以共享进程内部的资源。
并行与并发
并发
在单核 cpu 下,线程实际还是串行执行的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows 下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感 觉是同时运行的 。总结为一句话就是: 微观串行,宏观并行 ,一般会将这种线程轮流使用 CPU 的做法称为并发(concurrent)
并行
多核 cpu下,每个核(core) 都可以调度运行线程,这时候线程可以是并行的,不同的线程同时使用不同的cpu在执行。
二者对比
引用 Rob Pike 的一段描述:并发(concurrent)是同一时间应对(dealing with)多件事情的能力,并行(parallel)是同一时间动手做(doing)多件事情的能力
- 家庭主妇做饭、打扫卫生、给孩子喂奶,她一个人轮流交替做这多件事,这时就是并发
- 雇了3个保姆,一个专做饭、一个专打扫卫生、一个专喂奶,互不干扰,这时是并行
- 家庭主妇雇了个保姆,她们一起这些事,这时既有并发,也有并行(这时会产生竞争,例如锅只有一口,一 个人用锅时,另一个人就得等待)
友情提示:并发,想想面试中常问的高并发,用户那么多,但是核数就那么多,自然就是一个线程来回的切换,同一时间段让人感觉就像是同时在进行,并行,就是多个线程同时运行,说的是同一个时刻,多个线程同时执行。这个当然是在多核CPU情况下。
应用
同步和异步的概念
以调用方的角度讲,如果需要等待结果返回才能继续运行的话就是同步,如果不需要等待就是异步
1) 设计
多线程可以使方法的执行变成异步的,比如说读取磁盘文件时,假设读取操作花费了5秒,如果没有线程的调度机制,这么cpu只能等5秒,啥都不能做。
2) 结论
- 比如在项目中,视频文件需要转换格式等操作比较费时,这时开一个新线程处理视频转换,避免阻塞主线程
- tomcat 的异步 servlet 也是类似的目的,让用户线程处理耗时较长的操作,避免阻塞 tomcat 的工作线程
- ui 程序中,开线程进行其他操作,避免阻塞 ui 线程
应用之提高效率(案例1)
充分利用多核 cpu 的优势,提高运行效率。想象下面的场景,执行 3 个计算,最后将计算结果汇总。
如果是串行执行,那么总共花费的时间是 10 + 11 + 9 + 1 = 31ms
但如果是四核 cpu,各个核心分别使用线程 1 执行计算 1,线程 2 执行计算 2,线程 3 执行计算 3,那么 3 个 线程是并行的,花费时间只取决于最长的那个线程运行的时间,即 11ms 最后加上汇总时间只会花费 12ms
注意
需要在多核 cpu 才能提高效率,单核仍然时是轮流执行
结论
java线程
3.1 创建和运行线程
方法一,直接使用 Thread
// 构造方法的参数是给线程指定名字,,推荐给线程起个名字
Thread t1 = new Thread("t1") {@Override// run 方法内实现了要执行的任务public void run() {log.debug("hello");}
};
t1.start();
Java 8 以后可以使用 lambda 精简代码
@Slf4j(topic = "c.Test1")
public class Test1 {public static void test2() {Thread t = new Thread(()->{ log.debug("running"); }, "t2");t.start();}public static void test1() {Thread t = new Thread(){@Overridepublic void run() {log.debug("running");}};t.setName("t1");t.start();}
}
方法二,使用 Runnable 配合 Thread
把【线程】和【任务】(要执行的代码)分开,Thread 代表线程,Runnable 可运行的任务(线程要执行的代码)Test2.java
// 创建任务对象
Runnable task2 = new Runnable() {@Overridepublic void run() {log.debug("hello");}
};
// 参数1 是任务对象; 参数2 是线程名字,推荐给线程起个名字
Thread t2 = new Thread(task2, "t2");
t2.start();
Java 8 以后可以使用 lambda 精简代码
package cn.itcast.test;import lombok.extern.slf4j.Slf4j;@Slf4j(topic = "c.Test2")
public class Test2 {public static void main(String[] args) {Runnable r = () -> {log.debug("running");};Thread t = new Thread(r, "t2");t.start();}
}
原理之 Thread 与 Runnable 的关系
查看Runnable中的Thread的构造方法,有一个init方法。
查看init方法内部,target就是Runnabble
这是Thread内部的run方法,如果是用Runnable方法,就会使用target如果是直接用Thread就会重写run方法
小结
方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了,用 Runnable 更容易与线程池等高级 API 配合,用 Runnable 让任务类脱离了 Thread 继承体系,更灵活。通过查看源码可以发现,方法二其实到底还是通过方法一执行的!
方法三,FutureTask 配合 Thread
了解一下FutureTask
首先实现了RunnableFuture
RunnableFuture继承了Runnable接口和Future接口
Future接口的get方法就是证明FutureTask可以获取返回值的关键,注意get方法是阻塞式的
下面的是对比Callable接口和Runnbale接口执行线程的对比,Callable接口的call方法可以捕获异常,并且有返回值,Runnable接口的run方法即没有捕获异常也没有返回值。
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况
public static void main(String[] args) throws ExecutionException, InterruptedException {// 实现多线程的第三种方法可以返回数据FutureTask futureTask = new FutureTask<>(new Callable<Integer>() {@Overridepublic Integer call() throws Exception {log.debug("多线程任务");Thread.sleep(100);return 100;}});// 主线程阻塞,同步等待 task 执行完毕的结果new Thread(futureTask,"我的名字").start();log.debug("主线程");log.debug("{}",futureTask.get());}
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
public interface Future<V> {boolean cancel(boolean mayInterruptIfRunning);boolean isCancelled();boolean isDone();V get() throws InterruptedException, ExecutionException;V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}
Future提供了三种功能:
判断任务是否完成;
能够中断任务;
能够获取任务执行结果。
FutureTask是Future和Runable的实现
3.3 查看进程线程的方法
windows
任务管理器可以查看进程和线程数,也可以用来杀死进程
tasklist 查看进程
taskkill 杀死进程
linux
ps -fe 查看所有进程
ps -fT -p 查看某个进程(PID)的所有线程
kill 杀死进程 top 按大写 H 切换是否显示线程
top -H -p 查看某个进程(PID)的所有线程
Java
jps 命令查看所有 Java 进程
jstack 查看某个 Java 进程(PID)的所有线程状态
jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)
3.2 线程运行原理
虚拟机栈与栈帧
拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(stack frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,是属于线程的私有的。当java中使用多线程时,每个线程都会维护它自己的栈帧!每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
线程上下文切换(Thread Context Switch)
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码
下面的是什么情况下会出现上下文切换
- 线程的 cpu 时间片用完(每个线程轮流执行,看前面并行的概念)
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了
sleep
、yield
、wait
、join
、park
、synchronized
、lock
等方法
当 线程上下文切换 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念 就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
3.3 Thread的常见方法
详细版
精简版
3.3.1 start 与 run
友情提示:这个想说的就是对象方式调用run,这个是同步的和start方式调用run是异步的
调用start
public static void main(String[] args) {Thread thread = new Thread(){@Overridepublic void run(){log.debug("我是一个新建的线程正在运行中");FileReader.read(fileName);}};thread.setName("新建线程");thread.start();log.debug("主线程");}
输出:程序在 t1 线程运行, run()
方法里面内容的调用是异步的 Test4.java
11:59:40.711 [main] DEBUG com.concurrent.test.Test4 - 主线程
11:59:40.711 [新建线程] DEBUG com.concurrent.test.Test4 - 我是一个新建的线程正在运行中
11:59:40.732 [新建线程] DEBUG com.concurrent.test.FileReader - read [test] start ...
11:59:40.735 [新建线程] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 3 ms
调用run
将上面代码的thread.start();
改为 thread.run();
输出结果如下:程序仍在 main 线程运行, run()
方法里面内容的调用还是同步的
2:03:46.711 [main] DEBUG com.concurrent.test.Test4 - 我是一个新建的线程正在运行中
12:03:46.727 [main] DEBUG com.concurrent.test.FileReader - read [test] start ...
12:03:46.729 [main] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 2 ms
12:03:46.730 [main] DEBUG com.concurrent.test.Test4 - 主线程
小结
直接调用 run()
是在主线程中执行了 run()
,没有启动新的线程 使用 start()
是启动新的线程,通过新的线程间接执行 run()
方法 中的代码
3.3.2 sleep 与 yield
sleep
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,那么被打断的线程这时就会抛出
InterruptedException
异常【注意:这里打断的是正在休眠的线程,而不是其它状态的线程】 - 睡眠结束后的线程未必会立刻得到执行(需要分配到cpu时间片)
- 建议用 TimeUnit 的
sleep()
代替 Thread 的sleep()
来获得更好的可读性
代码:
package cn.itcast.test;import lombok.extern.slf4j.Slf4j;@Slf4j(topic = "c.Test6")
public class Test6 {public static void main(String[] args) {Thread t1 = new Thread("t1") {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}};t1.start();log.debug("t1 state: {}", t1.getState());try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}log.debug("t1 state: {}", t1.getState());}
}
输出结果
打断Sleep休眠
package cn.itcast.test;import lombok.extern.slf4j.Slf4j;@Slf4j(topic = "c.Test7")
public class Test7 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread("t1") {@Overridepublic void run() {log.debug("enter sleep...");try {Thread.sleep(2000);} catch (InterruptedException e) {log.debug("wake up...");e.printStackTrace();}}};t1.start();Thread.sleep(1000);log.debug("interrupt...");t1.interrupt();}
}
运行结果
使用TimeUnit设置Sleep睡眠时间
主要就可是可以修改时间单位
@Slf4j(topic = "c.Test8")
public class Test8 {public static void main(String[] args) throws InterruptedException {log.debug("enter");TimeUnit.SECONDS.sleep(1);log.debug("end");
// Thread.sleep(1000);}
}
yield
作用:就是将当前运行的线程让出时间片,让其他线程执行,需要注意的是,如果只剩当前线程,那么让出时间片,就是没有用,因为就剩下这一个线程
- 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
- 具体的实现依赖于操作系统的任务调度器(就是可能没有其它的线程正在执行,虽然调用了yield方法,但是也没有用)
yield和Sleep的区别
yield使cpu调用其它线程,但是cpu可能会再分配时间片给该线程;而sleep需要等过了休眠时间之后才有可能被分配cpu时间片
3.3.3 线程优先级
友情提示:线程优先级有1-10等级,默认是5等级,但 cpu 闲时,优先级几乎没作用
线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
代码
package cn.itcast.test;import lombok.extern.slf4j.Slf4j;@Slf4j(topic = "c.Test9")
public class Test9 {public static void main(String[] args) {Runnable task1 = () -> {int count = 0;for (;;) {System.out.println("---->1 " + count++);}};Runnable task2 = () -> {int count = 0;for (;;) {
// Thread.yield();System.out.println(" ---->2 " + count++);}};Thread t1 = new Thread(task1, "t1");Thread t2 = new Thread(task2, "t2");t1.setPriority(Thread.MIN_PRIORITY);t2.setPriority(Thread.MAX_PRIORITY);t1.start();t2.start();}
}
运行结果
设置2优先级较高,发现线程2运行的数字比线程1大
Sleep应用:解决CPU占有100%
3.3.4 join
友情提示:join的作用就是等待该线程执行结束。然后在执行下面的线程
为什么需要 join
static int r = 0;
public static void main(String[] args) throws InterruptedException {test1();
}
private static void test1() throws InterruptedException {log.debug("开始");Thread t1 = new Thread(() -> {log.debug("开始");sleep(1);log.debug("结束");r = 10;});t1.start();log.debug("结果为:{}", r);log.debug("结束");
}
分析
因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10
而主线程一开始就要打印 r 的结果,所以只能打印出 r=0
解决方法
用 sleep 行不行?为什么? 可以但是不知道线程一执行的结束时间
用 join,加在 t1.start() 之后即可
在主线程中调用t1.join,则主线程会等待t1线程执行完之后再继续执行
同步等待多个结果
private static void test1() throws InterruptedException {log.debug("开始");Thread t1 = new Thread(() -> {log.debug("开始");sleep(1);log.debug("结束");r = 10;},"t1");t1.start();t1.join();log.debug("结果为:{}", r);log.debug("结束");}
异步等待多个结果
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {test2();
}
private static void test2() throws InterruptedException {Thread t1 = new Thread(() -> {sleep(1);r1 = 10;});Thread t2 = new Thread(() -> {sleep(2);r2 = 20;
});long start = System.currentTimeMillis();t1.start();t2.start();t1.join();t2.join();long end = System.currentTimeMillis();log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
分析如下
第一个 join:等待 t1 时, t2 并没有停止, 而在运行
第二个 join:1s 后, 执行到此, t2 也运行了 1s, 因此也只需再等待 1s
如果颠倒两个 join 呢?(对象下面右边的流程图)
最终都是输出
20:45:43.239 [main] c.TestJoin - r1: 10 r2: 20 cost: 2005
左边的是上面的代码流程,右边的是join颠倒后的流程
有时效的 join(设置过期时间的)
作用:过期时间一过,就不会继续等待直接运行下面的内容
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {test3();
}
public static void test3() throws InterruptedException {Thread t1 = new Thread(() -> {sleep(1);r1 = 10;});long start = System.currentTimeMillis();t1.start();
// 线程执行结束会导致 join 结束t1.join(1500);long end = System.currentTimeMillis();log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
输出
20:48:01.320 [main] c.TestJoin - r1: 10 r2: 0 cost: 1010
没等够时间
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {test3();
}
public static void test3() throws InterruptedException {Thread t1 = new Thread(() -> {sleep(2);r1 = 10;});long start = System.currentTimeMillis();t1.start();// 线程执行结束会导致 join 结束t1.join(1500);long end = System.currentTimeMillis();log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
输出
20:52:15.623 [main] c.TestJoin - r1: 0 r2: 0 cost: 1502
3.3.5 interrupt 方法详解
友情提示:join的底层原理就是wait实现的,interrupt可以打断正在阻塞的线程和正在运行的线程,
打断 sleep,wait,join 的线程,注意sleep,wait,join这种阻塞式的打断调用isInterrupted()返回的是假,而不是真,因为他们是通过异常的方式打断线程的,主要可以打断阻塞,和运行时的线程
先了解一些interrupt()方法的相关知识:博客地址
sleep,wait,join 的线程,这几个方法都会让线程进入阻塞状态,以 sleep 为例
代码1
package cn.itcast.test;import lombok.extern.slf4j.Slf4j;@Slf4j(topic = "c.Test11")
public class Test11 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {log.debug("sleep...");try {Thread.sleep(5000); // wait, join} catch (InterruptedException e) {e.printStackTrace();}},"t1");t1.start();Thread.sleep(1000);log.debug("interrupt");t1.interrupt();log.debug("打断标记:{}", t1.isInterrupted());}
}
输出
代码2
public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread() {@Overridepublic void run() {log.debug("线程任务执行");try {Thread.sleep(10000); // wait, join} catch (InterruptedException e) {//e.printStackTrace();log.debug("被打断");}}};t1.start();Thread.sleep(500);log.debug("111是否被打断?{}",t1.isInterrupted());t1.interrupt();log.debug("222是否被打断?{}",t1.isInterrupted());Thread.sleep(500);log.debug("222是否被打断?{}",t1.isInterrupted());log.debug("主线程");}
输出结果:(我下面将中断和打断两个词混用)可以看到,打断 sleep 的线程, 会清空中断状态,刚被中断完之后t1.isInterrupted()
的值为true
,后来变为false
,即中断状态会被清除。那么线程是否被中断过可以通过异常来判断。【同时要注意如果打断被join()
,wait()
blocked的线程也是一样会被清除,被清除(interrupt status will be cleared)的意思即中断状态设置为false
,被设置( interrupt status will be set)的意思就是中断状态设置为true
】
17:06:11.890 [Thread-0] DEBUG com.concurrent.test.Test7 - 线程任务执行
17:06:12.387 [main] DEBUG com.concurrent.test.Test7 - 111是否被打断?false
17:06:12.390 [Thread-0] DEBUG com.concurrent.test.Test7 - 被打断
17:06:12.390 [main] DEBUG com.concurrent.test.Test7 - 222是否被打断?true
17:06:12.890 [main] DEBUG com.concurrent.test.Test7 - 222是否被打断?false
17:06:12.890 [main] DEBUG com.concurrent.test.Test7 - 主线程
打断正常运行的线程(这种是比较优雅的打断线程)
打断正常运行的线程, 线程并不会暂停,只是调用方法Thread.currentThread().isInterrupted();
的返回值为true,可以判断Thread.currentThread().isInterrupted();
的值来手动停止线程
public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while(true) {boolean interrupted = Thread.currentThread().isInterrupted();if(interrupted) {log.debug("被打断了, 退出循环");break;}}}, "t1");t1.start();Thread.sleep(1000);log.debug("interrupt");t1.interrupt();}
终止模式之两阶段终止模式(多线程设计模式利用Interrupt打断
)
友情提示:可以用在监控应用上,比如,监控计算机性能,开始让线程一直监控,然后通过某一个按钮,可以停止该线程监控。
两阶段终止模式(Two Phase Termination),就是考虑在一个线程T1中如何优雅地终止另一个线程T2?这里的优雅指的是给T2一个料理后事的机会(如释放锁)。
错误的思路
注意不能使用stop这样容易造成死锁,停止了那么锁就不能释放。
注意,阻塞线程,出现异常,如果进行打断,打断标记为false,如果是正常运行被打断,打断标记是true
如下所示:那么线程的isInterrupted()
方法可以取得线程的打断标记,如果线程在睡眠sleep
期间被打断,打断标记是不会变的,为false,但是sleep
期间被打断会抛出异常,我们据此手动设置打断标记为true
;如果是在程序正常运行期间被打断的,那么打断标记就被自动设置为true
。处理好这两种情况那我们就可以放心地来料理后事
监控功能实现
代码实现如下:
@Slf4j
public class Test11 {public static void main(String[] args) throws InterruptedException {TwoParseTermination twoParseTermination = new TwoParseTermination();twoParseTermination.start();Thread.sleep(3000); // 让监控线程执行一会儿twoParseTermination.stop(); // 停止监控线程}
}@Slf4j
class TwoParseTermination{Thread thread ;public void start(){thread = new Thread(()->{while(true){if (Thread.currentThread().isInterrupted()){log.debug("线程结束。。正在料理后事中");break;}try {Thread.sleep(500);log.debug("正在执行监控的功能");} catch (InterruptedException e) {
//注意这个一定要加上,在sleep下打断会抛出异常,但是打断标记是为false,这样上面的if是不会执行的,
//所以在执行一次打断标记Thread.currentThread().interrupt();e.printStackTrace();}}});thread.start();}public void stop(){thread.interrupt();}
}
友情提示:isInterrupted()和Interrupted()的区别,当打断后,一个不会清除打断标记(isInterrupted),一个会清除打断标记(Interrupted)。
Interrupt打断 park 线程
打断 park 线程后, 不会清空打断状态,就是说,打断一次park,后面如果在想park让线程阻塞,是阻塞不了的
友情提示:这个park和unpark都是LockSupport的方法,需要注意的是如果调用了interrupt之后,随后再次调用LockSupport.park();是没有效果的,如果还想让其有效果就需要让其调用isInterrupted状态变成false,可以用Interrupted方法,调用之后,会自动清除标记设置为false.
private static void test3() throws InterruptedException {Thread t1 = new Thread(() -> {log.debug("park...");LockSupport.park();log.debug("unpark...");
//注意啊,这个调用一次打断,后面如果还想再次park是不行的,可以调用Thread.currentThread().Interrupted()方法执行,清除打断标记变成falselog.debug("打断状态:{}", Thread.currentThread().isInterrupted());}, "t1");t1.start();sleep(0.5);t1.interrupt();
}
输出
21:11:52.795 [t1] c.TestInterrupt - park...
21:11:53.295 [t1] c.TestInterrupt - unpark...
21:11:53.295 [t1] c.TestInterrupt - 打断状态:true
下面的是打断后,再次调用park让其再次生效
友情提示:使用interrupted()方法,清除打断比较,让其打断标记变成false
常见的过时方法
友情提示:这些方法,都是容易造成死锁,所以不建议使用
还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,不能释放锁,造成线程死锁
对应上面过时的方法取代
1、对于stop可以使用两阶段终止(线程设计模式)的方式停止,而不要stop方式。
2、suspend可以使用wait进行取代
3、resume可以使用notify进行唤醒
3.3.6 sleep,yiled,wait,join 对比
关于join的原理和这几个方法的对比:看这里
补充:
- sleep,join,yield,interrupted是Thread类中的方法
- wait/notify是object中的方法
sleep 不释放锁、释放cpu join 释放锁、抢占cpu yiled 不释放锁、释放cpu wait 释放锁、释放cpu
3.4 主线程与守护线程
默认情况下,java进程需要等待所有的线程结束后才会停止,但是有一种特殊的线程,叫做守护线程,在其他线程全部结束的时候即使守护线程还未结束代码未执行完java进程也会停止。普通线程t1可以调用t1.
setDaemon(true);
方法变成守护线程
注意 垃圾回收器线程就是一种守护线程 Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等 待它们处理完当前请求
3.5 线程状态之五种状态
五种状态的划分主要是从操作系统的层面进行划分的
- 初始状态,仅仅是在语言层面上创建了线程对象,即
Thead thread = new Thead();
,还未与操作系统线程关联 - 可运行状态,也称就绪状态,指该线程已经被创建,与操作系统相关联,等待cpu给它分配时间片就可运行
- 运行状态,指线程获取了CPU时间片,正在运行
- 当CPU时间片用完,线程会转换至【可运行状态】,等待 CPU再次分配时间片,会导致我们前面讲到的上下文切换
- 阻塞状态
- 如果调用了阻塞API,如BIO读写文件,那么线程实际上不会用到CPU,不会分配CPU时间片,会导致上下文切换,进入【阻塞状态】
- 等待BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是,只要操作系统一直不唤醒线程,调度器就一直不会考虑调度它们,CPU就一直不会分配时间片
- 终止状态,表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
3.6 线程状态之六种状态
这是从 Java API 层面来描述的,我们主要研究的就是这种。状态转换详情图:地址 根据 Thread.State 枚举,分为六种状态
- NEW 跟五种状态里的初始状态是一个意思
- RUNNABLE 是当调用了
start()
方法之后的状态,注意,Java API 层面的RUNNABLE
状态涵盖了操作系统层面的【可运行状态】、【运行状态】和【io阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行) BLOCKED
,WAITING
(对应的wait) ,TIMED_WAITING(对应的Sleep)
都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节 详述
代码测试java中线程的6中状态
@Slf4j(topic = "c.TestState")
public class TestState {public static void main(String[] args) throws IOException {Thread t1 = new Thread("t1") {@Overridepublic void run() {log.debug("running...");}};Thread t2 = new Thread("t2") {@Overridepublic void run() {while(true) { // runnable}}};t2.start();Thread t3 = new Thread("t3") {@Overridepublic void run() {log.debug("running...");}};t3.start();Thread t4 = new Thread("t4") {@Overridepublic void run() {synchronized (TestState.class) {try {Thread.sleep(1000000); // timed_waiting} catch (InterruptedException e) {e.printStackTrace();}}}};t4.start();Thread t5 = new Thread("t5") {@Overridepublic void run() {try {t2.join(); // waiting} catch (InterruptedException e) {e.printStackTrace();}}};t5.start();Thread t6 = new Thread("t6") {@Overridepublic void run() {synchronized (TestState.class) { // blockedtry {Thread.sleep(1000000);} catch (InterruptedException e) {e.printStackTrace();}}}};t6.start();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}log.debug("t1 state {}", t1.getState());log.debug("t2 state {}", t2.getState());log.debug("t3 state {}", t3.getState());log.debug("t4 state {}", t4.getState());log.debug("t5 state {}", t5.getState());log.debug("t6 state {}", t6.getState());System.in.read();}
}
运行结果
统筹规划
从这个图上可以一眼看出,办法甲总共要16分钟(而办法乙、丙需要20分钟)。如果要缩短工时、提高工作 效率,应当主要抓烧开水这个环节,而不是抓拿茶叶等环节。同时,洗茶壶茶杯、拿茶叶总共不过4分钟,大 可利用“等水开”的时间来做
最佳方案:
看来这是“小题大做”,但在工作环节太多的时候,这样做就非常必要了。 这里讲的主要是时间方面的事,但在具体生产实践中,还有其他方面的许多事。这种方法虽然不一定能直接 解决所有问题,但是,我们利用这种方法来考虑问题,也是不无裨益的。
代码
@Slf4j(topic = "c.Test16")
public class Test16 {public static void main(String[] args) {Thread t1 = new Thread(() -> {log.debug("洗水壶");sleep(1);log.debug("烧开水");sleep(5);},"老王");Thread t2 = new Thread(() -> {log.debug("洗茶壶");sleep(1);log.debug("洗茶杯");sleep(2);log.debug("拿茶叶");sleep(1);try {t1.join();} catch (InterruptedException e) {e.printStackTrace();}log.debug("泡茶");},"小王");t1.start();t2.start();}
}
JUC基础教程阶段一相关推荐
- AngularJs 基础教程 —— 依赖注入
为什么80%的码农都做不了架构师?>>> 本文为 H5EDU 机构官方 HTML5培训 教程,主要介绍:AngularJs 基础教程 -- 依赖注入 AngularJS 依赖注 ...
- iOS 10应用开发基础教程
iOS 10应用开发基础教程 介绍: 本教程是国内第一本iOS 10开发应用教程.本教程基于Xcode 8.0,使用Swift 3.0语言讲解如何开发iOS 10的应用App. 学习建议:本教程针对i ...
- Spring Boot 2.x基础教程:使用JTA实现分布式事务
在一个Spring Boot项目中,连接多个数据源还是比较常见的.之前也介绍了如何在几种常用框架的场景下配置多数据源,具体可见: Spring Boot 2.x基础教程:JdbcTemplate的多数 ...
- python基础教程第二版和第三版哪个好-python基础教程 2版和3版哪个适合新手?!...
python基础教程 2版和3版哪个适合新手? 现在学是学python3. 两者的差异发者本身影响并不大,个别语法细微的差比如python3的print数方式使用,一些常用模块的名称差异,一些机制的差 ...
- python基础教程视频(全13集)-Python基础视频教程(600集)【传智播客精品教程】...
Python基础视频教程 初级共600节课 本套视频是传智播客紧抓未来最大趋势--人工智能,应运而生的一套Python入门视频教程,Python是学习人工智能的首选语言,而该视频是专门针对零基础的学员 ...
- python基础教程第三版和第二版选哪个-python基础教程 2版和3版哪个适合新手?!...
python基础教程 2版和3版哪个适合新手? 现在学是学python3. 两者的差异发者本身影响并不大,个别语法细微的差比如python3的print数方式使用,一些常用模块的名称差异,一些机制的差 ...
- python基础教程多少钱-厦门厦禾路Python基础教程培训费用多少-泰兴市新闻
厦门厦禾路Python基础教程培训费用多少-泰兴市新闻 [美力程]是厦门中信教育旗下,专注于中国6-18岁青少年STEAM创客教育的先锋品牌.依托中心20+年积累的计算机教育经验和职业IT精英教研团队 ...
- python基础教程书籍推荐-入门python有什么好的书籍推荐?
Python编程语言有许多语法结构.标准库函数和交互式开发环境功能.好在,你可以忽略大多数内容.你只需要学习部分内容,就能编写一些方便的小程序. 但在动手之前,你必须学习一些基本编程概念.就像魔法师培 ...
- python基础教程是什么意思-python基础教程都有什么?
分享一下传智播客的python基础教程 第一阶段 Python核心编程 可掌握的核心能力 1.掌握Python基础语法,具备基础的编程能力; 2.建立起编程思维以及面向对象程序设计思想.解决的现实问题 ...
最新文章
- python代码大全表解释-.python3基础之“术语表(1)”
- windows/linux服务器上java使用openoffice将word文档转换为PDF(亲测可用)
- 口令加密算法 - Java加密与安全
- java让对象分配在栈上_java – Hotspot何时可以在堆栈上分配对象?
- 区分错误类型_形象解释 Python 新手最容易犯的错误
- matlab数字图像处理大作业_线上教学优秀案例(16) | 数字图像处理基于蓝墨云+企业微信的线上教学经验分享...
- CentOS7 搭建samba服务
- oracle实施伙伴,甲骨文推出Oracle合作伙伴网络专属计划
- IPC$经典入侵步骤和常用net命令
- 老虎淘客系统淘宝平台无法生成淘口令问题如何解决?
- 如何擦除计算机连接网络的记录,如何清除上网记录 清除上网记录方法汇总
- Android EGL入门
- 用 Creator 写微信小游戏排行榜
- 三维匹配_倾斜影像和近景影像空地融合精细化实景三维建模
- Ubuntu18.04局域网共享文件夹,实现win7和Ubuntu本地访问
- 保护你的聊天隐私---“外挂式”加密软件设计思路
- 设置bing桌面壁纸
- Cordova徽章插件
- 2023最新大数据毕设选题
- 解决VMware虚拟机无法识别U盘问题