• Java中的线程安全和非线程安全


    线程安全:就是当多线程访问时,采用了加锁的机制;即当一个线程访问该类的某个数据时,会对这个数据进行保护,其他线程不能对其访问,直到该线程读取完之后,其他线程才可以使用。防止出现数据不一致或者数据被污染的情况。
    线程不安全:就是不提供数据访问时的数据保护,多个线程能够同时操作某个数据,从而出现数据不一致或者数据污染的情况。
    简单而言,非线程安全是指多线程操作同一个对象可能会出现问题。而线程安全则是多线程操作同一个对象不会有问题。

    存在线程安全问题必须满足三个条件:
    1.有共享变量
    2.处在多线程环境下
    3.共享变量有修改操作。

    线程安全工作原理:
    jvm中有一个Main Memory,每一个线程也有自己的Working Memory,一个线程对于一个变量variable进行操作的时候,都需要在自己的working memory里创建一个变量的副本copy,操作完之后再写入main memory。
    当多个线程操作同一个变量variable,就可能出现不可预知的结果。

    -------------------------------------------------------------------------------------------------------------

    note:

    1.主内存(Main Memory)
    主内存可以简单理解为计算机当中的内存,但又不完全等同。主内存被所有的线程所共享,对于一个共享变量(比如静态变量,或是堆内存中的实例)来说,主内存当中存储了它的“本尊”。
    2.工作内存(Working Memory)
    工作内存可以简单理解为计算机当中的CPU高速缓存,但又不完全等同。每一个线程拥有自己的工作内存,对于一个共享变量来说,工作内存当中存储了它的“副本”。

    线程对共享变量的所有操作都必须在工作内存进行,不能直接读写主内存中的变量。不同线程之间也无法访问彼此的工作内存,变量值的传递只能通过主内存来进行。
    (因为直接操作主内存太慢,所以jvm才利于性能比较高的工作内存,可以类比CPU、高速缓存和内存)

    Java内存模型(JMM,Java Memory Model)如图:

    -------------------------------------------------------------------------------------------------------------

    例:开启10个线程,每个线程当中让静态变量count自增100次。执行之后会发现,最终count的结果值未必是1000,有可能小于1000。(这里count是线程间的共享变量)

    public class Test {
        public static int count = 0;
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            //开启10个线程
            for(int i = 0; i < 10; i++){
                new Thread(
                        new Runnable(){
                            public void run(){
                                try{
                                    Thread.sleep(1);
                                }
                                catch(InterruptedException e){
                                    e.printStackTrace();
                                }
                                //每个线程中让count自增100次
                                for(int j = 0; j < 100; j++){
                                    count++;
                                }
                            }
                        }).start();
            }
            try{
                Thread.sleep(2000);
            }
            catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("count= "+count);
        }
    }
    //运行结果:count= 952

    假设count=2时,当线程1在将count++的结果(count=3)写入内存之前,线程2已经从内存中读取了count的值,并在这个值(count=2)上进行++操作,先于线程1将count=3写入了内存,这是线程1再将count=3写入内存,就存在错误了。

    使用synchronized同步方法改进:

    public class VolatileTest {
        public static int count = 0;
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            // 开启10个线程
            for (int i = 0; i < 10; i++) {
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // 每个线程中让count自增100次
                        for (int j = 0; j < 100; j++) {
                            count(); //count++;
                        }
                    }
                }).start();
            }
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("count= " + count);
        }
    
        public static synchronized void count() {
            count++;
        }
    }

    运行结果:

    Java中实现线程安全的方法:
    1. 最简单的方式,使用Synchronization关键字
    用synchronized的关键是建立一个监控(monitor),这个monitor可以是要修改的变量,也可以是其他自己认为合适的对象(方法),然后通过给这个monitor加锁来实现线程安全,每个线程在获得这个锁之后,要执行完加载(load)到working memory再到使用(use) && 指派(assign)到 存储(store)再到main memory的过程。才会释放它得到的锁。这样就实现了所谓的线程安全。
    2. 使用java.util.concurrent.atomic 包中的原子类,例如 AtomicInteger
    3. 使用java.util.concurrent.locks 包中的锁
    4. 使用线程安全的集合ConcurrentHashMap
    5. 使用volatile关键字,保证变量可见性(直接从内存读,而不是从线程cache读)
    参考:

    https://blog.csdn.net/u011389474/article/details/54602812
    https://www.zhihu.com/question/49855966
    java 线程安全 synchronized 
    漫画:什么是volatile关键字?(整合版)

  • 相关阅读:
    用例的粒度问题
    REST和RPC最大区别
    成功的结对编程要点
    我认为技术经理应该做的事儿
    敏捷测试实践
    DDD-围绕业务逻辑编程
    依赖反转原则
    Kafka和Rabbitmq的最大区别
    Cassandra快速两次写入导致顺序不对的问题
    DotNetBar之SupergridControl显示图片,行距自动调整
  • 原文地址:https://www.cnblogs.com/zeroingToOne/p/9524543.html
Copyright © 2020-2023  润新知