• 线程安全性问题


    线程性能问题

    CPU时间片

    上下文切换,切换的时候需要保持任务的状态, 以便后续接着任务的状态执行。比较消耗CPU 资源。

    活跃性问题

    • 死锁: 双方都有对方需要的资源,并且都不释放给对方使用
    • 活锁:一直互相释放资源给对方
    • 饥饿: 线程优先级比较低的线程可能一直得不到资源而无法运行

    死锁问题

    可以通过jconsole 工具来进行检测:

    饥饿和活锁问题不好检测

    饥饿与公平

    • 高优先级吞噬所有低优先级的CPU 时间片

    以下代码低优先级的线程还是有可能大多数情况下获取CPU时间片的,只是概率上会变小。

    public class Target implements Runnable {
    
        @Override
        public void run() {
            while (true) {
                System.out.println(Thread.currentThread().getName() + "...");
            }
        }
    }
    
    public class Demo8 {
    
        public static void main(String[] args) {
            Thread t = new Thread(new Target());
            Thread t1 = new Thread(new Target());
            Thread t2 = new Thread(new Target());
            Thread t3 = new Thread(new Target());
    
            t.setPriority(Thread.MAX_PRIORITY);
            //不同平台优先级值不一样,建议使用常量
            t2.setPriority(Thread.MIN_PRIORITY);
    
            t.start();
            t2.start();
        }
    }
    
    • 线程被永久堵塞在一个等待进入同步块的状态
    • 等待的线程永远不被唤醒

    如何尽量避免饥饿问题:

    1. 设置合理的优先级
    2. 使用锁来代替synchronized

    线程安全性问题

    写一个数值生成器:

    单线程环境下:

    public class Sequence {
    
        private int value;
    
        public int getNext() {
            return value++;
        }
    
    
        public static void main(String[] args) {
            Sequence sequence = new Sequence();
            while (true) {
                System.out.println(sequence.getNext());
            }
        }
    }
    

    多线程环境:

    package thread;
    
    public class Sequence {
    
        private int value;
    
        public int getNext() {
            return value++;
        }
    
    
        public static void main(String[] args) {
    
            Sequence sequence = new Sequence();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        System.out.println(Thread.currentThread().getName() + " " + sequence.getNext());
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        System.out.println(Thread.currentThread().getName() + " " + sequence.getNext());
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        System.out.println(Thread.currentThread().getName() + " " + sequence.getNext());
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }
    

    会有数据重复的问题,这就是线程安全性问题

    线程是多个执行顺序流,value++ 相当于 value = value + 1, 是两步操作

    从java字节码角度来看线程安全性问题:

    可以使用 javap -verbose Sequence.class 来看字节码文件

      0: aload_0
             1: dup
             2: getfield      #2                  // Field value:I
             5: dup_x1
             6: iconst_1
             7: iadd
             8: putfield      #2                  // Field value:I
            11: ireturn
    

    类的实例化对象它是放在堆内存中,堆是线程所共享的区域

    程序计数器是线程所独享的区域

    Value 是线程共享的区域

    • 第一个线程执行完,值变为1 ,只是在操作数栈中变为1,还没有设置value, 因此value 还是0
    • 第二个线程获取到时间片,首先是获取值 getfield, 获取的value为0,因为第一个线程还没有写进去
    • 第一个线程获取到时间片,开始把1 往value 放,value 变为1
    • 第二个线程获取到时间片,也开始把加完后的1 往value 放,value 还是1
    • 本来最终应该是2,结果是1,这就是线程安全性问题

    如何解决上面代码的线程安全性问题:

    package thread;
    public class Sequence {
        private int value;
    
        public synchronized int getNext() {
            return value++;
        }
        public static void main(String[] args) {
    
            Sequence sequence = new Sequence();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        System.out.println(Thread.currentThread().getName() + " " + sequence.getNext());
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        System.out.println(Thread.currentThread().getName() + " " + sequence.getNext());
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        System.out.println(Thread.currentThread().getName() + " " + sequence.getNext());
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }
    

    在 getNext() 方法前加了synchronized关键字,让方法变成同步方法就不会再有重复的数据

    Synchronized 相当于一个门加了一把锁,当一个线程执行的时候,就获取了这把锁,然后把门锁上,其他线程来的时候就要在外面等待,等执行完毕后把锁释放,其他线程才能进来。就会导致同一时刻只会有一个线程执行该段代码。

    什么情况会出现线程安全性问题:

    1. 多线程环境
    2. 多个线程共享一个资源
    3. 对资源进行非原子性操作

    总结

    本文主要介绍了线程的性能问题,死锁问题以及如何使用jconsole 查看线程是否发生死锁,线程的饥饿与公平,线程安全性问题:从字节码角度来分析线程安全性问题、如何解决线程安全的问题以及在什么情况下会出现线程安全性问题。

  • 相关阅读:
    Activity表单传值问题
    求助~!线程里不能MediaRecorder.start()
    获取浏览器的宽高:
    WebStorm快捷键收集
    CSS中强大的EM
    常用global.css
    常用base.css
    自适应网页设计的方法(手机端良好的访问体验)
    还需要学习的十二种CSS选择器
    js 根据屏幕大小调用不同的css文件
  • 原文地址:https://www.cnblogs.com/bigdata1024/p/11048231.html
Copyright © 2020-2023  润新知