多线程Thread(初阶三:线程的状态及线程安全)

目录

一、线程的状态

二、线程安全

总结线程不安全的原因

针对上述原因给出的解决方案


一、线程的状态

1.NEW Thread:对象创建好了,但是还没有调用 start 方法在系统中创建线程。

2.TERMINATED: Thread 对象仍然存在,但是系统内部的线程已经执行完毕了。

3.RUNNABLE: 就绪状态,表示这个线程正在 cpu 上执行,或者准备就绪随时可以去 pu 上执行。

4.TIMED WAITING: 指定时间的阻塞.就在到达一定时间之后自动解除阻塞使用 sleep 会进入这个状态 使用带有超时时间的join也会。

5.WAITING: 不带时间的阻塞(死等),必须要满足一定的条件,才会解除阻塞join 或者 wait 都会进入 WAITING。

6.BLOCKED: 由于锁竞争,引起的阻塞.(后面线程安全的时候具体介绍)

这六种状态在生命周期的大概位置,如图:

多线程Thread(初阶三:线程的状态及线程安全)_第1张图片

用代码形式测试 NEW 、RUNNABLE、TERMINATED 状态

代码:

public class ThreadDemo3 {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                count++;
            }
            System.out.println("count: " + count);
        });
        //t线程开始前
        System.out.println("t线程开始前: " + t.getState());

        t.start();
        //t线程执行中
        System.out.println("t线程执行中: " + t.getState());

        //t线程结束后
        t.join();
        System.out.println("t线程结束后: " + t.getState());
    }
}

执行效果:

多线程Thread(初阶三:线程的状态及线程安全)_第2张图片

学习这些状态,最大的作用就是调试多线程代码bug的时候,给我们提供重要的参考依据当某个程序卡住了,也就是意味着一些关键的线程阻塞了,我们就可以观察线程的状态,分享出一些原因。

注意:一个线程只能start一次,也就是说只有是NEW状态的线程才能start。

二、线程安全

线程安全:某个代码,不管它是单个线程执行,还是多个线程执行,都不会产生bug,这个情况就成为“线程安全”。

线程不安全:某个代码,它单个线程执行,不会产生bug,但是多个线程执行,就会产生bug,这个情况就成为 “线程不安全”,或者 “存在线程安全问题”。

举个线程不安全例子,现在,我们想计算一个变量的自增次数,它循环了100000次,用两个线程去计算,各自计算循环50000次的次数。

代码:

public class ThreadDemo4 {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 1; i < 50000; i++) {
                count++;
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 50000; i <= 100000; i++) {
                count++;
            }
        });
        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("count: " + count);
    }
}

我们知道,从1到10_0000,肯定是自增了10_0000次,但是我们看看输出结果如何?

多线程Thread(初阶三:线程的状态及线程安全)_第3张图片

答案却不是10_0000,是50000多次,这是为何呢?原因就是多线程代码,它们是并发执行的。

这个count++是由cpu的三个指令构成的:

(1)load 从内存中读取数据到cpu的寄存器中。

(2)add 把寄存器中的值 + 1。

(3)save 把寄存器中的值写回到内存中。

因为上面两个线程是并发执行的,那么 t1 和 t2 线程的执行顺序就是无序的,他们可能同时读取数据,自增完再+1,下面用图示他们的一些可能性。

多线程Thread(初阶三:线程的状态及线程安全)_第4张图片

多线程Thread(初阶三:线程的状态及线程安全)_第5张图片

暂时就画这么多,因为线程并发的执行结果个数是无数的,并不是简单的排列组合就能穷举出来,因为并发的原因,可能 t1 线程它执行了两次,才执行一次 t2 线程,也可能更多,或者 t2 执行的次数更多,t1 线程只执行一次。就又要排列组合了,这些情况都是有可能的。

这时,t1 和 t2自增的时候,就可能拿的是同一个值,这两线程的其中一个自增后,没放回内存中,另一个线程就又拿了,这肯定是不符合我们预期的。

所以我们从上面的可能情况找,符合我们预期的效果就只有这两个了,如图

多线程Thread(初阶三:线程的状态及线程安全)_第6张图片

那么这种情况也就是串行化执行,执行完 t1,再执行t2,或者两个顺序相反。

代码:

public class ThreadDemo4 {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 1; i < 50000; i++) {
                count++;
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                t1.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            for (int i = 50000; i <= 100000; i++) {
                count++;
            }
        });
        t1.start();

        t2.start();
        t2.join();

        System.out.println("count: " + count);
    }
}

执行效果:

多线程Thread(初阶三:线程的状态及线程安全)_第7张图片

但这样就没必要使用多线程了,因为串行化,一个线程就能搞定了,使用多线程也没太大意义。

总结线程不安全的原因

1、根本原因:操作系统上的线程是“抢占式执行”,随机调度的。

2、代码结构,多个线程同时修改同一个变量。

3、直接原因,多线程修改操作,本身不是原子的,比如count++,在cpu上有3个指令,可能修改还没保存,就被其他线程调度走了。

针对上述原因给出的解决方案

对于原因1:我们无法给出解决方案,因为操作系统内部已经实现了“抢占式执行”,我们干预不了

对于原因2:分情况,有的时候,代码结构可以调整,有的时候调整不了。

对于原因3:把要修改的变量这操作,通过特殊手段,把这操作在系统里的多个指令打包成一个“整体”。例如加锁操作,而加锁的操作,就是把多个指令打包成一个原子的操作。

你可能感兴趣的:(java,开发语言,java-ee)