线程状态
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());
}
线程安全
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());
}
最终结果8100
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的最新值

- 内存可见性问题
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
*/

java内存模型(jmm):⽬的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到⼀致的并发效果.
线程之间的共享变量存在 主内存 (Main Memory).
每⼀个线程都有⾃⼰的 “⼯作内存” (Working Memory) .(内存不可见)当线程要读取⼀个共享变量的时候, 会先把变量从主内存拷⻉到⼯作内存, 再从⼯作内存读取数据.
当线程要修改⼀个共享变量的时候, 也会先修改⼯作内存中的副本, 再同步回主内存.
指令重排序:编译或运行指令重排序(”jvm自作优化“
编译器优化的本质是调整代码的执⾏顺序,在单线程下没问题,但在多线程下容易出现混乱,从⽽造成
线程安全问题。
解决线程安全问题的手段:
1 使用Volatile解决内存可见性问题和指令重排序问题
代码在写⼊ volatile 修饰的变量的时候:
改变线程⼯作内存中volatile变量副本的值,将改变后的副本的值从⼯作内存刷新到主内存
代码在读取 volatile 修饰的变量的时候:
从主内存中读取volatile变量的最新值到线程的⼯作内存中,从⼯作内存中读取volatile变量的副本
缺点:
volatile 虽然可以解决内存可⻅性和指令重排序的问题,但是解决不了原⼦性问题,因此对于 ++ 和 –
操作的线程⾮安全问题依然解决不了
2 使用锁是java中解决线程安全问题最主要的手段:
内存锁:synchronized
可重入锁:ReentrantLock
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 修饰代码块:
this 修饰类:synchronized类名.class)
this 修饰类实例:synchronized(this)
注意事项:对于同一个业务的多个线程加锁对象,一定要是同一个对象加同一把锁
syntronized特性:
1 互斥性(排他性
synchronized 会起到互斥效果, 某个线程执⾏到某个对象的 synchronized 中时, 其他线程如果也
执⾏到同⼀个对象 synchronized 就会阻塞等待.- 进⼊ synchronized 修饰的代码块, 相当于 加锁
- 退出 synchronized 修饰的代码块, 相当于 解锁
2 刷新内存(内存可见性问题
3 synchronized 的⼯作过程:
- 获得互斥锁
- 从主内存拷⻉变量的最新副本到⼯作的内存
- 执⾏代码
- 将更改后的共享变量的值刷新到主内存
- 释放互斥锁
4 可重入:synchronized⽤的锁是存在Java对象头⾥的,里面有是否加锁的标志,以及拥有当前?的id
a. 查询当前对象头是否加锁 b. 判断隐藏对象头中的线程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("当前主线程再次获得?"); } } } }
3 synchronized实现原理:保证任何时候只有一个线程能够执行指定区域的代码
jvm层面依靠监视器Monitor实现(使用了一个对象的隐藏对象头 里面的 两个a,b属性实现的)
1 jdk1.6之前使用比较少(默认重量级锁实现,所以性能较差)
2 jdk1.6优化了
- 无锁(没有线程)->偏向锁(一个线程)->轻量级锁(少量线程)->重量级锁(较多线程 锁升级)
从操作系统的层面实现,基于操作系统的互斥锁(Mutax)
4 公平锁和非公平锁的区别:
公平锁一定要执行的步骤:1)上一个线程释放线程后执行唤醒操作 2)自旋后最前面阻塞休眠的线程从阻塞状态切换为运行状态;
非公平锁:来得早不如来得巧
5 ReentrantLockLock的实现步骤:
// 1.创建锁对象
Lock lock=new ReentrantLock();
// 2.加锁操作
lock.lock();
try{
//业务代码(可能会非常复杂 -> 导致异常)
System.out.println("你好! ReentrantLock");
}finally {
// 3.释放锁
lock.unlock();
}
Lock注意事项:
1 释放锁的unlock() 操作一定要放在finally里面,若没有放在这里可能会导致永久占用资源问题
2 加锁操作lock() 一定要放在try之前,或者try首行
- 未加锁却执行了释放锁的操作
- 释放锁的错误信息会覆盖掉业务代码的报错信息,从而增加调试代码的复杂度
指定Lock类型:
- 非公平锁:默认会创建非公平锁,性能高
- 公平锁:
new ReentrantLock(true)
2 synchronized VS Lock
- Lock 粒度可以更⼩
- Lock 更灵活。有更多方法比如:tryLock();
- 锁类型不同:Lock默认是非公平锁,但可以指定为公平锁;synchronized只能为公平锁
- 调用lock方法和synchronized方法线程等待状态不同;lock -》waiting synchronized-》blocked
- Lock 需要⼿动操作锁,⽽ Synchronized 是 JVM ⾃动操作的
- Lock 只能修饰代码块,⽽ Synchronized 可以修饰⽅法、静态⽅法、代码块