【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类详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.进程与线程的区别 简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程 ...
随机推荐
- C++面向对象-类和对象那些你不知道的细节原理
一.类和对象.this指针 OOP语言的四大特征是什么? 抽象 封装.隐藏 继承 多态 类体内实现的方法会自动处理为inline函数. 类对象的内存大小之和成员变量有关 类在内存上需要对齐,是为了减轻 ...
- linux篇-图解cacti监控安装
1登录 admin admin 2点击devices localhost 3进入配置保存 4保存 http服务要启动哦 5一步步做 6graph tree 7执行/usr/bin/php /var/w ...
- Cocos---简单案例:红气球
红气球 知识点 场景切换 动画播放,帧事件,Tween 按钮控件 音效管理 案例介绍 开始界面 点击按钮自动进入游戏界面 游戏界面 游戏目的找出红气球,如果点击红气球意味着游戏成功,其余意味着游戏失败 ...
- MVC 调试页面路径变成 Views/Controller/Action.cshtml问题
MVC在路由里面已经写好了路径,但是调试时地址栏还是会变成 Views/Controller/Action.cshtml,导致报404错误,找不到路径. 原因可能是你将某一页面设为了起始页,导致每次运 ...
- Flask 之 高可用IP代理网站
高可用代理IP网站 目标:提供高可用代理IP 步骤一:通过爬虫获取代理IP 步骤二:对代理IP进行检测,判断代理是否可用 步骤三:将可用的代理IP写入mongodb数据库 步骤四:创建网站,从数据库获 ...
- 在项目中如何直接使用hystrix?
一.背景 最近由于一些背景原因,需要在项目中需要对接口进行限流.所以就考虑到了直接使用Hystrix.但是呢,又不想直接使用SpringCloud,而是直接引入原生,现在发现挺好用的,所以记录下来,分 ...
- MASA Auth - 从用户的角度看整体设计
用户 在系统里,用户是一个核心概念.它代表了一个人的唯一身份标识,除了与角色.团队.组织架构等有关,甚至还会影响到在同一个界面不同的用户操作流程与显示内容都会发生变化,再复杂一点的话,或许在同一个系统 ...
- Java系列之运算符
运算符 算术运算符:+ (加).-(减) .*(乘)./(除).%(模).++(自增) . --(自减) 赋值运算符:= 关系运算符:>.<.>= <= == != insta ...
- 你真的了解git的分支管理跟其他概念吗?
现在前端要学的只是太多了,你是不是有时会有这个想法,如果我有两个大脑.一个学Vue,一个学React,然后到最后把两个大脑学的知识再合并在一起,这样就能省时间了. 哈哈,这个好像不能实现.现实点吧!年 ...
- DAST 黑盒漏洞扫描器 第六篇:运营篇(终)
0X01 前言 转载请标明来源:https://www.cnblogs.com/huim/ 当项目功能逐渐成熟,同时需要实现的是运营流程和指标体系建设.需要工程化的功能逐渐少了,剩下的主要工作转变成持 ...