美文网首页
线程安全

线程安全

作者: 思而忧 | 来源:发表于2017-09-21 01:47 被阅读0次

    在多线程程序中,如果多个线程同时对同一个对象进行读写,由于读写操作会在内存中先复制一份缓存数据,修改完缓存数据后再用缓存数据覆盖原来的数据,所以写操作不会马上影响到原始数据,这时候其他线程读取到的数据就是旧的数据(也成为“脏数据”),这个数据的读写就是线程不安全了。在数据库里面所谓的“脏读”也是类似的原理。

    例子程序:

    class UnSafeClass{
        // 线程不安全的类
        private int a = 0;
    
        public void add(){
            a+=1;
            System.out.println("UnSafeClass: " + Thread.currentThread().getName() + ":a is " + a);
        }
    
    }
    
    class SafeClass{
        // 线程安全的类
        private int a = 0;
        public void add(){
            synchronized (this) {
                a++;
                System.out.println("SafeClass: " + Thread.currentThread().getName() + ":a is " + a);
            }
        }
    }
    
    
    class TestUnSafeThread implements Runnable{
    
        private static UnSafeClass unSafeClass = new UnSafeClass();
    
        @Override
        public void run(){
            for(int i=0; i<10; i++) {
                try {
                    unSafeClass.add();
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println(e);
                }
            }
        }
    }
    
    class TestSafeThread implements Runnable{
    
        private static SafeClass safeClass = new SafeClass();
    
        @Override
        public void run() {
            for(int i=0;i<10;i++) {
                try {
                    safeClass.add();
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println(e);
                }
            }
        }
    }
    
    
    public class SafeThreadTest{
    
    
        public static void main(String[] args){
            System.out.println("SafeThreadTest-------------------");
    
    
            Thread[] t1 = new Thread[3];
    
            for(int i=0;i<t1.length;i++){
                t1[i] = new Thread(new TestUnSafeThread());
                t1[i].start();
            }
    
            Thread[] t2 = new Thread[3];
            for(int i=0;i<t2.length;i++){
                t2[i] = new Thread(new TestSafeThread());
                t2[i].start();
            }
        }
    }
    

    程序输出结果

    输出

    由结果可以看出来,线程不安全的类读写存在脏读的情况(不一定每次都会出现),而线程安全的类(即加锁)读取没有存在脏读的情况。

    对象的无状态与有状态

    • 有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。

    • 无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象.不能保存数据,是不变类,是线程安全的。

    // 无状态对象
    class StatelessClass {
       public void method(){
          //do something
          ....
       }
    }
    
    // 有状态对象
    class stateClass{
        private int count = 0;  // 对象的状态变量
    
        public void method(){
          count ++;  // 对象的变量变化,在多线程的时候不安全。
      }
    }
    

    利用无状态的技术有单例模式,这样可以共享实例,提高性能。有状态的Bean,多线程环境下不安全,那么适合用Prototype原型模式。Prototype: 每次对bean的请求都会创建一个新的bean实例。 有状态的bean都使用prototype作用域,而对无状态的bean则应该使用singleton作用域。

    关于volatile

    对于非volatile类型的long和double变量,jvm允许将64位的读操作或写操作分解为两个32位的操作。当读取一个非volatile类型的long变量是,如果对改变量的读操作和写操作在不同的线程中执行,那么很可能会读取到某个值的高32位和另一个值的低32位。因此,即使不考虑失效数据问题(就是读取到了“脏数据”),在多线程程序中使用共享且可变的long和double等类型的变量也是不安全的,除非用关键字volatile来声明或者用锁保护起来。

    当把变量声明为volatile类型后,编译器与运行的虚拟机都会注意到这个变量是共享的,因此不会讲改变量上的操作与其他内存操作一起重排序。在访问volatile变量是不会执行加锁操作,因此也就不会是执行线程阻塞,所以volatile变量是一个种比sychronized关键字更轻量级的同步机制。

    volatile变量一般用于某个状态标记变量,volatile变量不能确保数据的原子性,只能确保可见性(内存可见性)

    相关文章

      网友评论

          本文标题:线程安全

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