java并发编程 | 线程详解
个人网站:https://chenmingyu.top/concurrent-thread/
进程与线程
进程:操作系统在运行一个程序的时候就会为其创建一个进程(比如一个java程序),进程是资源分配的最小单位,一个进程包含多个线程
线程:线程是cpu调度的最小单位,每个线程拥有各自的计数器,对战和局部变量等属性,并且能过访问共享的内存变量
线程的状态
java线程的生命周期总共包括6个阶段:
- 初始状态:线程被创建,但是还没有调用
start()方法 - 运行状态:java中将就绪状态和运行状态统称为运行状态
- 阻塞状态:线程阻塞,线程等待进入
synchronized修饰的代码块或方法 - 等待状态:线程进入等待状态,需要调用
notify()或notifyAll()进行唤醒 - 超时等待状态:线程进入等待状态,在指定时间后自行返回
- 终止状态:线程执行完毕
在某一时刻,线程只能处于其中的一个状态
线程初始化后,调用start()方法变为运行状态,调用wait(),join()等方法,线程由运行状态变为等待状态,调用notify()或notifyAll()等方法,线程由等待状态变成运行状态,超时等待状态就是在等待状态基础上加了时间限制,超过规定时间,自动更改为运行状态,当需要执行同步方法时,如果没有获得锁,这时线程状态就变为阻塞状态,直到获取到锁,变为运行状态,当执行完线程的run()方法后,线程变为终止状态
创建线程
创建线程有三种方式
- 继承
Thread类 - 实现
Runnable接口 - 实现
Callable接口
继承Thread类
/**
* @author: chenmingyu
* @date: 2019/4/8 15:13
* @description: 继承Thread类
*/
public class ThreadTest extends Thread{
@Override
public void run() {
IntStream.range(0,10).forEach(i->{
System.out.println(this.getName()+":"+i);
});
}
public static void main(String[] args) {
Thread thread = new ThreadTest();
thread.start();
}
}
实现Runnable接口
/**
* @author: chenmingyu
* @date: 2019/4/8 15:18
* @description: 实现Runnable接口
*/
public class RunnableTest implements Runnable {
@Override
public void run() {
IntStream.range(0,10).forEach(i->{
System.out.println(Thread.currentThread().getName()+":"+i);
});
}
public static void main(String[] args) {
Runnable runnable = new RunnableTest();
new Thread(runnable,"RunnableTest").start();
}
}
实现Callable接口
/**
* @author: chenmingyu
* @date: 2019/4/8 15:23
* @description: 实现Callable接口
*/
public class CallableTest implements Callable<Integer> {
@Override
public Integer call() throws Exception {
IntStream.range(0,10).forEach(i->{
System.out.println(Thread.currentThread().getName()+":"+i);
});
return -1;
}
public static void main(String[] args) throws Exception {
Callable callable = new CallableTest();
FutureTask futureTask = new FutureTask(callable);
new Thread(futureTask,"future").start();
System.out.println("result:"+futureTask.get());
}
}
线程间通信
不安全的线程暂停,恢复,停止操作
Thread提供的过期方法可以实现对线程进行暂停suspend(),恢复resume(),停止stop()的操作
例:创建一个线程,run()中循环输出当前时间,在main()方法中对新建线程进行暂停,恢复,停止的操作
/**
* @author: chenmingyu
* @date: 2019/4/8 15:51
* @description: 线程的暂停,恢复,停止
*/
public class OperationThread implements Runnable{
@Override
public void run() {
while (true){
try {
TimeUnit.SECONDS.sleep(1L);
System.out.println(Thread.currentThread().getName()+"运行中:"+LocalTime.now());
}catch (InterruptedException e){
System.err.println(e.getMessage());
}
}
}
public static void main(String[] args) throws Exception{
Runnable runnable = new OperationThread();
Thread thread = new Thread(runnable,"operationThread");
/**
* 启动,输出当前时间
*/
thread.start();
TimeUnit.SECONDS.sleep(3L);
/**
* 线程暂停,不在输出当前时间
*/
System.out.println("此处暂停:"+LocalTime.now());
thread.suspend();
TimeUnit.SECONDS.sleep(3L);
/**
* 线程恢复,继续输出当前时间
*/
System.out.println("此处恢复:"+LocalTime.now());
thread.resume();
TimeUnit.SECONDS.sleep(3L);
/**
* 线程停止,不在输出当前时间
*/
thread.stop();
System.out.println("此处停止:"+LocalTime.now());
TimeUnit.SECONDS.sleep(3L);
}
}
输出
因为是过期方法,所以不推荐使用,使用suspend()方法后,线程不会释放已经占有的资源,就进入睡眠状态,容易引发死锁问题,而使用stop()方法终结一个线程是不会保证线程的资源正常释放的,可能会导致程序异常
安全的线程暂停,恢复(等待/通知机制)
线程安全的暂停,恢复操作可以使用等待/通知机制代替
安全的线程暂停,恢复(等待/通知机制)
相关方法:
| 方法名 | 描述 |
|---|---|
| notify() | 通知一个在对象上等待的线程,使其重wait()方法中返回,前提是该线程获得了对象的锁 |
| notifyAll() | 通知所有等待在该对象上的线程 |
| wait() | 调用该方法线程进入等待状态,只有等待另外线程的通知或被中断才会返回,调用该方法会释放对象的锁 |
| wait(long) | 超时等待一段时间(毫秒),如果超过时间就返回 |
| wait(long,int) | 对于超时时间耕细粒度的控制,可以达到纳秒 |
例:创建一个名为waitThread的线程,在run()方法,使用中使用synchronized进行加锁,以变量flag为条件进行while循环,在循环中调用LOCK.wait()方法,此时会释放对象锁,由main()方法获得锁,调用LOCK.notify()方法通知LOCK对象上等待的waitThread线程,将其置为阻塞状态,并将变量flag置为true,当waitThread线程再次获取对象锁之后继续执行余下代码
/**
* @author: chenmingyu
* @date: 2019/4/8 20:00
* @description: wait/notify
*/
public class WaitNotifyTest {
private static Object LOCK = new Object();
private static Boolean FLAG = Boolean.TRUE;
public static void main(String[] args) throws InterruptedException{
Runnable r = new WaitThread();
new Thread(r,"waitThread").start();
TimeUnit.SECONDS.sleep(1L);
synchronized (LOCK){
System.out.println(Thread.currentThread().getName()+"唤醒waitThread线程:"+LocalTime.now());
/**
* 线程状态由等待状态变为阻塞状态
*/
LOCK.notify();
/**
* 只有当前线程释放对象锁,waitThread获取到LOCK对象的锁之后才会从wait()方法中返回
*/
TimeUnit.SECONDS.sleep(2L);
FLAG = Boolean.FALSE;
}
}
public static class WaitThread implements Runnable {
@Override
public void run() {
/**
* 加锁
*/
synchronized (LOCK){
while (FLAG){
try {
System.out.println(Thread.currentThread().getName()+"运行中:"+LocalTime.now());
/**
* 线程状态变为等待状态
*/
LOCK.wait();
/**
* 再次获得对象锁之后,才会执行
*/
System.out.println(Thread.currentThread().getName()+"被唤醒:"+LocalTime.now());
}catch (InterruptedException e){
System.err.println(e.getMessage());
}
}
}
System.out.println(Thread.currentThread().getName()+"即将停止:"+LocalTime.now());
}
}
}
输出
可以看到在mian线程调用LOCK.notify()方法后,沉睡了2s才释放对象锁,waitThread线程在获得对象锁之后执行余下代码
安全的线程停止操作(中断标识)
线程的安全停止操作是利用线程的中断标识来实现,线程的中断属性表示一个运行中的线程是否被其他线程进行了中断操作,其他线程通过调用该线程的interrupt()方法对其进行中断操作,而该线程通过检查自身是否被中断来进行响应,当一个线程被中断可以使用Thread.interrupted()方法对当前线程的中断标识位进行复位
例:新建一个线程,run方法中使用Thread.currentThread().isInterrupted()是否中断作为判断条件,在主线程中使用thread.interrupt()方法对子线程进行中断操作,用来达到终止线程的操作,这种方式会让子线程可以去清理资源或一些别的操作,而使用stop()方法则会会直接终止线程
/**
* @author: chenmingyu
* @date: 2019/4/8 20:47
* @description: 中断
*/
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
Runnable r = new StopThread();
Thread thread = new Thread(r,"stopThread");
thread.start();
TimeUnit.SECONDS.sleep(1L);
System.out.println(Thread.currentThread().getName()+"对stopThread线程进行中断:"+LocalTime.now());
thread.interrupt();
}
public static class StopThread implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName()+"运行中:"+LocalTime.now());
}
System.out.println(Thread.currentThread().getName()+"停止:"+LocalTime.now());
}
}
}
Thread.join()
Thread.join()作用是等待该线程终止
比如在主线程中新建一个子线程,调用子线程的join()方法,那么在子线程未执行完时,主线程的状态是阻塞状态,只有当子线程执行结束,主线程才会继续往下执行
| 方法名 | 作用 |
|---|---|
| join() | 调用A线程的join()方法后,那么当前线程需要等待A线程终止,才可以继续执行 |
| join(long) | 在join()方法的基础上增加了时间限制(毫秒),超出时间后,无论A线程是否执行完,当前线程都进入就绪状态,重新等待cpu调用 |
| join(long,int) | 在join(long)方法基础上,时间控制上更加严谨,时间细粒度为纳秒(Long毫秒+int纳秒) |
例:循环创建子线程,在main线程中调用子线程的join()方法,在子线程中输出了一句日志
/**
* @author: chenmingyu
* @date: 2019/4/9 20:53
* @description: thread.join();
*/
public class JoinThreadTest {
public static void main(String[] args) throws InterruptedException{
IntStream.range(0, 5).forEach(i -> {
try {
Runnable runnable = new JoinThread();
Thread thread = new Thread(runnable,"joinThread");
thread.start();
thread.join();
System.out.println(Thread.currentThread().getName() + "运行中: " + LocalTime.now());
} catch (InterruptedException e) {
System.err.println(e.getMessage());
}
System.out.println("------- 分隔符 ------- ");
});
}
public static class JoinThread implements Runnable {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1L);
System.out.println(Thread.currentThread().getName() + "运行中: " + LocalTime.now());
} catch (InterruptedException e) {
System.err.println(e.getMessage());
}
}
}
}
输出
每次循环都是主线程等待子线程终止,在子线程执行完之后主线程才会继续执行
thread.join()源码
调用方线程(调用join方法的线程)执行等待操作,直到被调用的线程(join方法所属的线程)结束,再被唤醒
public final void join() throws InterruptedException {
join(0);
}
join(0)方法
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;
}
}
}
join()方法实现等待其实是调用了wait()方法,isAlive()方法的作用是监测子线程是否终止,如果终止或者超过了指定时间,代码就继续往下执行,否则就继续等待,知道条件满足
ThreadLocal
ThreadLocal,叫线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构,ThreadLocal 类型的变量在每个线程中是独立的,在多线程环境下不会相互影响
/**
* @author: chenmingyu
* @date: 2019/4/10 17:56
* @description: ThreadLocal
*/
public class ThreadLocalTest {
private static String STATE;
private static ThreadLocal<String> STRING_THREAD_LOCAL = new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException{
STATE = "未重置";
STRING_THREAD_LOCAL.set("未重置");
Thread thread = new Thread(() ->
{
STATE = "已重置";
STRING_THREAD_LOCAL.set("已重置");
System.out.println(Thread.currentThread().getName() + " : 变量已重置");
});
thread.start();
thread.join();
System.out.println(Thread.currentThread().getName() + "STATE : " + STATE);
System.out.println(Thread.currentThread().getName() + "STRING_THREAD_LOCAL : " + STRING_THREAD_LOCAL.get());
}
}
输出
ThreadLocal<String>类型的变量STRING_THREAD_LOCAL未被子线程修改
参考:java并发编程的艺术
java并发编程 | 线程详解的更多相关文章
- java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock
原文:java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock 锁 锁是用来控制多个线程访问共享资源的方式,java中可以使用synch ...
- Java并发编程--Volatile详解
摘要 Volatile是Java提供的一种弱同步机制,当一个变量被声明成volatile类型后编译器不会将该变量的操作与其他内存操作进行重排序.在某些场景下使用volatile代替锁可以减少 ...
- Java并发编程(详解wait(), notify(),sleep())
http://blog.csdn.net/luckyzhoustar/article/details/48179161
- Java 并发编程 | 线程池详解
原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...
- Java并发关键字Volatile 详解
Java并发关键字Volatile 详解 问题引出: 1.Volatile是什么? 2.Volatile有哪些特性? 3.Volatile每个特性的底层实现原理是什么? 相关内容补充: 缓存一致性协议 ...
- java并发编程 线程基础
java并发编程 线程基础 1. java中的多线程 java是天生多线程的,可以通过启动一个main方法,查看main方法启动的同时有多少线程同时启动 public class OnlyMain { ...
- Java并发编程:线程间通信wait、notify
Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...
- Java并发编程:线程和进程的创建(转)
Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...
- java网络编程(TCP详解)
网络编程详解-TCP 一,TCP协议的特点 面向连接的协议(有发送端就一定要有接收端) 通过三次连接握手建立连接 通过四次握手断开连接 基于IO流传输数据 传输数据大小 ...
随机推荐
- Redis入门简述
Redis 是一个开源的,使用 ANSI C (C语言)编写,高性能的 Key-Value(键值对) 的 NoSQL 数据库 NoSQL = Not Only SQL,意即“不仅仅是SQL”,是一项全 ...
- java基础(二):谈谈Java基本数据结构
数据结构是计算机存储,组织数据的方式.数据结构是指相互之间存在一种或多种特定关系的数据元素的集合.通常情况下,精心选择的数据结构可以带来更高的运行或存储效率.数据结构往往同高效的检索算法和索引技术有关 ...
- odoo12 物流 自动计算运费 ,采购销售使用不同计量单位自动换算
在物流和食品生鲜类行业,重量是很重要的因素,不仅要处理产品的数量,也要处理产品的重量.在多数行业,重量也是订单的重要数据. odoo原生包含了对重量的处理,下文中将指导如何优雅应用odoo原生模块处理 ...
- MyBatis Generator 生成器把其他数据库的同名表生成下来的问题
[问题] 使用MyBatis Generator生成器时,发现Mapper文件中出现字段与连接数据库不符,经过查找发现该表是其他数据库的同名表的字段. [解决问题] 在构造文件中,这里是generat ...
- C++ 子类继承父类纯虚函数、虚函数和普通函数的区别
C++三大特性:封装.继承.多态,今天给大家好好说说继承的奥妙 1.虚函数: C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现.子类可以重写父类的虚函数实现子类 ...
- Django-1- urls的路由配置, 命名, 重定向, path及re_path
1-项目目录下配置(path方法) 这里的访问路径可以是127.0.0.1:8000/index/ 2-APP目录下配置(path方法) 这里的访问路径可以是127.0.0.1:8000/t ...
- C#自定义控件添加到工具箱
1.在需要使用这个自定义控件的项目中,在引用中,右键,添加引用,浏览,找到dll(如:ComButton.dll),进行添加,这时候,控件应该是能够自动添加到工具箱的. 2.在Visual Studi ...
- uboot的启动过程-FDT
uboot的启动过程,省略了汇编部分之后,第一个执行函数是board_init_f(),在uboot/common目录的board_f.c中 board_init_f函数,首先初始化了全局数据 # ...
- Scrapy框架-CrawlSpider
目录 1.CrawlSpider介绍 2.CrawlSpider源代码 3. LinkExtractors:提取Response中的链接 4. Rules 5.重写Tencent爬虫 6. Spide ...
- 深入研究 Mini ASP.NET Core(迷你 ASP.NET Core),看看 ASP.NET Core 内部到底是如何运行的
前言 几年前,Artech 老师写过一个 Mini MVC,用简单的代码告诉读者 ASP.NET MVC 内部到底是如何运行的.当时我研究完以后,受益匪浅,内心充满了对 Artech 老师的感激,然后 ...