线程与进程

进程

  • 程序由指令和数据组成,但是这些指令要运行,数据要读写,就必须将指令加载到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提供了三种功能:   

  1. 判断任务是否完成;   

  2. 能够中断任务;   

  3. 能够获取任务执行结果。

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 时间片用完(每个线程轮流执行,看前面并行的概念)
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleepyieldwaitjoinparksynchronizedlock 等方法

当 线程上下文切换 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,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

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,那么被打断的线程这时就会抛出 InterruptedException异常【注意:这里打断的是正在休眠的线程,而不是其它状态的线程】
  3. 睡眠结束后的线程未必会立刻得到执行(需要分配到cpu时间片)
  4. 建议用 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

作用:就是将当前运行的线程让出时间片,让其他线程执行,需要注意的是,如果只剩当前线程,那么让出时间片,就是没有用,因为就剩下这一个线程

  1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
  2. 具体的实现依赖于操作系统的任务调度器(就是可能没有其它的线程正在执行,虽然调用了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的原理和这几个方法的对比:看这里

补充:

  1. sleep,join,yield,interrupted是Thread类中的方法
  2. 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 线程状态之五种状态

五种状态的划分主要是从操作系统的层面进行划分的

  1. 初始状态,仅仅是在语言层面上创建了线程对象,即Thead thread = new Thead();,还未与操作系统线程关联
  2. 可运行状态,也称就绪状态,指该线程已经被创建,与操作系统相关联,等待cpu给它分配时间片就可运行
  3. 运行状态,指线程获取了CPU时间片,正在运行
    1. 当CPU时间片用完,线程会转换至【可运行状态】,等待 CPU再次分配时间片,会导致我们前面讲到的上下文切换
  4. 阻塞状态
    1. 如果调用了阻塞API,如BIO读写文件,那么线程实际上不会用到CPU,不会分配CPU时间片,会导致上下文切换,进入【阻塞状态】
    2. 等待BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
    3. 与【可运行状态】的区别是,只要操作系统一直不唤醒线程,调度器就一直不会考虑调度它们,CPU就一直不会分配时间片
  5. 终止状态,表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

3.6 线程状态之六种状态

这是从 Java API 层面来描述的,我们主要研究的就是这种。状态转换详情图:地址 根据 Thread.State 枚举,分为六种状态

  1. NEW 跟五种状态里的初始状态是一个意思
  2. RUNNABLE 是当调用了 start() 方法之后的状态,注意,Java API 层面的 RUNNABLE 状态涵盖了操作系统层面的【可运行状态】、【运行状态】和【io阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
  3. BLOCKEDWAITING(对应的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基础教程阶段一相关推荐

  1. AngularJs 基础教程 —— 依赖注入

    为什么80%的码农都做不了架构师?>>>    本文为 H5EDU 机构官方 HTML5培训 教程,主要介绍:AngularJs 基础教程 -- 依赖注入 AngularJS 依赖注 ...

  2. iOS 10应用开发基础教程

    iOS 10应用开发基础教程 介绍: 本教程是国内第一本iOS 10开发应用教程.本教程基于Xcode 8.0,使用Swift 3.0语言讲解如何开发iOS 10的应用App. 学习建议:本教程针对i ...

  3. Spring Boot 2.x基础教程:使用JTA实现分布式事务

    在一个Spring Boot项目中,连接多个数据源还是比较常见的.之前也介绍了如何在几种常用框架的场景下配置多数据源,具体可见: Spring Boot 2.x基础教程:JdbcTemplate的多数 ...

  4. python基础教程第二版和第三版哪个好-python基础教程 2版和3版哪个适合新手?!...

    python基础教程 2版和3版哪个适合新手? 现在学是学python3. 两者的差异发者本身影响并不大,个别语法细微的差比如python3的print数方式使用,一些常用模块的名称差异,一些机制的差 ...

  5. python基础教程视频(全13集)-Python基础视频教程(600集)【传智播客精品教程】...

    Python基础视频教程 初级共600节课 本套视频是传智播客紧抓未来最大趋势--人工智能,应运而生的一套Python入门视频教程,Python是学习人工智能的首选语言,而该视频是专门针对零基础的学员 ...

  6. python基础教程第三版和第二版选哪个-python基础教程 2版和3版哪个适合新手?!...

    python基础教程 2版和3版哪个适合新手? 现在学是学python3. 两者的差异发者本身影响并不大,个别语法细微的差比如python3的print数方式使用,一些常用模块的名称差异,一些机制的差 ...

  7. python基础教程多少钱-厦门厦禾路Python基础教程培训费用多少-泰兴市新闻

    厦门厦禾路Python基础教程培训费用多少-泰兴市新闻 [美力程]是厦门中信教育旗下,专注于中国6-18岁青少年STEAM创客教育的先锋品牌.依托中心20+年积累的计算机教育经验和职业IT精英教研团队 ...

  8. python基础教程书籍推荐-入门python有什么好的书籍推荐?

    Python编程语言有许多语法结构.标准库函数和交互式开发环境功能.好在,你可以忽略大多数内容.你只需要学习部分内容,就能编写一些方便的小程序. 但在动手之前,你必须学习一些基本编程概念.就像魔法师培 ...

  9. python基础教程是什么意思-python基础教程都有什么?

    分享一下传智播客的python基础教程 第一阶段 Python核心编程 可掌握的核心能力 1.掌握Python基础语法,具备基础的编程能力; 2.建立起编程思维以及面向对象程序设计思想.解决的现实问题 ...

最新文章

  1. python代码大全表解释-.python3基础之“术语表(1)”
  2. windows/linux服务器上java使用openoffice将word文档转换为PDF(亲测可用)
  3. 口令加密算法 - Java加密与安全
  4. java让对象分配在栈上_java – Hotspot何时可以在堆栈上分配对象?
  5. 区分错误类型_形象解释 Python 新手最容易犯的错误
  6. matlab数字图像处理大作业_线上教学优秀案例(16) | 数字图像处理基于蓝墨云+企业微信的线上教学经验分享...
  7. CentOS7 搭建samba服务
  8. oracle实施伙伴,甲骨文推出Oracle合作伙伴网络专属计划
  9. IPC$经典入侵步骤和常用net命令
  10. 老虎淘客系统淘宝平台无法生成淘口令问题如何解决?
  11. 如何擦除计算机连接网络的记录,如何清除上网记录 清除上网记录方法汇总
  12. Android EGL入门
  13. 用 Creator 写微信小游戏排行榜
  14. 三维匹配_倾斜影像和近景影像空地融合精细化实景三维建模
  15. Ubuntu18.04局域网共享文件夹,实现win7和Ubuntu本地访问
  16. 保护你的聊天隐私---“外挂式”加密软件设计思路
  17. 设置bing桌面壁纸
  18. Cordova徽章插件
  19. 2023最新大数据毕设选题
  20. 解决VMware虚拟机无法识别U盘问题

热门文章

  1. 集成框架 -- 聚水潭对接
  2. 画图软件--亿图图示专家中文显示
  3. 程序员在北京可以选择哪些国企、央企以及研究所?
  4. mysql类似于excel的删除重复项_sql删除重复项并保留其中一条(含sql优化)
  5. 如何下载房山区卫星地图高清版大图
  6. goland安装教程
  7. 接口测试平台代码实现10:菜单页面升级
  8. 基于深度学习的智能问答
  9. pt工具的使用(1) pt工具的安装
  10. 如何正确新建M文件并使用自建函数?