【Java中的线程】java.lang.Thread 类分析
进程和线程
联想一下现实生活中的例子--烧开水,烧开水时是不是不需要在旁边守着,交给热水机完成,烧开水这段时间可以去干一点其他的事情,例如将衣服丢到洗衣机中洗衣服。这样开水烧完,衣服洗的也差不多了。这样既能喝到热水,衣服也差不多了。
操作系统中将多个程序(例如上面的热水机进程和洗衣机进程)同时进行、交替执行的称之为进程。
那么操作系统为什么需要进程。
通常,操作系统进行IO处理相比计算慢得多
通过Java 进行IO 和 运算测试,IO 的速率大概比运算慢200-400倍
而IO是不需要使用CPU的,这由IO设备完成。而正在运行的程序只能等待IO设备,这样会造成性能瓶颈。所以可以通过切换到其他任务来提高CPU的吞吐率。
以下仅考虑单核情况
那么当遇到IO操作时,操作系统需要切换进程。提醒OS切换的可以是中断,定时器,进程退出等外部事件。
如果有多个任务,应该选择哪一个来运行,这需要调度器来抉择。
以及如果两个进程需要进行合作例如需要读取检索当前目录并对该目录的文件名进行排序,这称之为进程间的通信(Inter Process Communication ,IPC)问题。这里只提及一下不做深入研究。
线程的概念和进程的概念很相似,那么有了进程为什么还要线程。
- 首先,每个进程都使用的是不同的地址空间,有了多进程之后可以使得多个并行的实体拥有了同一个地址空间。
- 其次,线程比进程更加轻量,它们比进程更快,更容易创建也更容易撤销。
- 最后,如果多个线程都是CPU密集型的(需要持续计算),那么不能获得性能上的增强,如果存在这大量的计算和 I/O处理,拥有多线程会加快程序执行的速度。
类比生活中,将一个面包店看作一个进程。那么里面的打蛋器,揉面机,烤炉等,这些看作它的线程。这些“线程”都在一个地址空间下(面包店),并且这些“线程”容易增删(相比于面包店,这些机器可以随时移走)。
揉面机到烤炉是需要等待的,那么这时可以切换另外一个烤炉,这样多线程的优势就出来了。
那么如何知道烤炉什么时候完成,这时候就需要知道烤炉的状态,有了状态就能够管理到每个进程。
OS中使用了状态来管理进程,同样的线程也有状态:

