展开
拓展 关闭
订阅号推广码
GitHub
视频
公告栏 关闭

线程安全性

  • 线程安全
当多个线程访问某个类,不管运行时环境采用何种调度方式或者这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类为线程安全的
  • 新建线程不安全操作的案例类
public class UnSafeThread {

    private static int num = 0;

    private static CountDownLatch countDownLatch = new CountDownLatch(10);

    /**
     * 每次调用对num进行++操作
     */
    public static void  inCreate() {
        num++;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j < 100; j++) {
                    inCreate();
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //每个线程执行完成之后,调用countDownLatch
                countDownLatch.countDown();
            }).start();
        }

        while (true) {
            if (countDownLatch.getCount() == 0) {
                System.out.println(num);
                break;
            }
        }

    }
}
  • 打开cmd
# 编译
C:\work\git\spring_learn_repo\concurrent\src\main\java\com\xdclass\synopsis>javac -encoding UTF-8 UnsafeThread.java
# 反编译
C:\work\git\spring_learn_repo\concurrent\src\main\java\com\xdclass\synopsis>javap -c UnsafeThread.class
Compiled from "UnsafeThread.java"
public class com.xdclass.synopsis.UnSafeThread {
  public com.xdclass.synopsis.UnSafeThread();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void inCreate();
    Code:
       0: getstatic     #2                  // Field num:I
       3: iconst_1
       4: iadd
       5: putstatic     #2                  // Field num:I
       8: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: bipush        10
       5: if_icmpge     29
       8: new           #3                  // class java/lang/Thread
      11: dup
      12: invokedynamic #4,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
      17: invokespecial #5                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
      20: invokevirtual #6                  // Method java/lang/Thread.start:()V
      23: iinc          1, 1
      26: goto          2
      29: getstatic     #7                  // Field countDownLatch:Ljava/util/concurrent/CountDownLatch;
      32: invokevirtual #8                  // Method java/util/concurrent/CountDownLatch.getCount:()J
      35: lconst_0
      36: lcmp
      37: ifne          29
      40: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      43: getstatic     #2                  // Field num:I
      46: invokevirtual #10                 // Method java/io/PrintStream.println:(I)V
      49: goto          52
      52: return

  static {};
    Code:
       0: iconst_0
       1: putstatic     #2                  // Field num:I
       4: new           #18                 // class java/util/concurrent/CountDownLatch
       7: dup
       8: bipush        10
      10: invokespecial #19                 // Method java/util/concurrent/CountDownLatch."<init>":(I)V
      13: putstatic     #7                  // Field countDownLatch:Ljava/util/concurrent/CountDownLatch;
      16: return
}
  • 结论
       0: getstatic     #2                  // Field num:I    获取指定类的静态域,并将其押入栈顶 
       3: iconst_1                                            将int型1押入栈顶
       4: iadd                                                将栈顶两个int型
相加,将结果押入栈顶
       5: putstatic     #2                  // Field num:I    为指定类静态域赋值
       8: return
  • 步骤
1、获取静态域num = 0
2、第1个线程对num加1
3、若这时创建第2个线程,对num加1
4、最后的预期结果应该为2,实际结果为1

# 产生线程不安全问题的原因:num++ 不是原子性操作,被拆分成好几个步骤,在多线程并发执行的情况下,因为cpu调度,多线程快递切换,有可能两个同一时刻都读取了同一个num值,之后对它进行+1操作,导致线程安全性

  • 原子性操作
一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
  • 将非原子性操作变成原子性
使用synchronize关机字,使得操作具有原子性

volatile关键字仅仅保证可见性,并不保证原子性 
  • 代码案例
public class UnSafeThread {

    private static int num = 0;

    private static CountDownLatch countDownLatch = new CountDownLatch(10);

    /**
     * 每次调用对num进行++操作
     */
    public static synchronized void  inCreate() {
        num++;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j < 100; j++) {
                    inCreate();
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //每个线程执行完成之后,调用countDownLatch
                countDownLatch.countDown();
            }).start();
        }

        while (true) {
            if (countDownLatch.getCount() == 0) {
                System.out.println(num);
                break;
            }
        }

    }
}
  • synchronized关键字
