1 线程状态
1 打印线程的所有状态
private static void printState() {
for(Thread.State item:Thread.State.values())
System.out.println(item);
}
NEW
RUNNABLE
BLOCKED
WAITING 等待
TIMED_WAITING 超时等待 有明确结束时间
TERMINATED
2 线程状态:
- new:新建状态,当线程被创建,但未启动(start()之前
-
runnable:运行状态
- 运行:得到时间片运行中状态
- 就绪:未得到时间片就绪状态 (保存了上下文
-
blocked:阻塞状态,如果
遇到锁
线程就会变为阻塞状态,等到另一个线程释放锁 -
waiting:
休眠
等待状态(无明确等待时间 -
timed_waiting:
休眠
等待状态(有明确结束时间 - terminated:销毁状态,线程结束之后会变成此状态
3 获取线程状态:
public static void main(String[] args) throws InterruptedException {
//printState();
Thread t1=new Thread(()->{
System.out.println("当前线程状态2:"+Thread.currentThread().getState());
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("当前线程状态:"+t1.getState());
t1.start();
//让主线程休眠1秒
Thread.sleep(1000);
System.out.println("当前线程状态3:"+t1.getState());
//等子线程执行完
t1.join();
System.out.println("当前线程状态4:"+t1.getState());
}
2 线程安全
1 线程安全问题:多线程执行环境下,程序执行结果和预期不符;
2 导致线程不安全的原因:
-
抢占式执行(狼多肉少
-
多个线程同时修改同一个变量
static class Counter{
private int number=0;
private int MAX_COUNT=0;
public Counter(int MAX_COUNT){
this.MAX_COUNT=MAX_COUNT;
}
//++方法
public void increment(){
for (int i = 0; i < MAX_COUNT; i++) {
number++;
}
}
public void decrement(){
for (int i = 0; i < MAX_COUNT; i++) {
number--;
}
}
public int getNumber(){
return number;
}
}
public static void main(String[] args) throws InterruptedException {
Counter c=new Counter(100000);
Thread th=new Thread(()->{
c.increment();
});
Thread th1=new Thread(()->{
c.decrement();
});
// c.increment();单线程时++——没问题
// c.decrement();
//启动多线程进行执行
th.start();
th1.start();
//等待两个线程执行完
th.join();
th1.join();
System.out.println("最终结果"+ c.getNumber());
}
结果应该是0
最终结果8100
换种方式 使用全局变量接受结果后 结果为0
static class Counter{
private int number=0;
private int MAX_COUNT=0;
public Counter(int MAX_COUNT){
this.MAX_COUNT=MAX_COUNT;
}
//++方法
public int increment(){
int tep=0;
for (int i = 0; i < MAX_COUNT; i++) {
// number++;
tep++;
}
return tep;
}
public int decrement(){
int tep=0;
for (int i = 0; i < MAX_COUNT; i++) {
// number--;
tep--;
}
return tep;
}
public int getNumber(){
return number;
}
}
//全局变量接收线程执行结果
static int n1=0;
static int n2=0;
public static void main(String[] args) throws InterruptedException {
Counter c=new Counter(100000);
Thread th=new Thread(()->{
n1 = c.increment();
});
// th.start(); th.join();
Thread th1=new Thread(()->{
n2=c.decrement();
});
// c.increment();
// c.decrement();
th.start();
th1.start();
th.join();
th1.join();
// System.out.println("最终结果"+ c.getNumber());
System.out.println("最终结果"+ (n1+n2));
}
}
0
-
操作是非原子性操作(tep++ 1.查询当前tep的值 2.tep-1操作 3.刷新tep的最新值
1 线程从主内存读出最新的值Value
2 放到线程的虚拟机栈的栈帧中的操作栈先压入,再弹出,供CPU计算+1,计算完再压入再弹出
3 线程新值只写回到内存中
- 内存可见性问题
是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够立即看到发生的状态变化。 由于线程之间的交互都发生在主内存中,但对于变量的修改又发生在自己的工作内存中,经常会造成读写共享变量的错误,我们也叫可见性错误。
使用 volatile解决内存可见性问题(也可解决指令重排序问题
package ThreadSafe;
import java.time.LocalDateTime;
/**
* 内存可见性问题
*
*/
public class ThreadSafe1 {
private static volatile boolean flag=true;
public static void main(String[] args) {
//创建子线程
Thread t1=new Thread(()->{
System.out.println("线程1:开始执行"+ LocalDateTime.now());
while(flag){
}
System.out.println("线程1:执行结束"+LocalDateTime.now());
});
t1.start();
Thread t2=new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2修改flag=false"+LocalDateTime.now());
flag=false;
});
t2.start();
}
}
/**
* 线程1:开始执行2022-04-03T10:40:54.170
* 线程2修改flag=false2022-04-03T10:40:55.126
*/
JMM Java Memory Model (JAVA 内存模型 ):
是描述线程之间如何通过内存来进行交互。 具体说来, JVM中存在一个主存区(Main Memory或Java Heap Memory),对于所有线程进行共享,而每个线程又有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作并非发生在主存区,而是发生在工作内存中,而线程之间是不能直接相互访问,变量在程序中的传递,是依赖主存来完成的。其⽬的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到⼀致的并发效果.
Java内存模型的抽象示意图如下
-
指令重排序:编译或运行指令重排序(”jvm自作聪明(优化)“)
编译器优化的本质是调整代码的执⾏顺序,在单线程下没问题,但在多线程下容易出现混乱,从⽽造成
线程安全问题。
3 解决线程安全问题的手段:
1 使用Volatile解决内存可见性问题和指令重排序问题
-
代码在写⼊ volatile 修饰的变量的时候:
改变线程⼯作内存中volatile变量副本的值,将改变后的副本的值从⼯作内存刷新到主内存
-
代码在读取 volatile 修饰的变量的时候:
从主内存中读取volatile变量的最新值到线程的⼯作内存中,从⼯作内存中读取volatile变量的副本
-
缺点:
volatile 虽然可以解决内存可⻅性和指令重排序的问题,但是解决不了原⼦性问题,因此对于 ++ 和 –操作的线程⾮安全问题依然解决不了
4 使用锁是java中解决线程安全问题最主要的手段:
-
内存锁:synchronized
-
可重入锁:ReentrantLock
5 synchronized基本用法(非公平锁 互斥锁 可重入锁):
1 修饰静态方法:
public class TreadDemo_synchronized {
private static int number=0;
static class Counter{
private static int MAX_COUNT=200000;
//++方法
public synchronized static void increment(){
for (int i = 0; i < MAX_COUNT; i++) {
number++;
}
}
public synchronized static void decrement(){
for (int i = 0; i < MAX_COUNT; i++) {
number--;
}
}
public static int getNumber(){
return number;
}
}
public static void main(String[] args) throws InterruptedException {
Thread th=new Thread(()->{
Counter.increment();
});
th.start();
Thread th1=new Thread(()->{
Counter.decrement();
});
th1.start();
th.join();th1.join();
System.out.println("最终结果"+ number);
}
}
2 修饰普通方法:
package ThreadSafe;
public class TreadDemo_synchronized2 {
private static int number=0;
static class Counter{
private int MAX_COUNT=0;
public Counter(int MAX_COUNT){
this.MAX_COUNT=MAX_COUNT;
}
//++方法
public synchronized void increment(){
for (int i = 0; i < MAX_COUNT; i++) {
number++;
}
}
public synchronized void decrement(){
for (int i = 0; i < MAX_COUNT; i++) {
number--;
}
}
public int getNumber(){
return number;
}
}
public static void main(String[] args) throws InterruptedException {
Counter c=new Counter(100000);
Thread th=new Thread(()->{
c.increment();
});
th.start();
Thread th1=new Thread(()->{
c.decrement();
});
th1.start();
th.join();th1.join();
System.out.println("最终结果"+ number);
}
}
3 修饰代码块:
-
修饰类:synchronized(类名.class)
-
修饰类实例:synchronized(this)
public class ThreadSynchronized3 {
private static int number = 0;
static class Counter {
private static final int count = 100000;
// 自定义锁对象
final Object myLock = new Object();
// ++方法
public void increase() {
for (int i = 0; i < count; i++) {
synchronized (myLock) {
number++;
}
}
}
// --方法
public void desc() {
for (int i = 0; i < count; i++) {
synchronized (myLock) {
number--;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread thread1 = new Thread(counter::increase);
thread1.start();
Thread thread2 = new Thread(counter::desc);
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终结果:" + number);
}
}
注意事项:对于同一个业务的多个线程加锁对象,一定要是
同一个对象加同一把锁
public class ThreadSynchronized {
private static final int count = 100000;
static int num = 0;
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Object obj2 = new Object();
Thread t1 = new Thread(() -> {
synchronized (obj) {
for (int i = 0; i < count; i++) {
num++;
}
}
}, "线程1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (obj2) {
for (int i = 0; i < count; i++) {
num--;
}
}
}, "线程2");
t2.start();
t1.join();
t2.join();
System.out.println("最终执⾏结果:" + num);
}
}
6 syntronized特性:
1 互斥性(排他性
synchronized 会起到互斥效果, 某个线程执⾏到某个对象的 synchronized 中时, 其他线程如果也
执⾏到同⼀个对象 synchronized 就会阻塞等待.
- 进⼊ synchronized 修饰的代码块, 相当于 加锁
- 退出 synchronized 修饰的代码块, 相当于 解锁
2 刷新内存(内存可见性问题
3 synchronized 的⼯作过程:
- 获得互斥锁
- 从主内存拷⻉变量的最新副本到⼯作的内存
- 执⾏代码
- 将更改后的共享变量的值刷新到主内存
- 释放互斥锁
4 可重入:
synchronized⽤的锁是存在Java对象头⾥的,里面有是否加锁的标志,以及拥有当前🔒的id
- 查询当前对象头是否加锁
- 判断隐藏对象头中的线程id是否等于当前线程的id
public class ThreadDemo_synchronized5 {
public static void main(String[] args) {
synchronized( ThreadDemo_synchronized5.class){
System.out.println("当前主线程获得了🔒");
a b: (true) synchronized( ThreadDemo_synchronized5.class){
System.out.println("当前主线程再次获得🔒");
}
}
}
}
7 synchronized实现原理:
保证任何时候只有
一个线程能够执行指定区域的代码
1 jvm层面依靠
监视器Monitor
实现(使用了一个对象的隐藏对象头 里面的两个a,b属性实现)
-
jdk1.6之前使用比较少(默认重量级锁实现,所以性能较差)
-
jdk1.6优化了
无锁(没有线程)——>偏向锁(一个线程)——>轻量级锁(少量线程)——>重量级锁(较多线程 锁升级)
2 从操作系统的层面实现,基于操作系统的互斥锁(Mutax)
8 公平锁和非公平锁的区别:
1 公平锁一定要执行的步骤:
- 上一个线程释放线程后执行唤醒操作
- 自旋后 最前面阻塞休眠的线程 从阻塞状态切换为运行状态
2 非公平锁:来得早不如来得巧
9 ReentrantLock
1 Lock的实现步骤:
// 1.创建锁对象
Lock lock=new ReentrantLock();
// 2.加锁操作
lock.lock();
try{
//业务代码(可能会非常复杂 -> 导致异常)
System.out.println("你好! ReentrantLock");
}finally {
// 3.释放锁
lock.unlock();
}
2 Lock注意事项:
-
释放锁的unlock() 操作一定要放在finally里面,若没有放在这里可能会导致永久占用资源问题
-
加锁操作lock() 一定要放在try之前,或者try首行
1. 未加锁却执行了释放锁的操作
2. 释放锁的错误信息会覆盖掉业务代码的报错信息,从而增加调试代码的复杂度 -
指定Lock类型:
-
非公平锁:默认会创建非公平锁,性能高
new ReentrantLock()
-
公平锁:
new ReentrantLock(true)
-
非公平锁:默认会创建非公平锁,性能高
10 synchronized VS Lock
- Lock 粒度可以更⼩ ,更灵活。有更多方法比如:tryLock();
- 锁类型不同:Lock默认是非公平锁,但可以指定为公平锁;synchronized只能为公平锁
- 调用lock方法和synchronized方法线程等待状态不同;lock -》waiting synchronized-》blocked
- Lock 需要⼿动操作锁,⽽ Synchronized 是 JVM ⾃动操作的
- Lock 只能修饰代码块,⽽ Synchronized 可以修饰⽅法、静态⽅法、代码块