Java 中的多线程
Thread 是Java程序中的可执行线程,JVM允许多线程并发运行。每个线程都有一个优先级,具有较高优先级的线程优先执行,线程的优先级和创建该线程的线程相同。线程可以分为守护线程和普通线程,守护线程需要再运行之前指定,并且创建该线程的也必须是守护线程。
Thread具有生命周期(或者成为状态),可以通过一些方法来控制线程。
线程状态
Java 中的线程相应的也具有状态,和OS中的比较类似。Thread.State 表示了Thread的六种状态
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
| 状态名称 | 说明 |
|---|---|
| NEW | 新建状态,线程还未开始运行 |
| RUNNABLE | 可运行状态,操作系统中的就绪和运行两种状态 |
| BLOCKED | 阻塞状态,线程阻塞于锁 |
| WAITING | 等待状态,无期限等待 |
| TIME_WAITING | 超时等待状态,有限制时间的等待,等待一定时间之后可以自行返回 |
| TERMINATED | 终止状态,标识线程已经执行完毕 |
线程优先级
优先级高的线程会被先调度,Thread中定义了priority字段来表示线程的优先级:
private int priority;
// 以及priority的范围
// 最小优先级
public static final int MIN_PRIORITY = 1;
// 默认的优先级
public static final int NORM_PRIORITY = 5;
// 最大的优先级
public static final int MAX_PRIORITY = 10;
但需要注意,当前线程优先级是随着父线程优先级改变的,如果父线程优先级为1,子线程也为1。
根据OS的不同,优先级高的线程不一定比优先级底的线程优先调度。
真正的调度顺序是由OS和线程调度算法来决定的
线程的创建
线程有以下几个常用的构造方法:
Thread(Runnable target){...}
Thread(Runnable target, String name){...}
Thread(ThreadGroup group, Runnable target, String name){...}
可以指定要执行的代码,线程名称,线程组
下面是一个Demo
Thread thread = new Thread(() -> {
// ...
});
thread.setName("thread-1");
// 设置线程为守护线程
// thread.setDaemon(true);
thread.start ();
如果要将线程设置为守护线程,必须在start之前就设置好。
线程和线程组
通过线程组可以对线程进行批量控制,Java 中使用 ThreadGroup来表示线程组。
每个线程组中可以嵌套线程组,是一种包含关系。
如果在执行线程时没有指定线程组,那么默认将夫线程的线程组设置为自己的线程组。
ThreadGroup的结构是树形结构:
public
class ThreadGroup implements Thread.UncaughtExceptionHandler {
private final ThreadGroup parent;
Thread threads[];
ThreadGroup groups[];
}
parent 为上一级线程组,线程组可以包含线程和线程组。
ThreadGroup 可以中断、摧毁所有活跃的线程
// 通过调用destroy来摧毁所有线程
threadGroup.destroy()
// 通过调用interrupt来中断线程组中的所有线程
threadGroup.interrupt();
线程之间的通信
wait+notify
如果需要对多个线程进行同步操作,那么需要synchronized
那么当线程A获取锁后又释放锁,这些消息如何传达给其他线程。可以通过Object提供的以下方法:
notify 会随机唤醒一个等待的线程
notifyAll会唤醒所有正在等待的线程。
wait 会进入等待状态,直到被唤醒。
如果想让多个线程合作,可以通过wait+notify的形式
public static void main(String[] args) {
Object lock = new Object();
Runnable task = () -> {
for (int i = 0; i < 3; i++) {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " lock");
lock.notifyAll();
try {
lock.wait();
System.out.println(Thread.currentThread().getName() + " unlock"); k'|IOL
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
Thread t1 = new Thread(task,"t1");
Thread t2 = new Thread(task,"t2");
t1.start();
t2.start();
}
运行结果:
t1 lock
t2 lock
t1 unlock
t1 lock
t2 unlock
t2 lock
t1 unlock
t1 lock
t2 unlock
t2 lock
t1 unlock
wait 、notify、notifyAll 都是native
public final native void wait(long timeoutMillis) throws InterruptedException;
public final native void notify ();
public final native void notifyAll ();
信号量
信号量,使用一个整形变量来累计唤醒次数,信号量可以保证操作是原子的。
信号量类似于红绿灯,控制者线程什么时候可以工作,什么时候不可以工作。
如下代码所示,定义了信号量为2来表示同一时间只能唤醒两个线程。
但是创建了三个线程,所以有一个线程需要在其他线程睡眠或结束时才能被唤醒
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
Semaphore semaphore = new Semaphore(2);
Runnable r = ()->{
String name = Thread.currentThread().getName();
try{
System.out.println(name +" try to wake");
if(semaphore.tryAcquire(2, TimeUnit.SECONDS)){
System.out.println(name + " is awakened");
// do something
TimeUnit.MILLISECONDS.sleep(1000);
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
semaphore.release();
System.out.println(name+" waiting or terminated");
}
};
for (int i = 0; i < 3; i++) {
pool.execute(r);
}
pool.shutdown();
}
结果
pool-1-thread-1 try to wake
pool-1-thread-3 try to wake
pool-1-thread-1 is awakened
pool-1-thread-2 try to wake
pool-1-thread-3 is awakened
pool-1-thread-2 is awakened
pool-1-thread-1 waiting or terminated
pool-1-thread-3 waiting or terminated
pool-1-thread-2 waiting or terminated
join
join是Thread类的一个实例方法。作用是让当前线程等待,等目标线程完成自己再运行。
例如当前线程需要等待目标线程的结果,就可以使用join。
下面是一个小测试 -- 主线程需要使用到子线程的结果,需要等待子线程计算的结果,这个时候就可以用join:
private static volatile int data = 0;
public static void main(String[] args) throws InterruptedException {
Runnable r = ()->{
// to calculate
data = 1;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" is calculated");
};
Thread t1 = new Thread(r, "T1");
t1.start();
t1.join();
System.out.println("use "+ data);
}
下面是Thrad.join的实现,是一个同步的方法,使其能够保证线程安全,改变线程状态的还是使用的wait
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);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
sleep
sleep 是Thread类的一个静态方法。作用是让线程睡眠一会。
sleep 和 wait不同的是,前者不会释放锁而后者会。
sleep必须指定时间,而wait不用
sleep和wait都让出时间片,但是前者不释放锁,后者会,所以sleep更加容易造成死锁的情况。
线程异常处理
测试一段代码:
public static void main(String[] args) {
try {
new Thread(()->{
try {
int num = 1/0;
} catch (Exception e) {
e.printStackTrace();
}
}).start();
} catch (Exception e) {
System.out.println("catch by zero exception");
}
}
创建一个任务并在主线程中捕获异常,当运行后,并不能处理该异常。控制台并未打印出输出语句:
java.lang.ArithmeticException: / by zero
对线程A不能捕获线程B的异常做出的解释 https://www.zhihu.com/question/67790293
简答来说就是,线程之间是相互独立的。线程A不可能对线程B时时关照,因为线程A也有自己的事情需要做,如果B出问题了,那么A也会收到干扰,这对多线程来说是很糟糕的。
可以通过设置线程异常处理器来处理:
public static void main(String[] args) {
Thread thread = new Thread(() -> {
int num = 1 / 0;
});
thread.setUncaughtExceptionHandler((t, e) -> {
e.printStackTrace();
System.out.println("catch by zero exception");
});
thread.start();
}
这样就能够正常的处理异常了
java.lang.ArithmeticException: / by zero
...
catch by zero exception
UncaughtExceptionHandler 是Thread的内置注释接口,所有需要处理未被捕获异常的都需要实现改接口:
@FunctionalInterface
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}
创建线程只需要通过setUncaughtExceptionHandler去设置
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
checkAccess();
uncaughtExceptionHandler = eh;
}
【Java中的线程】java.lang.Thread 类分析的更多相关文章
- Java基础之线程——派生自Thread类的子类(TryThread)
控制台程序. 程序总是至少有一个线程,程序开始执行时就会创建这个线程.在普通的Java应用程序中,这个线程从mian()方法的开头启动. 要开始执行线程,可以调用Thread对象的start()方法. ...
- Java中守护线程的总结 thread.setDaemon(true)
https://www.cnblogs.com/ziq711/p/8228255.html 在Java中有两类线程:User Thread(用户线程).Daemon Thread(守护线程) 用个比较 ...
- Java中创建线程的三种方式以及区别
在java中如果要创建线程的话,一般有3种方法: 继承Thread类: 实现Runnable接口: 使用Callable和Future创建线程. 1. 继承Thread类 继承Thread类的话,必须 ...
- Java 线程--继承java.lang.Thread类实现线程
现实生活中的很多事情是同时进行的,Java中为了模拟这种状态,引入了线程机制.先来看线程的基本概念. 线程是指进程中的一个执行场景,也就是执行流程,进程和线程的区别: 1.每个进程是一个应用程序,都有 ...
- java.lang.Thread类详解
java.lang.Thread类详解 一.前言 位于java.lang包下的Thread类是非常重要的线程类,它实现了Runnable接口,今天我们来学习一下Thread类,在学习Thread类之前 ...
- Java中的线程Thread总结
首先来看一张图,下面这张图很清晰的说明了线程的状态与Thread中的各个方法之间的关系,很经典的! 在Java中创建线程有两种方法:使用Thread类和使用Runnable接口. 要注意的是Threa ...
- Java中的线程
http://hi.baidu.com/ochzqvztdbabcir/item/ab9758f9cfab6a5ac9f337d4 相濡以沫 Java语法总结 - 线程 一 提到线程好像是件很麻烦很复 ...
- Java并发编程(三)Thread类的使用
一.线程的状态 线程从创建到最终的消亡,要经历若干个状态.一般来说,线程包括以下这几个状态:创建(new).就绪(runnable).运行(running).阻塞(blocked).time wait ...
- Java基础-进程与线程之Thread类详解
Java基础-进程与线程之Thread类详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.进程与线程的区别 简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程 ...
随机推荐
- CesiumJS 2022^ 原理[5] - 着色器相关的封装设计
目录 1. 对 WebGL 接口的封装 1.1. 缓冲对象封装 1.2. 纹理与采样参数封装 1.3. 着色器封装 1.4. 上下文对象与渲染通道 1.5. 统一值(uniform)封装 1.6. 渲 ...
- 聊一聊 HBase 是如何写入数据的?
hi,大家好,我是大D.今天继续了解下 HBase 是如何写入数据的,然后再讲解一下一个比较经典的面试题. Region Server 寻址 HBase Client 访问 ZooKeeper: 获取 ...
- Typora 开始收费,改用好玩的MarkText
收费-- 可以考虑使用:MarkText 简述MarkText MarkText 这个工具侧重于"命令",导航栏都被收起来了.有些小伙伴感觉反而不好用,其实不然,是未了解该工具的强 ...
- Fail2ban 命令详解 fail2ban-server
Fail2ban的服务端操作命令,用于启动一个Fail2ban服务. root@local:~# fail2ban-server --help Usage: /usr/bin/fail2ban-ser ...
- SPPNet(特征金字塔池化)学习笔记
SPPNet paper:Spatial pyramid pooling in deep convolutional networks for visual recognition code 首先介绍 ...
- 基于云服务MRS构建DolphinScheduler2调度系统
摘要:本文介绍如何搭建DolphinScheduler并运行MRS作业. 本文分享自华为云社区<基于云服务MRS构建DolphinScheduler2调度系统>,作者: 啊喔YeYe . ...
- 多态——JavaSE基础
多态 同一个方法可以根据对象的不同采取不同的动作 一个对象的实际类型是确定的,但可以指向对象的引用类型有很多 基本条件: 有继承关系 子类重写父类方法 父类引用指向子类对象Father f1 = ne ...
- Hyperledger Fabric 智能合约开发及 fabric-sdk-go/fabric-gateway 使用示例
前言 在上个实验 Hyperledger Fabric 多组织多排序节点部署在多个主机上 中,我们已经实现了多组织多排序节点部署在多个主机上,但到目前为止,我们所有的实验都只是研究了联盟链的网络配置方 ...
- SAP Tree editor(树形结构)
SAP List Tree 效果 源代码 *&---------------------------------------------------------------------* *& ...
- js 循环生成元素,并为元素添加click事件,结果只执行最后一个点击事件
问题描述:有一个参数集合data,for循环为每一个参数生成一个dom元素,并附加onclick事件.生成之后发现点击事件里的参数全是data集合里的最后一个. 代码如下: var dom=$('#d ...