Java多线程基础-第一节3:线程状态和线程安全

Posted 我擦我擦

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程基础-第一节3:线程状态和线程安全相关的知识,希望对你有一定的参考价值。

文章目录

一:线程状态

(1)Java中的线程状态

Java中的线程状态:Java中的线程状态其实是一个枚举类型,即Thread.state

  • NEW新建状态):创建后,启动前,线程就处于该状态
  • RUNNABLE可运行状态):线程正在执行代码,就处于该状态
  • BLOCKED阻塞状态):一个线程获取synchronized锁对象失败,就处于该状态
  • WAITING无限状态):一个线程获取Lock锁对象失败,就处于该状态。调用wait方法,线程也会处于该状态
  • TIMED_WAITING计时等待状态):线程正在执行sleep方法,就处于该状态
  • TERMINATED消亡状态):线程把任务执行完毕后,就处于该状态

注意BLOCKEDWAITINGTIMED_WAITING 这三个状态简单点来讲都表示排队等着其他事情。由于这三种状态还涉及其他一些知识,所以这里不便详细展开说明,到后面我们会深入学习的

public class TestDemo7 
    public static void main(String[] args) 
        for (Thread.State state : Thread.State.values()) 
            System.out.println(state);
        
    

(2)线程状态转移

线程状态转移:通过方法调用、触发事件等,线程就会在上面6种状态进行切换,如下图

如下代码展示了一个简单的线程状态变化的例子

  • 注意BLOCKEDWAITINGTIMED_WAITING 三种并未涉及或涉及很少,会在后文中详细讲解
public class TestDemo8 
    public static void main(String[] args) throws InterruptedException 
        Thread thread = new Thread()
            @Override
            public void run()
                for (int i = 0; i < 10000_0000; i++)
                    //do nothing
                
                try 
                    Thread.sleep(2000);
                 catch (InterruptedException e) 
                    throw new RuntimeException(e);
                
            
        ;
        System.out.println("【thread.start()之前】");
        System.out.println("thread状态:" + thread.getState());
        System.out.println("main状态:" + Thread.currentThread().getState());

        thread.start();

        System.out.println("【thread.start()之后】");
        System.out.println("thread状态:" + thread.getState());
        System.out.println("main状态:" + Thread.currentThread().getState());

        Thread.sleep(500);

        System.out.println("【main睡眠0.5s之后】");
        System.out.println("thread状态:" + thread.getState());
        System.out.println("main状态:" + Thread.currentThread().getState());

        thread.join();

        System.out.println("【thread.join()之后】");
        System.out.println("thread状态:" + thread.getState());
        System.out.println("main状态:" + Thread.currentThread().getState());

        //结束工作,状态为TERMINGATED
        System.out.println("thread状态:" + thread.getState());
        System.out.println("main状态:" + Thread.currentThread().getState());
    

二:线程安全

(1)线程安全的概念

线程安全:如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境下应该的结果,则说明这个程序是线程安全的

如下是一个线程不安全的例子:两个线程分别对counter对象中tickets成员(初始值为10W)连续自减5W次,在单线程情况下,最后tickets的结果应该为0,但运行后结果却出现了很大的误差,而且每次运行结果都存在差异

class Counter
    public int tickets = 100000;
    public void decrease()
        tickets--;
    


public class TestDemo9 
    private static Counter counter = new Counter();
    public static void main(String[] args) throws InterruptedException 
        //下面两个线程,每个线程都会counter进行5W次自减
        //正确结果理应为0

        Thread thread1 = new Thread()
            @Override
            public void run()
                    for(int i = 0; i < 50000; i++)
                        counter.decrease();
                    
            
        ;

        Thread thread2 = new Thread()
            @Override
            public void run()
                for(int i = 0; i < 50000; i++)
                    counter.decrease();
                
            
        ;

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("counter: " + counter.tickets);

    

实际上,tickets--并非原子性操作,在底层涉及如下三条指令

而线程调度时又没有确定的顺序,所以这两个线程在执行时就可能出现各种各样的执行顺序,结果自然也是五花八门

(2)线程不安全的原因

①抢占式执行:多个线程的调度执行过程,可以视为是全随机的。所以在写多线程代码的时候,就需要考虑到在任意一种调度情况下都能够运行出正确结果,这无疑增加了编程的难度

②修改共享数据:上面的线程不安全代码中,至少涉及2个线程针对counter.tickets变量的修改,这个counter.tickets便是多个线程都能访问到的共享数据

  • counter.tickets实际位于堆上,所以可以被多个线程共享访问
  • 在之前的学习中,我们常常会说××是线程安全的。例如String,它被设置为了private,是不可变的,所以不能修改,因而是线程安全的

③原子性操作:原子是不可分割的最小单位,原子性操作是指动作要一气呵成而不能中断。上面的线程不安全代码中,tickets--就不是一个原子性操作,会涉及如下三条指令

  • mov
  • sub
  • mov

CPU执行程序的最小单位便是一个指令,所以有可能会出现三条指令还没有被执行完就被调度走的情况出现,也即一个线程正在对某个变量操作,中途其他线程突然插入了进来导致操作被打断,结果极有可能就会出现错误。原子性也是解决线程不安全最常用的方法,我们可以把多个操作通过特殊手段打包成一个原子操作

④内存可见性:可见性是指一个线程对共享数据的修改,其它线程能够及时看到。这个问题主要是因编译器优化而导致的

  • 此内容将在下一节详细介绍

⑤指令重排

  • 此内容将在下一节详细介绍

以上是关于Java多线程基础-第一节3:线程状态和线程安全的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程基础-第一节2:Thread类

Java多线程基础-第一节6:多线程案例

Java多线程基础-第一节5:wait和notify

不会吧,你连Java 多线程线程安全都还没搞明白,难怪你面试总不过

Java多线程基础-第一节4:synchronized关键字(监视器锁monitor lock)和volatile关键字

进程管理-第一节6:线程的实现方式和多线程模型