1、内置锁: 每个java对象都可以用做一个实现同步的锁,这些锁称为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法
2、互斥锁: 内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去
3、修饰普通方法:锁住对象的实例
4、修饰静态方法:锁住整个类
5、修饰代码块:锁住一个对象 synchronized (lock) 即synchronized后面括号里的内容
  • 案例1
public class SynDemo {
    // 修饰普通方法时,会锁住当前SynDemo对象
    public synchronized void out() throws InterruptedException {
        System.out.println(Thread.currentThread().getName());
        Thread.sleep(5000L);
    }

    // 即使创建2个对象,也是调用各自对象中的线程
    public static void main(String[] args) {
        SynDemo synDemo = new SynDemo();
        SynDemo synDemo2 = new SynDemo();

        new Thread(() -> {
            try {
                synDemo.out();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                synDemo2.out();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

}

# 控制台结果:2个线程同时打印出结果,说明2个线程互不干扰
Thread-0
Thread-1
  • 案例2
public class  SynDemo {
    // 修饰静态方法
    public static synchronized void staticOut() throws InterruptedException {
        System.out.println(Thread.currentThread().getName());
        Thread.sleep(5000L);
    }
    // 创建2个线程执行
    public static void main(String[] args) {
        SynDemo synDemo = new SynDemo();
        SynDemo synDemo2 = new SynDemo();

        new Thread(() -> {
            try {
                synDemo.staticOut();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                synDemo2.staticOut();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

}

# 控制台结果:先打印线程1个线程,过了1段时间后,再打印第2个线程;说明锁住了整个类,有1个线程在执行时,其他线程是不能操作的
Thread-0
Thread-1
  • 案例3
public class  SynDemo {

    private Object lock = new Object();

    // 修饰代码块时,锁住lock对象
    public void myOut() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName());
            try {
                Thread.sleep(5000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // 创建2个线程,执行同1个代码块
    public static void main(String[] args) {
        SynDemo synDemo = new SynDemo();

        new Thread(() -> {
                synDemo.myOut();
        }).start();

        new Thread(() -> {
            synDemo.myOut();
        }).start();
    }

}

# 控制台结果:先打印线程1个线程,过了1段时间后,再打印第2个线程;说明锁住了lock对象,其中1个线程在操作时,其他线程不能操作
Thread-0
Thread-1
  • volatile关键字及其使用场景
1、能且仅能修饰变量
2、保证该变量的可见性,volatile关键字仅仅保证可见性,并不保证原子性
3、禁止指令重排序
4、A,B两个线程同时读取volatile关键字修饰的对象,A读取之后,修改了变量的值,修改后的值,对B线程来说,是可见
5、使用场景 1:作为线程开关 2:单例,修饰对象实例,禁止指令重排序
  • 代码案例
public class VolatileDemo implements Runnable {

    private static volatile boolean flag = true;

    @Override
    public void run() {
        while (flag) {
            System.out.println(Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {

    }
    
}
  • 单例与线程安全
1、饿汉式:本身线程安全
    在类加载的时候,就已经进行实例化,无论之后用不用到。如果该类比较占内存,之后又没用到,就白白浪费了资源
2、懒汉式:最简单的写法是非线程安全的
    在需要的时候再实例化
  • 案例1
public class HungerSingleton {

    private static HungerSingleton ourInstance = new HungerSingleton();

    public static HungerSingleton getInstance() {
        return ourInstance;
    }

    // 构造器私有化
    private HungerSingleton() {
    }

    // 创建多个线程
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                System.out.println(HungerSingleton.getInstance());
            }).start();
        }
    }
    
}

# 控制台结果:10个线程构建的对象是一样的,说明是线程安全的
  • 案例2
public class LazySingleton {

    private static LazySingleton lazySingleton = null;

    private LazySingleton() {

    }

    public static LazySingleton getInstance() {
        //判断实例是否为空,为空则实例化
        if (null == lazySingleton) {
            //模拟实例化时耗时的操作
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 如果为null,则new对象
            lazySingleton = new LazySingleton();
        //否则直接返回
        return lazySingleton;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(LazySingleton.getInstance());
            }).start();
        }
    }

}

# 控制台结果:创建了多个不同的对象,说明线程不安全
com.xdclass.safe.LazySingleton@55b1e661
com.xdclass.safe.LazySingleton@9ea8999
com.xdclass.safe.LazySingleton@1b5b6dd2
com.xdclass.safe.LazySingleton@15e99a61
com.xdclass.safe.LazySingleton@30fefd9
com.xdclass.safe.LazySingleton@5238f79d
com.xdclass.safe.LazySingleton@2d92d0a6
com.xdclass.safe.LazySingleton@1b2721ec
com.xdclass.safe.LazySingleton@525feac5
com.xdclass.safe.LazySingleton@5b5aee8d
  • 案例3
public class LazySingleton {

    private static LazySingleton lazySingleton = null;

    private LazySingleton() {

    }

    // 添加1个锁
    public static synchronized LazySingleton getInstance() {
        //判断实例是否为空,为空则实例化
        if (null == lazySingleton) {
            //模拟实例化时耗时的操作
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 如果为null,则new对象
            lazySingleton = new LazySingleton();
        }
        //否则直接返回
        return lazySingleton;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(LazySingleton.getInstance());
            }).start();
        }
    }

}

# 控制台结果:加了锁之后,是可以保证线程安全的;但是该方式会造成线程阻塞
com.xdclass.safe.LazySingleton@96ce312
com.xdclass.safe.LazySingleton@96ce312
com.xdclass.safe.LazySingleton@96ce312
com.xdclass.safe.LazySingleton@96ce312
com.xdclass.safe.LazySingleton@96ce312
com.xdclass.safe.LazySingleton@96ce312
com.xdclass.safe.LazySingleton@96ce312
com.xdclass.safe.LazySingleton@96ce312
com.xdclass.safe.LazySingleton@96ce312
com.xdclass.safe.LazySingleton@96ce312
  • 案例4
public class LazySingleton {

    private static LazySingleton lazySingleton = null;

    private LazySingleton() {

    }

    public static LazySingleton getInstance() {        // 10个线程进入该方法,判断对象是否为空,10个线程同时都判断为null
        //判断实例是否为空,为空则实例化
        if (null == lazySingleton) {                   
            // 添加锁
            synchronized (LazySingleton.class) {      // 其中1个线程获取到锁,new了1个对象,其他9个线程还是判断为null,new了9个对象是相同的
                lazySingleton = new LazySingleton();
            }
        }
        //否则直接返回
        return lazySingleton;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(LazySingleton.getInstance());
            }).start();
        }
    }

}

