【JavaEE】多线程(二)线程的状态

文章详细介绍了Java线程的六种状态,包括NEW、TERMINATED、RUNNABLE、TIMED_WAITING、BLOCKED和WAITING,并通过代码示例展示了状态转换。同时,文章讨论了线程不安全的原因,提供了一个线程不安全的计数器实例,并通过加锁操作解决线程安全问题,强调了原子操作和内存可见性的重要性。
摘要由CSDN通过智能技术生成

一、线程的状态

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();
    }
}

运行结果如下:
new

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());
    }
}

运行结果如下:
terminated

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());
    }
}

运行结果如下:
runnable

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());
    }
}

运行结果如下:
timed_waiting

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());
    }
}

运行结果如下:
synchronized

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();
    }
}

运行结果如下:

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值