java并发编程 | 线程详解
进程与线程
进程:操作系统在运行一个程序的时候就会为其创建一个进程(比如一个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());
}
}
}
未完待续...
最后,宣传一下我的群
欢迎Java工程师朋友们加入Java进阶高级架构:809389099
群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,
MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)
合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- django实战(五)--增加数据
- 实战django(一)--(你也能看懂的)注册与登录(带前端模板)
- 【python-leetcode23-多路归并】合并k个排序链表
- 实战django(二)--登录实现记住我
- org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.gong.mybatis.da
- 【python-leetcode378-二分查找】有序矩阵中的第k小元素
- 使用cookie来记录用户登录次数,为何次数不更新
- (二)golang--windows下vscode的安装以及go环境的配置
- mybatis文件映射之使用#取值时的一些规则
- 【论文笔记】Improved Residual Networks for Image and Video Recognition(ResNet新变体:IResNet)
- SQL语句在MYSQL中的运行过程和各个组件的介绍
- (五)golang--常用的一些玩意
- 关于MYSQL 的日志系统
- (六)golang--变量
- springmvc之文件上传