# 控制台结果:线程不安全
com.xdclass.safe.LazySingleton@79e8e17a
com.xdclass.safe.LazySingleton@6687d524
com.xdclass.safe.LazySingleton@6687d524
com.xdclass.safe.LazySingleton@6687d524
com.xdclass.safe.LazySingleton@6687d524
com.xdclass.safe.LazySingleton@411dfe5
com.xdclass.safe.LazySingleton@6687d524
com.xdclass.safe.LazySingleton@6687d524
com.xdclass.safe.LazySingleton@6687d524
com.xdclass.safe.LazySingleton@6687d524
  • 案例5
public class LazySingleton {

    private static LazySingleton lazySingleton = null;

    private LazySingleton() {

    }

    public static LazySingleton getInstance() {                // 10个线程进入该方法,判断对象是否为空,10个线程同时都判断为null
        //判断实例是否为空,为空则实例化
        if (null == lazySingleton) {
            //模拟实例化时耗时的操作
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 添加锁
            synchronized (LazySingleton.class) {              // 其中1个线程获取到锁,new了1个对象,其他9个线程还是判断为null,由于模拟了实例化耗时,则第2个线程new1个新的对象,在第2个线程还没创建完成的时候,第3个线程判断为null,则第3个线程也创建,最后的结果是创建了9个不同的对象
                lazySingleton = new LazySingleton();
            }
        }
        //否则直接返回
        return lazySingleton;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(LazySingleton.getInstance());
            }).start();
        }
    }

}

