美文网首页
线程安全性

线程安全性

作者: JBryan | 来源:发表于2019-12-20 16:29 被阅读0次

    1.线程安全性

    当多个线程访问某个类,不管运行时环境采用何种调度方式或者这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类为线程安全的。----《并发编程实战》
    多线程并发访问时,得不到正确的结果,就是线程不安全的。
    线程不安全举例:

    public class UnsafeThread {
        
        public static int count = 0;
        public static void sum() {
            count++;
        }
        
        public static void main(String[] args) throws InterruptedException {
            //循环启动十个线程,每个线程执行100次++操作.
            for(int j=0;j<10;j++) {
                Thread thread = new Thread() {
                    @Override
                    public void run() {
                        for(int i=0;i<1000;i++) {
                            UnsafeThread.sum();
                        }
                    }
                };
                thread.start();
            }
            Thread.sleep(1000);
            System.out.println(count);
        }
    }
    

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

    2.Synchronized关键字

    原子性:一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
    内置锁:每个java对象都可以用做一个实现同步的锁,这些锁称为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
    互斥锁:内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
    修饰普通方法:锁住对象的实例。
    修饰静态方法:锁住整个类。
    修饰代码块: 锁住一个对象 synchronized (lock) 即synchronized后面括号里的内容。

    public class SyncDemo {
        
        /**
         * synchronized修饰方法
         * 锁住的是该对象的实例,而不会影响其他实例
         * @throws InterruptedException 
         */
        public synchronized void method() throws InterruptedException {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(2000); 
        }
        
        
        /**
         * synchronized修饰静态方法
         * 锁住这个类的所有实例
         * @throws InterruptedException
         */
        public synchronized static void method1() throws InterruptedException {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(2000);
        }
        
        private Object lock = new Object();
        
        /**
         * synchronized修饰代码块
         * 锁住一个对象,此方法内,锁住lock对象
         * @throws InterruptedException 
         * 
         */
        public void method2() throws InterruptedException {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName());
                Thread.sleep(2000);
            }
        }
        
        public static void main(String[] args) {
            SyncDemo demo1 = new SyncDemo();
    //      SyncDemo demo2 = new SyncDemo();
            
            Thread thread = new Thread() {
                @SuppressWarnings("static-access")
                @Override
                public void run() {
                    try {
    //                  demo1.method1();
                        demo1.method2();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            thread.start();
            
            Thread thread2 = new Thread() {
                @SuppressWarnings("static-access")
                @Override
                public void run() {
                    try {
    //                  demo2.method1();
                        demo1.method2();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };  
            thread2.start();    
        }
    }
    

    3.Volatile关键字

    能且仅能修饰变量。
    保证该变量的可见性,volatile关键字仅仅保证可见性,并不保证原子性。
    禁止指令重排序。
    A、B两个线程同时读取volatile关键字修饰的对象,A读取之后,修改了变量的值,修改后的值,对B线程来说,是可见
    使用场景
    1:作为线程开关 。
    2:单例,修饰对象实例,禁止指令重排序。

    4.单例与线程安全

    饿汉式:本身线程安全,在类加载的时候,就已经进行实例化,无论之后用不用到。如果该类比较占内存,之后又没用到,就白白浪费了资源。

    public class HungerSingleton {
    
        private static HungerSingleton hungerSingleton = new HungerSingleton();
        
        public static HungerSingleton getInstance() {
            return hungerSingleton;
        }
        //构造方法一定私有化
        private HungerSingleton() {     
        }
        
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread() {
                    @Override
                    public void run() {
                        System.out.println(HungerSingleton.getInstance());
                    }
                }.start();
            }
        }
    }
    
    

    懒汉式 :最简单的写法是非线程安全的,在需要的时候再实例化。

    public class LazySingleton {
        
        //volatile防止JVM指令重排序
        private static volatile LazySingleton lazySingleton = null;
        
        private LazySingleton() {   
        }
        
        /**
         * 非线性安全的,加上synchronized
         * 若加到方法上,太消耗性能
         * @return
         */
        public static LazySingleton getInstance() {
            if(lazySingleton == null) {
                synchronized (LazySingleton.class) {
                    if(lazySingleton == null) {
                        lazySingleton = new LazySingleton();
                    }
                    //若直接初始化对象,多个线程同时进入if()代码块,等待释放锁之后,重复初始化
    //              lazySingleton = new LazySingleton();
                }
            }
            return lazySingleton;
        }
        
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread() {
                    @Override
                    public void run() {
                        System.out.println(LazySingleton.getInstance());
                    }
                }.start();
            }
        }
    }
    
    

    5.避免线程安全问题

    线程安全性问题成因
    1.多线程环境
    2.多个线程操作同一共享资源
    3.对该共享资源进行了非原子性操作
    如何避免,打破成因中三点任意一点 :
    1.多线程环境--将多线程改单线程(必要的代码,加锁访问)
    2.多个线程操作同一共享资源--不共享资源(ThreadLocal、不共享、操作无状态化、不可变)
    3.对该共享资源进行了非原子性操作-- 将非原子性操作改成原子性操作(加锁、使用JDK自带的原子性操作的类、JUC提供的相应的并发工具类)

    相关文章

      网友评论

          本文标题:线程安全性

          本文链接:https://www.haomeiwen.com/subject/uohinctx.html