【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#] MEF 主程序与插件加载不同版本的DLL
一.前言 MEF(Managed Extensibility Framework),是轻量级的插件框架.使用简单,功能强大.详细介绍见MSDN,本文不再赘述. 在使用MEF时,会遇到这样一种场景: 主 ...
- requests入门
1.通过GET请求获得搜索结果的网页源代码 import requests name=input("请输入想要搜索的明星:") url=f'https://www.sogou.co ...
- 使用多线程提高REST服务器性能
异步处理REST服务 1.使用Runnable异步处理Rest服务 释放主线程,启用副线程进行处理,副线程处理完成后直接返回请求 主要代码 import java.util.concurrent.Ca ...
- php 使用phpqrcode生成二维码并上传到OSS
一般情况调用phpqrcode第三方插件 会把生成的二维码图片保存到服务器,不保存服务器也会以header头的形式输出到浏览器,(我们不允许把图片文件保存的liunx服务器,只能保存到阿里云OSS存储 ...
- 从局部信息推测基恩士的Removing BackGround Information算法的实现。
最近从一个朋友那里看到了一些基恩士的资料,本来是想看下那个比较有特色的浓淡补正滤波器的(因为名字叫Shading Correction Filter,翻译过来的意思有点搞笑),不过看到起相关文档的附近 ...
- WAVE音频格式及及转换代码
音频信号的读写.播放及录音 python已经支持WAV格式的书写,而实时的声音输入输出需要安装pyAudio(http://people.csail.mit.edu/hubert/pyaudio).最 ...
- 隐私计算FATE-模型训练
一.说明 本文分享基于 Fate 自带的测试样例,进行 纵向逻辑回归 算法的模型训练,并且通过 FATE Board 可视化查看结果. 本文的内容为基于 <隐私计算FATE-概念与单机部署指南& ...
- Javaweb-Servlet学习
1.Servlet简介 Servlet就是sun公司开发动态web的一门技术 Sun在这些API中提供一个借口叫做:Servlet,如果你想开发一个Servlet程序,只需要完成两个小步骤: 编写一个 ...
- MySql查询日周月
常用计算日期的函数 日 date(日期) = CURDATE() 自然周 YEARWEEK(date_format(日期,'%Y-%m-%d') , 1) = YEARWEEK(now() , 1) ...
- 数据库系列:MySQL索引优化总结(综合版)
1 背景 作为一个常年在一线带组的Owner以及老面试官,我们面试的目标基本都是一线的开发人员.从服务端这个技术栈出发,问题的范围主要还是围绕开发语言(Java.Go)等核心知识点.数据库技术.缓存技 ...