侧边栏壁纸
博主头像
快乐江湖的博客博主等级

更多内容请点击CSDN关注“快乐江湖”

  • 累计撰写 127 篇文章
  • 累计创建 33 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

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

快乐江湖
2023-06-15 / 0 评论 / 0 点赞 / 11 阅读 / 11271 字

一:线程状态

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

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

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

⑤指令重排

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

评论区