Java基础知识总结(77)

发布于:2024-04-29 ⋅ 阅读:(26) ⋅ 点赞:(0)

 * 2、JMM模型

    JMM(Java Memory Model):Java 内存模型,是 Java 虚拟机规范中所定义的一种内存模型,Java 内存模型是标准化的,屏蔽掉了底层不同计算机的区别。也就是说,JMM 是 JVM 中定义的一种并发编程的底层模型机制。

   

    JMM内存模型规定详解如下:

   

        所有的共享变量都存储于主内存。这里所说的变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。

            每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。

            线程对变量的所有的操作(读,写)都必须在工作内存中完成,而不能直接读写主内存中的变量。

            不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存中转来完成。

   

        总结:

   

            1. 每个线程都有自己的私有内存(工作内存)

            2. 线程对共享变量的操作只能在私有内存中进行,操作完成后写入到主内存中

            3. 共享比那里都存储在主内存中。

   

        假设此时内存中现在有两个线程在运行,如果A、B两个线程之间要通信,必须经历以下2个步骤

   

            线程A把本次内存中更新过的共享变量刷新到主内存中去。

            线程B从主内存中读取线程A已经更新过的共享变量。

            然而,JMM 这样的规定可能会导致线程对共享变量的修改没有即时更新到主内存,或者线程没能够及时将共享变量的最新值同步到工作内存中,从而使得线程在使用共享变量的值时,该值并不是最新的。

   

        JMM特性:

   

            1.原子性:

                一个或者多个操作不可分割,要么全部执行,并且执行过程中不会被任何因素打断,要么就都不执行。Java中可以使用synchronized保证原子性。

            2.可见性:

                是指当一个线程修改了某一个共享变量的值时,其他线程是否能够立即知道这个修改。显然,对于串行程序来说,可见性问题是不存在的。因为你在任何一个操作步骤中修改了某个变量,在后续的步骤中读取这个变量的值时,读取的一定是修改后的新值。Java中的volatile、synchronized、Lock都能保证可见性。如一个变量被volatile修饰后,表示当一个线程修改共享变量后,其会立即被更新到主内存中,其他线程读取共享变量时,会直接从主内存中读取。而synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

            3.有序性:

                对于一个线程的执行代码而言,我们总是习惯性地认为代码是从前往后依次执行的。这么理解也不能说完全错误,因为就一个线程内而言,确实会表现成这样。但是,在并发时,程序的执行可能就会出现乱序。给人的直观感觉就是:写在前面的代码,会在后面执行。听起来有些不可思议,有序性问题的原因是程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。

                在单核CPU的场景下,当指令被重排序之后,编译器和CPU都需要遵守As-if-Serial规则,无论如何重排序,都必须保证代码在单线程下运行正确

   

    3、synchronized关键字

       

        synchronize是一个修饰符,表示同步的意思,可以修饰方法,也可以修饰代码块。修饰的方法叫做同步方法,修饰的代码块叫做同步代码块,被synchronized修饰的方法或则代码,也叫做临界区,临界区的特点在同一时刻有且只能有一个线程访问。

/**

 * 线程不加锁可能会出现值不同步的情况

   */

   public class SynchronizedDemo {

   //静态成员变量 在主内存中

   static int i;

   //静态成员方法

   public static void add(){

       i++;

   }

   public static void main(String[] args) throws InterruptedException {

       Thread t1 = new Thread(()->{

           for (int j = 0; j < 100; j++) {

               //t1线程调用add方法,先从主内存中复制变量i到本地内存中

               //执行逻辑代码

               //将执行产生的新结果写入到主内存中

               add();

           }

       });

       Thread t2 = new Thread(()->{

           for (int j = 0; j < 100; j++) {

               add();

           }

       });

       //启动t1线程

       t1.start();

       //启动t2线程

       t2.start();

       //休眠main线程,让其他线程优先执行

       TimeUnit.SECONDS.sleep(1);

       //由于线程没有加锁,故线程t1在进行数据的逻辑处理时,由于分配的时间片结束用完,导致线程t1出现死锁的情况,线程t2先执行。会出现值不同步的非线程安全的情况

       System.out.println(i);

   }

   }