Java - JUC详解
目录
一、了解和JUC相关的概念
二、Java线程
三、线程共享模型
一、了解和JUC相关的概念
1.1 什么是JUC?
JUC是java.util.concurrent包的简称,在Java5.0添加,目的就是为了更好的支持高并发任务。让开发者进行多线程编程时减少竞争条件和死锁的问题!
1.2 什么是进程?
- 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还要用到磁盘、网络等设备。进程就是用来加载指令,管理内存管理IO的。
- 当一个进程被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
- 进程就可以视为一个实例。大部分程序可以同时运行多个实例(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程(例如网易云音乐、360安全卫士)。
1.3 什么是线程?
- 一个进程之内可以分为一到多个线程。
- 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行。
- Java中,线程作为最小的调度单位,进程作为资源分配的最小单位。在windows中进程是不活动的,只是作为线程的容器。
进程与线程的对比:
- 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
- 进程拥有共享的资源,如内存空间等,供其内部的线程共享
- 进程间通信较为复杂 。同一台计算机的进程通信称为 IPC(Inter-process communication)。不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP。
- 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
- 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低
1.4 并发与并行
- 并发(concurrent)是同一时间段分别应对(dealing with)多件事情的能力
- 并行(parallel)是同一时间段同时动手做(doing)多件事情的能力
1.5 同步和异步
从方法调用的角度来讲,如果:
- 需要等待结果返回,才能继续运行的就是同步
- 不需要等待结果返回,就能继续运行的就是异步
注意:同步在多线程中还有另外一层意思,是让多个线程步调一致
二、Java线程
2.1 创建线程的三种方法
方法一:直接使用Thread
// 创建线程对象
Thread t = new Thread() {public void run() {// 要执行的任务}
};
// 启动线程
t.start();
方法二:使用Runnable配合Thread
Runnable runnable = new Runnable() {public void run(){// 要执行的任务}
};
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
t.start();
Java 8以后可以使用lambda精简代码:
// 创建任务对象
Runnable task2 = () -> log.debug("hello");
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
- 方法一 是把线程和任务合并在了一起,方法二 是把线程和任务分开了
- 用 Runnable 更容易与线程池等高级 API 配合
- 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活
方法三:FutureTask 配合 Thread
// 创建任务对象
FutureTask<Integer> task3 = new FutureTask<>(() -> {log.debug("hello");return 100;
});
// 参数1 是任务对象; 参数2 是线程名字,推荐
new Thread(task3, "t3").start();
// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task3.get();
log.debug("结果是:{}", result);
2.2 查看进程线程的方法
windows
- 任务管理器可以查看进程和线程数,也可以用来杀死进程
- tasklist 查看进程
- taskkill 杀死进程
linux
- ps -fe 查看所有进程
- ps -fT -p <PID> 查看某个进程(PID)的所有线程
- kill 杀死进程
- top 按大写 H 切换是否显示线程
- top -H -p <PID> 查看某个进程(PID)的所有线程
Java
- jps 命令查看所有 Java 进程
- jstack <PID> 查看某个 Java 进程(PID)的所有线程状态
- jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)
2.3 线程有关的常见方法
方法名 | static | 功能说明 | 注意 |
---|---|---|---|
start() |
启动一个新线程,在新的线程运行 run 方法中的代码
|
start 方法只是让线程进入就绪,里面代码不一定立刻 运行(CPU
的时间片还没分给它)。每个线程对象的 start方法只能调用一次,
如果调用了多次会出现 IllegalThreadStateException
|
|
run()
|
新线程启动后会
调用的方法
|
如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默 认不执行任何操作。但可以创建 Thread 的子类对象, 来覆盖默认行为
|
|
join()
|
等待线程运行结束
|
||
join(long n)
|
等待线程运行结
束(最多等待 n 毫秒)
|
如果线程结束了,就继续向下执行,不会一直等待到最大时间 | |
getId()
|
获取线程长整型的 id
|
id 唯一
|
|
getName()
|
获取线程名
|
||
setName(String)
|
修改线程名
|
||
getPriority()
|
获取线程优先级
|
||
setPriority(int)
|
修改线程优先级
|
java中规定线程优先级是1~10 的整数,较大的优先级
能提高该线程被 CPU 调度的机率
|
|
getState()
|
获取线程状态
|
Java 中线程状态是用 6 个 enum 表示,分别为:
NEW, RUNNABLE, BLOCKED, WAITING,
TIMED_WAITING, TERMINATED
|
|
interrupted()
|
判断是否被打断
|
不会清除 打断标记
|
|
isAlive()
|
线程是否存活(还没有运行完毕)
|
||
interrupt()
|
打断线程
|
如果被打断线程正在 sleep,wait,join 会导致被打断
的线程抛出 InterruptedException,并清除 打断标
记 ;如果打断的正在运行的线程,则会设置 打断标
记 ;park 的线程被打断,也会设置 打断标记
|
|
interrupted()
|
static
|
判断当前线程是
否被打断
|
会清除 打断标记
|
currentThread()
|
static
|
获取当前正在执行的线程
|
|
sleep(long n)
|
static
|
让当前执行的线
程休眠n毫秒,
休眠时让出 cpu
的时间片给其它
线程
|
|
yield()
|
static
|
提示线程调度器
让出当前线程对
CPU的使用
|
主要是为了测试和调试
|
【start与run方法】
我们通过代码示例可以看出start和run的区别:使用 t1.run() 时:
public static void main(String[] args) {Thread t1 = new Thread("t1") {@Overridepublic 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 ...
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
小结:
- 直接调用 run 是在主线程中执行了 run,没有启动新的线程
- 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
【sleep与yield方法】
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 阻塞状态
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
- 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态 ,然后调度执行其它线程
- 具体的实现依赖于操作系统的任务调度器(可能出现没 “让” 出去地的现象)
【jion方法】
- 等待线程运行结束
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("开始");Thread.sleep(1000);log.debug("结束");r = 10;});t1.start();t1.jion();// 在start后调用joinlog.debug("结果为:{}", r);log.debug("结束");
}
不加 t1.join 输出结果r为 0 ,加了 t1.join() 后输出结果r为 10;原因是加了 t1.join() ,主线程运行到此行后会等待 t1 运行结束后再继续向下运行,即让 main 线程同步等待 t1 线程。
【interrupt方法】
private static void test1() throws InterruptedException {Thread t1 = new Thread(()->{log.debug("sleep...");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace;}}, "t1");t1.start();Thread.sleep(1000);log.debug("interrupt");t1.interrupt();log.debug(" 打断标记: {}", t1.isInterrupted());
}
输出:
2、打断正常运行的线程时:不会清空打断状态,即打断标志置为true;同时,interrupt打断正常运行的线程时,不会让线程停下来,线程会继续执行。若想让线程停下来,需要根据对打断标志为 true 的判断从而手动让线程停下来。
private static void test2() throws InterruptedException {Thread t1 = new Thread(()->{while(true) {Thread current = Thread.currentThread();if(current.isInterrupted()) {log.debug("被打断了,退出循环");break;}}}, "t1");t1.start();Thread.sleep(1000);t1.interrupt();
}
输出:
下面来一道关于interrupt的常见面试题:在一个线程T1中如何优雅地终止线程T2?这里的优雅是指给T2一个料理后事的机会。
1. 如果使用线程的 stop() 方法停止线程:stop() 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当他被杀死后就再也没有机会释放锁,其他线程将永远无法获取该锁。(容易破坏代码块,造成死锁的方法还有suspend():挂起/暂停线程运行、resume():恢复线程运行,这些方法已经过时)
2. 若使用 System.exit(int) 方法停止线程:该方法是让整个进程都停止,而我们只想要一个线程通知,这种做法明显不划算。
此时,使用 interrupt() 方法的两阶段终止模式为最优解:
代码实现如下:
class TwoPhaseTermination{private Thread monitor;// 启动监控线程public void start() {monitor = new Thread(() -> {while(true) {Thread current = Thread.currentThread();if(current.isInterrupted()) {log.debug("料理后事");break;}try {Thread.sleep(1000);// 情况1:中断发生在线程睡眠时,会在catch中抛出异常,中断标志为falselog.debug("执行监控记录");// 情况2:中断发生在正常运行线程时,中断标志置为true} catch (InterruptedException e) {e.printStackTrace();// 重新设置打断标志current.interrupt();}}});monitor.start();// 启动线程}// 停止监控线程public void stop() {monitor.interrupt();}}
输出结果:
【守护线程】
线程对象名.setDaemon(true);// 设置守护线程
守护线程示例:
- 垃圾回收器线程就是一种和守护线程
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 tomcat 接收到 shutdown 命令后,不会等待他们处理完当前请求
2.4 线程的五种状态
从 操作系统 层面来描述:
- 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
- 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
- 【运行状态】指获取了 CPU 时间片运行中的状态 。
- 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
- 【阻塞状态】
- 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
- 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
- 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
2.5 线程的六种状态
从 Java API 层面来描述:
- NEW:线程刚被创建,但是还没有调用 start() 方法
- RUNNABLE:当调用了 start() 方法之后。注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
- BLOCKED(无锁) , WAITING(join) , TIMED_WAITING(sleep):都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节详述
- TERMINATED:当线程代码运行结束
- 当调用 t.start() 方法时,由 NEW --> RUNNABLE
- t 线程用 synchronized(obj) 获取了对象锁后
- 调用 obj.wait() 方法时,t 线程从 RUNNABLE --> WAITING
- 调用 obj.notify() , obj.notifyAll() , t.interrupt() 时:竞争锁成功,t 线程从 WAITING --> RUNNABLE;竞争锁失败,t 线程从 WAITING --> BLOCKE
- 当前线程调用 t.join() 方法时,当前线程从 RUNNABLE --> WAITING。注意是当前线程在t 线程对象的监视器上等待
- t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING --> RUNNABLE
- 当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE --> WAITING
- 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING --> RUNNABLE
- t 线程用 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
- 当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE --> TIMED_WAITING。注意是当前线程在t 线程对象的监视器上等待
- 当前线程等待时间超过了 n 毫秒,或 t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 TIMED_WAITING --> RUNNABLE
- 当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE --> TIMED_WAITING
- 当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING --> RUNNABLE
- 当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线程从 RUNNABLE --> TIMED_WAITING
- 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从 TIMED_WAITING--> RUNNABLE
- t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE --> BLOCKED
- 持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争成功,从 BLOCKED --> RUNNABLE ,其它失败的线程仍然 BLOCKED
- 当前线程所有代码运行完毕,进入 TERMINATED
三、线程共享模型
线程共享模型详解
Java - JUC详解相关推荐
- Java开发 - 不知道算不算详细的JUC详解
前言 大概有快两周没有发文了,这段时间不断的充实自己,算算时间,也到了该收获的时候,今天带来一篇JUC详解,但说实话,我也不敢自信它详不详细.JUC说白了就是多线程,学Java不久的人知道多线程,恐怕 ...
- JUC详解 | JUC Lock
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 JUC详解 | JUC Lock锁的详解 前言 一.Lock锁的入门 1. ReentrantLock可重入锁 1.1 测试可重入性 ...
- Apache Thrift - java开发详解
2019独角兽企业重金招聘Python工程师标准>>> Apache Thrift - java开发详解 博客分类: java 架构 中间件 1.添加依赖 jar <depen ...
- Java泛型详解-史上讲解最详细的,没有之一
目录 1. 概述 2. 一个栗子 3. 特性 4. 泛型的使用 4.1 泛型类 4.2 泛型接口 4.3 泛型通配符 4.4 泛型方法 4.4.1 泛型方法的基本用法 4.4.2 类中的泛型方法 4. ...
- Java虚拟机详解----JVM常见问题总结
[正文] 声明:本文只是做一个总结,有关jvm的详细知识可以参考本人之前的系列文章,尤其是那篇:Java虚拟机详解04----GC算法和种类.那篇文章和本文是面试时的重点. 面试必问关键词:JVM垃圾 ...
- java 泛型详解、Java中的泛型方法、 java泛型详解
本文参考java 泛型详解.Java中的泛型方法. java泛型详解 概述 泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用. 什么是泛型?为什么要使用泛型? 泛型,即& ...
- 最详细的java泛型详解
来源:最详细的java泛型详解 对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下. 本文参考java 泛型详解.Java中的泛型方法. ja ...
- Java异常详解及如何处理
来源:Java异常详解及如何处理 简介 程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常.异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?或者用C语言 ...
- Java基础——Java NIO详解(一)
一.基本概念 1.I/0简介 I/O即输入输出,是计算机与外界世界的一个借口.IO操作的实际主题是操作系统.在java编程中,一般使用流的方式来处理IO,所有的IO都被视作是单个字节的移动,通过str ...
最新文章
- 知乎高赞:OracleJDK和OpenJDK有什么区别?网友:不愧是大神的回答~
- 她琴棋书画全能,还进入清华计算机系实验室,被赞智商太超群、能力过强悍...
- python字符串删除,列表删除以及字典删除的总结
- etherchannel
- 联通研究院处长王志军:Hadoop在电信业大数据的应用
- 全国计算机等级考试题库二级C操作题100套(第01套)
- 用js来实现那些数据结构 第一章
- php 发送post请求json,thinkphp ,php post发送json请求,就收post请求
- python3.5 pip安装_用python3.5 pip安装Numpy
- 父子页面iframe相互调用方法详解
- python pil怎么调用_python3如何使用pil
- html thead隐藏,HTML中thead标签的使用方法
- Unity笔记-29-ARPG游戏项目-11-完善运动
- 离职,我应该做什么?
- Teambition使用教程
- 百度OCR识别手写签名识别率不高解决
- 单片机看门狗c语言,单片机看门狗程序
- 都市丽人全方位能力升维,增长驱动力强劲
- gpt.4.0-gpt 国内版
- failed to findInstances, Micro-service does not exist 问题排查
热门文章
- 蛋花花分析你学完前端后可以做什么
- i3 12100和i5 12400差距 i312100和i512400对比
- Mybatis中自动生成代码(利用mybatis-generator-core-1.3.2)
- 蓝桥杯2N皇后问题-使用Python实现
- 2022备受初学者推荐的8本高质量Python书籍(赠电子版)
- java走梅花桩_西非海牛吃上粤式团圆饭 留粤过年家庭齐捞“风生水起”
- 【logrotate】logrotate 的使用简介及遇到的问题排查
- android微信支付黑屏,【报Bug】本地打包的APP成功调起微信支付后点击返回APP出现卡死黑屏...
- mysql之group_concat函数详解
- 2022年湖南省导游资格(政策与法律法规)练习题及答案