栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

线程状态与线程安全

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

线程状态与线程安全

目录

线程的状态

NEWTERMINATEDRUNNABLETIMED_WAITINGBLOCKEDWAITING线程状态转换图 线程安全

概念线程不安全的典型案例线程不安全的原因 synchronized的用法

直接修饰普通方法修饰一个代码块修饰一个静态方法

线程的状态

之前讨论的“就绪”和“阻塞”都是针对系统层面上的线程的状态(相当于针对一个进程只有一个线程的情况),但更常见的情况是一个进程中包含了多个线程,所谓的状态,其实是绑定在线程上。

NEW

安排了工作,还未开始行动
把Thread对象创建好了,但还没开始调用start

public class Demo14 {
    public static void main(String[] args) {
        Thread t=new Thread(() ->{

        });
        System.out.println(t.getState());
        t.start();
    }
}


运行效果

TERMINATED

工作完了
就是操作系统的线程已经执行完毕,销毁了,但是Thread对象还在,获取到的状态。

public class Demo14_1 {
    public static void main(String[] args) throws InterruptedException {
        Thread  t=new Thread(() ->{
           
        });
        t.start();
        Thread.sleep(1000);
        System.out.println(t.getState());
    }
}

执行效果

RUNNABLE

就绪状态
处于这个状态的线程,就是在就绪队列中,随时可以被调度到CPU上
如果代码中没有sleep,也没有其他可能导致阻塞的状态,代码大概率是处在Runnable状态的。

ublic class Demo14_2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(() ->{
            while(true){
                //这里什么都不写
            }
        });
        t.start();
        Thread.sleep(1000);
        System.out.println(t.getState());
    }
}

执行效果

TIMED_WAITING

代码中调用了sleep或者join(超时时间),就会进入到此状态。
意思是当前的线程在一定时间之内是阻塞的状态【一定时间到了之后,阻塞解除】。(阻塞状态之一)

public class Demo14_3 {
   
    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());
    }
}

执行效果

BLOCKED

当前线程在等待锁,导致阻塞(阻塞状态之一)
synchronized

WAITING

当前线程在等待唤醒,导致阻塞(阻塞状态之一)

线程状态转换图

线程安全 概念

操作系统,线程调度的时候,是随机的(抢占式执行),正是因为这样的随机性,就可能导致程序执行出现一些bug.
如果因为这样的调度随机性引入了bug,就认为线程是不安全的,如果是因为这样的调度随机性,也没有带来bug,就认为是线程安全的。

线程不安全的典型案例

使用两个线程,对同一个整型变量,进行自增操作,每个线程自增5w次,看最终的结果。

class Counter {
    public int count;
    public void increase(){
        count++;
    }
}

public class Demo15 {
    private static Counter counter=new Counter();
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(() ->{
           for(int i=0;i<50000;i++){
               counter.increase();
           }
        });
        Thread t2=new Thread(() ->{
            for(int i=0;i<50000;i++){
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        //必须要在t1  t2都执行完了之后,在打印count的结果
        //否则,main 和 t1  t2之间都是并发执行的关系,导致t1 t2还没执行完,就先执行了下面的打印操作
        t1.join();
        t2.join();

        //在main中打印一下两个线程自增完成之后,得到的count结果
        System.out.println(counter.count);


    }
}

执行效果

预期是10w,但实际是63826

原因:

如何解决上述问题?
加锁

如何加锁
使用synchronized 关键字

class Counter {
    public int count;
    synchronized public void increase(){
        count++;
    }
}

执行效果

给方法直接synchronized关键字,此时进入方法,就会自动加锁,离开方法,就会自动解锁。
当一个线程加锁成功的时候,其他线程尝试加锁,就会触发阻塞等待(此时对应的线程,就处在BLOCKED状态),阻塞会一直持续到占用锁的线程把锁释放为止。

线程不安全的原因

不是所有的多线程都要加锁(如果这样,多线程的并发能力就形同虚设)
1.线程是抢占式执行的,线程间的调度充满随机性【线程不安全的万恶之源】根本原因,无可奈何
2.多个线程对同一个变量进行修改操作(如果是多个线程针对不同的变量进行修改,没事;如果是多个线程针对同一个线程读,也没事)通过调整代码结构,使不同线程操作不同变量
3.针对变量的操作不是原子的
有些操作,比如读取变量的值,只是针对一条机器指令,此时这样的操作本身就可以视为是原子的。
通过加锁操作,也就是把好几个指令给打包成一个原子的了。
4.内存可见性,也会影响到线程安全。

public class Demo16 {
    private static int isQuite=0;
    public static void main(String[] args) {
        Thread t=new Thread(() ->{
            while(isQuite==0){
                
            }
            System.out.println("循环结束!t线程退出");
        });
        t.start();

        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入一个isQuit的值:");
        isQuite=scanner.nextInt();
        System.out.println("main线程执行完毕");
        
    }
}

执行效果

此时,这里没有输出这句话。System.out.println(“循环结束!t线程退出”);

那么如何解决呢?
(1)使用synchronized关键字
synchronized不光能保证指令的原子性,同时也能保证内存可见性。被synchronized包裹起来的代码,编译器就不敢有上述的行为!相当于手动禁用了编译器的优化!
(2)使用volatile关键字
volatile和原子性无关,但能保证内存的可见性。
禁止编译器做出上述优化。

 private static  volatile int isQuite=0;

执行效果

5.指令重排序,也会影响到线程安全问题
指令重排序,也是编译器优化中的一种操作。


那么,如何解决这样的问题呢?
使用synchronized关键字
synchronized不仅能保证原子性,同时也能保证内存可见性,同时还能禁止指令重排序。

synchronized的用法

synchronized翻译为同步的。
“同步”在计算机中存在多种意思。
在多线程中,线程安全中,同步指“互斥”。
在I/O或者网络编程中,同步相对的词叫“异步”,此处的同步和互斥没有关系和线程也没有关系,表示的是消息的发送方如何获取到结果。

直接修饰普通方法

使用synchronized的时候,本质上是针对某个“对象”进行加锁。

修饰一个代码块

需要显示指定针对哪个对象加锁(Java中的任意对象都有锁对象)

class Counter {
    public int count;
     public void increase(){
         synchronized (this){
             count++;
         }

    }
}

public class Demo15 {
    private static Counter counter=new Counter();
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(() ->{
           for(int i=0;i<50000;i++){
               counter.increase();
           }
        });
        Thread t2=new Thread(() ->{
            for(int i=0;i<50000;i++){
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        //必须要在t1  t2都执行完了之后,在打印count的结果
        //否则,main 和 t1  t2之间都是并发执行的关系,导致t1 t2还没执行完,就先执行了下面的打印操作
        t1.join();
        t2.join();

        //在main中打印一下两个线程自增完成之后,得到的count结果
        System.out.println(counter.count);


    }
}


修饰一个静态方法

相当于针对当前类对象加锁。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/777135.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号