目录
一、线程的状态
1. NEW(初始状态)
NEW: 表示安排了工作,还未开始行动。
代码如下:
public class demo2 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (true) {
}
});
System.out.println(t.getState());//通过这个方法获取指定线程的状态
t.start();
}
}
运行结果如下:
2. TERMINATED(终止状态)
TERMINATED:工作完成了。
代码如下:
public class demo1 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
});
t.start();
Thread.sleep(1000);
System.out.println(t.getState());
}
}
运行结果如下:
3. RUNNABLE(运行时状态)
RUNNABLE:可工作的。又可以分成正在工作中和即将开始工作。
代码如下:
public class demo3 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (true) {
}
});
t.start();
Thread.sleep(1000);
System.out.println(t.getState());
}
}
运行结果如下:
4. TIMED_WAITING(超市等待状态)
TIMED_WAITING:这几个都表示排队等着其他事情
代码如下:
public class demo4 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
Thread.sleep(1000);
System.out.println(t.getState());
}
}
运行结果如下:
5. BLOCKED(阻塞状态)
BLOCKED:表示排队等着其他事情。
6. WAITING(等待状态)
WAITING:表示排队等着其他事情。
5、6了解即可😎
线程状态转换的简易图:
二、线程安全问题(面试题)
1.线程不安全的原因
操作系统调度线程的时候是无序的/随机的,线程之间是抢占式执行的,正是因为这种特性很可能会导致程序出现bug。
2.一个线程不安全的实例
代码如下:
class Counter{
private int count=0;
public void add(){
count++;
}
public int get(){
return count;
}
}
//两个线程针对同一个变量自增50000次
public class demo5 {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(()->{
for(int i=0;i<50000;i++){
counter.add();
}
});
Thread t2 = new Thread(()->{
for(int i=0;i<50000;i++){
counter.add();
}
});
t1.start();
t2.start();
//等待两个线程执行结束,然后看结果
t1.join();
t2.join();
System.out.println(counter.get());
}
}
注意这里我们需要等待t1、t2线程都结束之后再打印结果。
运行结果如下:
我们观察结果可以得出并不是10w,而是一个5w-10w之间的数字,为什么会这样呢?
在CPU的角度看来:count++实际上是三个CPU指令。
load->add->save
- load 把内存中 count 的值,加载到CPU寄存器中。
- add 把寄存器中的值+1。
- save 把寄存器的值写回到内存的count中。
线程调度是抢占式执行的,并且 count++ 操作是分三步完成,当 t1、t2 同时执行 count++
的操作时,顺序上是有很多可能的,这就是为什么我们最终的结果没有达到10w。
这张图是理想情况下的,但实际顺序并不只有这一种可能,可能限制性 t1 的 load,然后执行 t2 的 load ,这样交错执行,就会出现“bug”。
3.加锁操作
对上述线程不安全问题,提出方案->加锁
举例子说明一下:ATM 取钱,小李准备去ATM 里面取钱,当小李走进去之后,他便会把门上锁,这样小杨、小张等其他人想去钱的话只能等小李取完,这个等待操作就是阻塞。小李取完钱之后便可以给这个房间解锁,下一个人才能进 ATM 操作。
代码如下:
class Counter{
private int count=0;
synchronized public void add(){
count++;
}
/*public void add(){与上面的写法一致
synchronized (this){
count++;
}+
}*/
public int get(){
return count;
}
}
//两个线程针对同一个变量自增50000次
public class demo5 {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(()->{
for(int i=0;i<50000;i++){
counter.add();
}
});
Thread t2 = new Thread(()->{
for(int i=0;i<50000;i++){
counter.add();
}
});
t1.start();
t2.start();
//等待两个线程执行结束,然后看结果
t1.join();
t2.join();
System.out.println(counter.get());
}
}
运行结果如下:
4.产生线程不安全的原因
1.线程是抢占式执行的,线程间的调度充满随机性。(线程不安全的根本原因)
2.多个线程对同一个变量进行修改操作。
3.针对变量的操作不是原子的,通过加锁操作就是把几个指令打包成一个原子的。
4.内存可见性。
代码如下:
import java.util.Scanner;
public class ThreadDemo12 {
volatile public static int flag=0;
public static void main(String[] args) {
Thread t1 = new Thread(()->{
while(flag==0){
}
System.out.println("循环结束!t1 结束!");
});
Thread t2 = new Thread(()->{
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个整数");
flag = scanner.nextInt();
});
t1.start();
t2.start();
}
}
运行结果如下: