目录
一、线程状态
1.1 所有线程状态
线程的状态是一个枚举类型 Thread.State
public class ThreadState {
public static void main(String[] args) {
for (Thread.State state:Thread.State.values()) {
System.out.println(state);
}
}
}
· NEW: 新建状态,安排了工作还未开始行动。
· RUNNABLE:运行状态,分为①RUNNABLE:得到时间片运行中的状态 ②Ready:未得到时 间片的就绪状态
· BOLCKED:阻塞状态,当遇到锁时,会出现这种状态。
· WAITING:无限期的等待状态 。
· TIMED_WAITING:有明确结束时间的等待状态。
· TERMINATED:终止状态,当线程任务结束之后会变成此状态。
1.2 线程状态的转移
NEW->RUNNABLE:
新一个线程,状态为new,在我们启动线程之后就会变成runnable。
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(()->{
System.out.println("当前线程状态2:"+Thread.currentThread().getState());
});
System.out.println("当前线程状态:"+thread1.getState());
thread1.start();
}
}
RUNNABLE->TIMED_WAITING
在线程运行时我们让线程休眠一段时间,这时获取线程的状态,就是timed_waiting。
import java.util.concurrent.TimeUnit;
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(()->{
System.out.println("当前线程状态2:"+Thread.currentThread().getState());
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("当前线程状态:"+thread1.getState());
thread1.start();
Thread.sleep(1000);
System.out.println("当前线程状态3:"+thread1.getState());
}
}
RUNNABLE ->TERMINATED
线程执行完之后,就会从 RUNNABLE 状态变成 TERMINATED 销毁状态,如下代码所示:
import java.util.concurrent.TimeUnit;
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(()->{
System.out.println("当前线程状态2:"+Thread.currentThread().getState());
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("当前线程状态:"+thread1.getState());
thread1.start();
Thread.sleep(1000);
System.out.println("当前线程状态3:"+thread1.getState());
//等待线程1执行完
thread1.join();
System.out.println("当前线程状态4:"+thread1.getState());
}
}
二、线程安全性问题
2.1 线程安全的概念
线程不安全是指多线程的执行结果不符合预期。
单线程:
public class ThreadDemo15 {
static class Counter{
private int num=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++) {
num++;
}
}
//--方法
public void decrement(){
int temp=0;
for (int i = 0; i < MAX_COUNT; i++) {
num--;
}
}
public int getNum(){
return num;
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter=new Counter(100000);
counter.increment();
counter.decrement();
System.out.println("最终结果:"+counter.getNum());
}
}
多线程:
public class ThreadDemo15 {
static class Counter{
private int num=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++) {
num++;
}
}
//--方法
public void decrement(){
int temp=0;
for (int i = 0; i < MAX_COUNT; i++) {
num--;
}
}
public int getNum(){
return num;
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter=new Counter(100000);
Thread thread1=new Thread(()->{
counter.increment();
});
Thread thread2=new Thread(()->{
counter.decrement();
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终结果:"+counter.getNum());
}
}
这个时候我们得到的结果就是不确定的,这是为什么呢?下面我们来看以下问题。
2.2 线程不安全的原因
2.2.1 抢占式执行
多个线程争抢着执行任务,会导致线程不安全,如果我们让一个线程执行完之后,再执行下一个线程,就不会出现线程不安全的情况了,如下代码所示:
public class ThreadDemo15 {
static class Counter{
private int num=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++) {
num++;
}
}
//--方法
public void decrement(){
int temp=0;
for (int i = 0; i < MAX_COUNT; i++) {
num--;
}
}
public int getNum(){
return num;
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter=new Counter(100000);
Thread thread1=new Thread(()->{
counter.increment();
});
Thread thread2=new Thread(()->{
counter.decrement();
});
thread1.start();
thread1.join();
thread2.start();
thread2.join();
System.out.println("最终结果:"+counter.getNum());
}
}
只是将thread1.join()放在了thread2启动之前,就不会出现线程不安全的问题了。
2.2.2 多个线程修改同一个变量
因为两个线程都是修改 num这个变量,线程在并发执行的时候会出现不安全的问题,但如果各自修改各自的变量,就不会出现这个问题,如下代码所示:
public class ThreadDemo15 {
static class Counter{
// private int num=0;
private int num1=0;
private int num2=0;
private int MAX_COUNT=0;
public Counter(int MAX_COUNT){
this.MAX_COUNT=MAX_COUNT;
}
//++方法
public int increment(){
for (int i = 0; i < MAX_COUNT; i++) {
num1++;
}
return num1;
}
//--方法
public int decrement(){
int temp=0;
for (int i = 0; i < MAX_COUNT; i++) {
num2--;
}
return num2;
}
public int getNum(){
return num1+num2;
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter=new Counter(100000);
Thread thread1=new Thread(()->{
counter.increment();
});
Thread thread2=new Thread(()->{
counter.decrement();
});
thread1.start();
thread1.join();
thread2.start();
thread2.join();
System.out.println("最终结果:"+counter.getNum());
}
}
2.2.3 非原子性操作
什么是原子性?
我们把⼀段代码想象成⼀个房间,每个线程就是要进⼊这个房间的⼈。如果没有任何机制保证,A进⼊房间之后,还没有出来;B 是不是也可以进⼊房间,打断 A 在房间⾥的隐私。这个就是不具备原子性的。
那我们应该如何解决这个问题呢?是不是只要给房间加⼀把锁,A 进去就把⻔锁上,其他⼈是不是就进不来了。这样就保证了这段代码的原子性了。
⼀条 java 语句不⼀定是原⼦的,也不⼀定只是⼀条指令。
⽐如刚才我们看到的 num++,其实是由三步操作组成的:
1. 从内存把数据读到 CPU
2. 进⾏数据更新
3. 把数据写回到 CPU
不保证原⼦性会给多线程带来什么问题?
如果⼀个线程正在对⼀个变量操作,中途其他线程插⼊进来了,如果这个操作被打断了,结果就可能是错误的。这点也和线程的抢占式调度密切相关. 如果线程不是 "抢占" 的, 就算没有原子性, 也问题不大。
线程1的++操作执行了一半的时候,线程2开始进行--操作,因此由于操作不是原子性的就产生了线程不安全的问题。
2.2.4 内存可见性
可见性指一个线程共享变量进行修改时,另外的线程能够及时的看到。
Java内存模型(JMM):java虚拟机规范中定义了java内存模型。
目的是屏蔽各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的并发效果。
1.当线程要读取一个共享变量时,会先把变量从主内存拷贝到工作内存,再从工作内存内读取数据。
2.当线程要修改一个共享变量时,也会先修改工作内存中的副本,再同步回主内存。
由于每个线程有自己的工作内存,这些工作内存中的内容相当于同一个共享变量的"副本",此时修改线程1中的工作内存中的值,线程2的工作内存不一会及时知道,这就是内存可见性对线程安全 性造成的影响。
2.2.5 指令重排序
JVM会对我们的指令自动进行他认为的优化,在单线程下没有问题,但是多线程中会对我们的线程安全造成影响。