# 控制台结果:10个线程创建了10个对象,说线程不安全
com.xdclass.safe.LazySingleton@15e99a61
com.xdclass.safe.LazySingleton@557595cb
com.xdclass.safe.LazySingleton@5ba99877
com.xdclass.safe.LazySingleton@66a71f1b
com.xdclass.safe.LazySingleton@3c01442a
com.xdclass.safe.LazySingleton@6687d524
com.xdclass.safe.LazySingleton@96ce312
com.xdclass.safe.LazySingleton@525feac5
com.xdclass.safe.LazySingleton@2d92d0a6
com.xdclass.safe.LazySingleton@29b41d6e
  • 案例6
public class LazySingleton {

    private static LazySingleton lazySingleton = null;

    private LazySingleton() {

    }

    public static LazySingleton getInstance() {            // 10个线程进入该方法后,判断对象是否为null
        //判断实例是否为空,为空则实例化
        if (null == lazySingleton) {
            //模拟实例化时耗时的操作
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 添加锁
            synchronized (LazySingleton.class) {            // 其中1个线程拿到锁,再次判断,之后new对象
                // 如果为null,则new对象
                if (null == lazySingleton) {
                    lazySingleton = new LazySingleton();
                }
            }
        }
        //否则直接返回
        return lazySingleton;                                // 等第1个线程释放锁后,第2个线程拿到锁,判断不为null,直接返回对象
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(LazySingleton.getInstance());
            }).start();
        }
    }

}

# 控制台结果:10个线程创建的是相同的对象,但该方式也不能保证线程安全,可能会导致指令重排
com.xdclass.safe.LazySingleton@1c8a8cea
com.xdclass.safe.LazySingleton@1c8a8cea
com.xdclass.safe.LazySingleton@1c8a8cea
com.xdclass.safe.LazySingleton@1c8a8cea
com.xdclass.safe.LazySingleton@1c8a8cea
com.xdclass.safe.LazySingleton@1c8a8cea
com.xdclass.safe.LazySingleton@1c8a8cea
com.xdclass.safe.LazySingleton@1c8a8cea
com.xdclass.safe.LazySingleton@1c8a8cea
com.xdclass.safe.LazySingleton@1c8a8cea
  • 案例7
public class LazySingleton {

    // 添加volatile关键字,防止指令重排
    private static volatile LazySingleton lazySingleton = null;

    private LazySingleton() {

    }

    public static LazySingleton getInstance() {
        //判断实例是否为空,为空则实例化
        if (null == lazySingleton) {
            //模拟实例化时耗时的操作
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 添加锁
            synchronized (LazySingleton.class) {
                // 如果为null,则new对象
                if (null == lazySingleton) {
                    lazySingleton = new LazySingleton();
                }
            }
        }
        //否则直接返回
        return lazySingleton;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(LazySingleton.getInstance());
            }).start();
        }
    }

}

# 控制台结果:添加volatile关键字,防止指令重排
com.xdclass.safe.LazySingleton@9ea8999
com.xdclass.safe.LazySingleton@9ea8999
com.xdclass.safe.LazySingleton@9ea8999
com.xdclass.safe.LazySingleton@9ea8999
com.xdclass.safe.LazySingleton@9ea8999
com.xdclass.safe.LazySingleton@9ea8999
com.xdclass.safe.LazySingleton@9ea8999
com.xdclass.safe.LazySingleton@9ea8999
com.xdclass.safe.LazySingleton@9ea8999
com.xdclass.safe.LazySingleton@9ea8999
1、线程安全性问题成因
    多线程环境
    多个线程操作同一共享资源
    对该共享资源进行了非原子性操作
2、避免线程安全性问题,打破成因中三点任意一点 
    1:多线程环境:将多线程改单线程(必要的代码,加锁访问) 
    2:多个线程操作同一共享资源:不共享资源(ThreadLocal、不共享、操作无状态化、不可变) 
    3:对该共享资源进行了非原子性操作:将非原子性操作改成原子性操作(加锁、使用JDK自带的原子性操作的类、JUC提供的相应的并发工具类)
posted @ 2022-05-11 17:20  DogLeftover  阅读(15)  评论(0编辑  收藏  举报