线程安全性问题

线程性能问题

CPU时间片java

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

活跃性问题

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

死锁问题多线程

能够经过jconsole 工具来进行检测:ide

饥饿和活锁问题很差检测工具

饥饿与公平

  • 高优先级吞噬全部低优先级的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

线程安全性问题

写一个数值生成器:3d

单线程环境下:code

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 查看线程是否发生死锁,线程的饥饿与公平,线程安全性问题:从字节码角度来分析线程安全性问题、如何解决线程安全的问题以及在什么状况下会出现线程安全性问题。

欢迎扫码关注,第一时间收到最新文章推送

个人博客即将同步至腾讯云+社区,邀请你们一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=14jki2ulsej09

相关文章
相关标签/搜索