基础:Java内存模型(JMM)

一、简介

创建一个对象需要分配内存空间,不需要该对象时及时回收,这个内存的控制是由Java虚拟机完成的

  • 所有的共享变量都存储于主内存,共享变量:实例变量和类变
  • 每一个线程存在自己的工作内存,局部变量属于线程私有,线程对变量的所有操作(读/写)都必须在工作内存中进行,不能直接读写主内存中的变量
  • 不同线程之间无法直接访问对方工作内存中的变量,Java 线程间的通信采用的是共享内存方式

线程间通信方式

  • 消息传递
  • 共享内存

二、原子性、可见性、有序性

1.原子性

定义:一个或多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就不执行

原子性是拒绝多线程操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作

  • 对基本数据类型的变量的读取和赋值操作是原子性操作
  • 变量之间的相互赋值不是原子操作
  • Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronizedLock来实现。

2.可见性

定义:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

Java提供了volatile关键字来保证可见性,当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

通过synchronizedLock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

   static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    if (!flag) {
                        break;
                    }
                }
            }
        });
        thread.start();
        Thread.sleep(1000);
        flag = false;
    }

在这个例子中,通过主线程中修改flag标志位,让线程thread退出循环

但结果却不会退出while循环

因为线程thread最开始从主内存读取flag的值为true,然后JIT编译器会将读取的值存入线程thread的工作内存中,thread线程读取会直接从工作内存中那值,在主线程中对flag进行修改,thread线程并不会去读取主内存的值,直接从缓存中拿

解决办法:使用 volatile 

  • 线程操作 volatile 变量都是直接操作主存

3.有序性

定义:程序执行的顺序按照代码的先后顺序执行

指令重排序:一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

指令重排序不会影响单个线程的执行,但是会影响线程并发执行的正确性

总结:

        要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确

三、如何解决可见性——synchronized和volatile

1.用synchronized加锁

当一个线程进入synchronized代码块后,线程会获得锁,清空工作内存,从主内存中拷贝共享变量最新的值到工作内存中存为副本,执行代码块后,将修改的副本的值刷新回主内存中,线程释放锁

其他获取锁失败的线程,会进入阻塞,等待释放锁,这样就保证了当前获取的变量的值是最新的。

产生问题:加锁不可避免的会涉及到线程切换,会增加系统开销

2.volatile修饰变量

  • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  • 禁止进行指令重排序

volatile修饰的变量是在主存上进行读写操作,所以可以保证可见性

四、volatile和synchronized的区别

  • volatile只能修饰实例变量和类变量,而synchronized可以修饰方法,以及代码块

  • volatile保证数据的可见性,但是不保证原子性(多线程进行写操作,不保证线程安全);而synchronized可以保证原子性

  • volatile用于禁止指令重排序:可以解决单例双重检查对象初始化代码执行乱序问题。

  • 如果是对一个共享变量进行多个线程的赋值,而没有其他的操作,可以用volatile来代替synchronized,因为赋值本身是有原子性的,而volatile保证了可见性,所以就可以保证线程安全

  • volatile是无锁操作,所以性能比synchronized更好

五、synchronized和Lock

  • synchronized是关键字,属于JVM层面,而Lock是一个接口,属于JDK层面
  • synchronized会主动释放锁,但是Lock必须手动释放
  • synchronized具有原子性,所以是不可中断的,而Lock可中断也可以不中断
  • synchronized可以锁住代码块和方法,而Lock只能锁住方法
  • synchronized是非公平锁,ReentrantLock可以控制是否是公平锁。

猜你喜欢

转载自blog.csdn.net/weixin_42277946/article/details/130713638