Java中的线程 Krains 2020-08-24
Java内存模型
Java线程之间的通信由Java内存模型(简称JMM)控制,从抽象的角度来说,JMM定义了线程和主内存之间的抽象关系。
JMM的抽象示意图:
- 所有的共享变量都存在主内存中
- 每个线程都保存了一份该线程使用到的共享变量的副本
- 如果线程A与线程B之间要通信的话,必须经历下面两个步骤
- 线程A将本地内存A中更新过的共享变量刷新到主内存中
- 线程B到主内存中去读取线程A之前已经更新过的共享变量
那么怎么知道这个共享变量的被其他线程更新了呢?这就是JMM的功劳了,也是JMM存在的必要性之一。JMM通过控制主内存与每个线程的本地内存之间的交互,来提供内存可见性保证。
Java中的volatile关键字可以保证多线程操作共享变量的可见性以及禁止指令重排序,synchronized关键字不仅保证可见性,同时也保证了原子性(互斥性)。在更底层,JMM通过内存屏障来实现内存的可见性以及禁止重排序。为了程序员的方便理解,提出了happens-before,它更加的简单易懂,从而避免了程序员为了理解内存可见性而去学习复杂的重排序规则以及这些规则的具体实现方法。
内存模型的三大特性(如何保证?待补充)
- 原子性
- 可见性
- 有序性
synchronized能够保证三大特性,volatile能够保证可见性和有序性
Java线程生命周期
Java线程生命周期与操作系统中的进程生命周期定义有所不同。
状态名称 |
说明 |
---|---|
NEW |
初始状态,Thread对象被创建,但是还没有调用start()方法 |
RUNNABLE |
运行状态,Java中将运行和就绪态统称为运行态 |
BLOCKED |
阻塞状态,线程获取不到锁资源而进入阻塞状态 |
WAITING |
等待状态,进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断) |
TIME_WAITING |
超时等待状态,不同于等待状态,可以在指定的时间自行返回 |
TERMINATED |
终止状态,表示当前线程已经执行完毕 |
纠错:左上角Object.join()应为Thread.join()
创建线程的三种方式
方法一:继承Thread,覆写run()方法
方法二:实现Runnable接口,然后交给Thread执行
例子:
@Test
public void test1(){
Thread t1 = new Thread("t1"){
@Override
public void run(){
System.out.println(1);
}
};
t1.start();
}
@Test
public void test2(){
// 使用Lambda接口简化类的创建
Runnable task = () -> System.out.println(2);
Thread t2 = new Thread(task, "t2");
t2.start();
}
区别
- 方法1把线程和任务合并在一起,方法2将两者分开了
- 用
Runnable
更容易与线程池等高级 API 配合 - 用
Runnable
让任务类脱离了Thread
继承体系,更灵活
方法三:FutureTask配合Thread,FutureTask 能够接收 Callable 类型的参数,Callable也是一个函数式接口,只有一个call()方法,创建好FutureTask任务交给Thread执行,它用来处理有返回结果的情况
使用例子:
// 创建任务对象,指定返回结果类型
FutureTask<String> task = new FutureTask<>(()->{
Thread.sleep(1000);
return "sss";
});
// 新建线程去执行任务
new Thread(task, "thread1").start();
// 调用者线程阻塞,直到task任务执行结束返回结果
String result = task.get();
System.out.println(result);
Thread类
常用方法
// 启动一个新线程运行run方法
start();
// 线程运行时的代码
run();
// 等待 调用 该方法的线程结束,当前线程才继续执行
join();
// 最多等待n毫秒
join(long n);
// 获取当前正在 执行 的线程
currentThread();
// 让当前执行的线程休眠n毫秒
sleep(n);
// 提示线程调度器让出当前线程对CPU的使用
yield();
// 打断线程,调用sleep、wait、join的线程会进入阻塞状态,可用该方法打断阻塞状态的线程,并抛出异常和清除打断标记
// 如果线程正在运行,打断标记为真
interrupt();
关于start()的两个引申问题
- 反复调用同一个线程的start()方法是否可行?
- 假如一个线程执行完毕(此时处于TERMINATED状态),再次调用这个线程的start()方法是否可行?
要分析这两个问题,我们先来看看start()的源码:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
看不到对threadStatus的修改,通过端点调试,两个问题的答案都是不可行,在调用一次start()之后,threadStatus的值会改变(threadStatus !=0),此时再次调用start()方法会抛出IllegalThreadStateException异常。
变量的线程安全分析
成员变量和静态变量是否线程安全?
- 如果它们没有共享,则线程安全
- 如果被共享了
- 对变量只有读操作,则线程安全
- 对变量有读写操作,则这段代码是临界区,需要考虑线程安全问题
局部变量是否线程安全?
- 局部变量是线程安全的,因为每个线程都创建了一份栈帧,局部变量存在局部变量表中,不是共享的
- 但局部变量引用的对象则未必,如果该对象逃离了方法的作用范围,则需要考虑线程安全问题。
参考链接
[1].http://concurrent.redspider.group/article/02/6.html
- java中的tuple实现
- MYSQL5.7开启慢查询日志
- 微信又是一次大更新,下拉多任务切换 各种有趣小游戏
- 人工智能有可能超越人类大脑?
- 一种简单的数据库性能测试方法
- xiaomao.com7位数高价成交,并已启用建站
- Docker基于已有的镜像制新的镜像-Docker for Web Developers(3)
- 如何通过Remoting实现双向通信
- jenkins 入门教程(上)
- 让jQuery Tools Scrollable控件在Mobile Web里面支持resize功能
- CentOS6.5上golang环境配置
- 马斯克频发推文,或在揭示特斯拉明年大动作?
- yum安装出现No package nodejs available解决办法
- InfoPath中repeationg section动态填充数据
- 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 文档注释
- PHP代码审计03之实例化任意对象漏洞
- 最简单入门深度学习
- Redis 字典结构细谈
- 终于弄明白 i = i++和 i = ++i 了
- 更简易的机器学习-pycaret的安装和环境初始化
- 直观讲解一下 RPC 调用和 HTTP 调用的区别!
- pycaret之训练模型(创建模型、比较模型、微调模型)
- 什么是递归,通过这篇文章,让你彻底搞懂递归
- pycaret之集成模型(集成模型、混合模型、堆叠模型)
- pycaret模型分析之绘制模型结果
- pycaret模型分析
- 用 Python 给自己的头像加一个小国旗(小月饼)
- pycaret之模型部署
- pyspark读取pickle文件内容并存储到hive
- Redis基础篇