1. Java内存区域 or JVM内存模型

 

 

方法区:属于线程共享区域,主要存储被虚拟机加载的类信息、静态变量、常量等。当方法区无法满足内存分配需求时将抛出outOfMemoryError

jvm堆:属于线程共享区域,在虚拟机启动时创建,是java虚拟机所管理的内存中最大的一块,主要存放对象实例注意java堆是垃圾回收器管理主要区域-OutOfMemoryError

虚拟机栈:属于线程私有区域,每个线程创建时都会创建一个虚拟机栈,其内部保存一个个栈帧,对应着一次次的java调用。虚拟机栈主管java程序运行,保存方法的局部变量、部分结果并参与方法的调用和返回

      通过-Xss选项设置线程的最大栈空间,栈的大小直接决定函数调用最大深度 -stackoverflow 

本地方法栈:属于线程私有区域,主要与虚拟机用到的native方法关联。 - stackoverflow

程序计数器:属于线程私有区域,通过改变这个值来选取下一条需要执行字节码指令

 

堆vs栈:

堆可以理解存储单位,解决数据存储问题:即数据怎么存,存到哪里

栈式运行时单位解决程序运行问题:程序如何运行,如何处理数据

 

2. JMM(java内存模型)

2.1 JMM内存模型

(1)主内存:是共享内存区域,所有变量都存在主内存中,所有线程都可以访问;
(2)工作内存线程操作变量必须先从主内存中读取变量信息到工作内存,然后在工作内存中进行修改,最后再写回主内存中,修改才会生效;

    线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中变量;这里的工作内存时JMM的一个抽象概念,也叫本地内存(ThreadLocal),其存储了该线程读/写共享变量的副本

注意JMM和java内存区域是不同的概念,JMM和java内存区域相似点式都存在共享数据区域和私有区域,在JMM中主内存属于共享数据区域,从某种程度上对应java内存区域的方法区和堆;而工作内存是线程私有的,从某种程度上对应java内存区域的程序计数器、虚拟机栈和本地方法栈

 

 

 不同线程之间无法直接访问对方线程工作内存中的变量,如果线程A想要和线程B通信,只能通过主内存进行

2.2 JMM三个特征:

  原子性:一个操作不能被打断,要么全部执行完毕,要么不执行。在这点上有点类似于事务操作,要么全部执行成功,要么回退到执行该操作之前的状态。

    比如对于一个静态变量int x,两条线程同时对他赋值,线程A赋值为1,而线程B赋值为2,不管线程如何运行,最终x的值要么是1,要么是2,线程A和线程B间的操作是没有干扰的,这就是原子性操作,不可  被中断的特点

  有序性:单线程程序里代码是从前往后执行,但对于多线程并发执行出现乱序

  可见性:一个线程对共享变量做了修改之后,其他的线程立即能够看到(感知到)该变量的这种修改(变化)。

 2.3 JMM基于原子性、有序性、可见性提供的解决方案:

原子性:synchronized关键字

有序性:sychronized、voliatile关键字

可见性:voliatile关键字

3. Volatile

volatile关键字可以保证共享变量可见性和有序性。

public class VolatileTest {
    private static  boolean val = true;            ///①
//    private static volatile boolean val = true;  ///②
    public static void main(String[] args) {
        new VolatileTest().new First().start();
        new VolatileTest().new Second().start();
    }

    class First extends Thread {
        @SneakyThrows
        @Override
        public void run() {
            while (true) {
                if (val) {
                    System.out.println("Thread one is working."+System.currentTimeMillis());
                    val = false;
                }
            }
        }
    }

    class Second extends Thread {
        @SneakyThrows
        @Override
        public void run() {
            while (true) {
                if (!val) {
                    System.out.println("Thread two is working."+System.currentTimeMillis());
                    val = true;
                }
            }
        }
    }
}

 

private static  boolean val = true; 輸出結果如下,某一时刻结果会卡住不在输出,因为线程1、2对val改动结果没有及时刷回主内存,val条件不满足线程1、2运行条件,结果便不再打印

Thread two is working.1660620905273
Thread one is working.1660620905273
Thread two is working.1660620905273
Thread one is working.1660620905273
Thread two is working.1660620905273
Thread one is working.1660620905273
Thread two is working.1660620905273
Thread one is working.1660620905273
Thread two is working.1660620905273



private static volatile boolean val = true;  输出结果如下,线程1、2会持续输出,因为线程1、2分别对val改动对方都可以感知

3581
Thread one is working.1660620633581
Thread two is working.1660620633581
Thread one is working.1660620633581
Thread two is working.1660620633581
Thread one is working.1660620633581
Thread two is working.1660620633581
Thread one is working.1660620633581
Thread two is working.1660620633581
Thread one is working.1660620633581
Thread two is working.1660620633581
Thread one is working.1660620633581

 

3.1 volatile不能保证原子性

public class VolatileAtomicDemo {
    private volatile static int count=0;
    private static AtomicInteger sum=new AtomicInteger(0);

    @SneakyThrows
    public static void main(String[] args) {
        for(int i=0;i<500;i++){
            Thread thread=new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int j=0;j<10000;j++) {
                        count++;
                        sum.incrementAndGet();
                    }
                }
            });
            thread.start();
        }
        Thread.sleep(2000);
        System.out.println("count:"+count);
        System.out.println("summm:"+sum);
    }
}

输出结果;

count:1792884
summm:5000000

 

4. JMM中的happens-before原则

程序开发中,仅依靠sychronized和volatile关键字保证原子性、可见性、有序性,则代码比较麻烦,Java内存模型自身提供了happens-before原则来辅助保证程序执行的原子性、可见性、有序性的问题,

它是判断数据是否存在竞争、线程是否安全的依据,happens-before 原则内容如下

a)程序顺序原则,即在一个线程内必须保证语义串行性,也就是说按照代码顺序执行。

b) 锁规则 解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前,也就是说,如果对于一个锁解锁后,再加锁,那么加锁的动作必须在解锁动作之后(同一个锁)。

c) volatile规则 volatile变量的写,先发生于读,这保证了volatile变量的可见性,简单的理解就是,volatile变量在每次被线程访问时,都强迫从主内存中读该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存,任何时刻,不同的线程总是能够看到该变量的最新值。

d) 线程启动规则 线程的start()方法先于它的每一个动作,即如果线程A在执行线程B的start方法之前修改了共享变量的值,那么当线程B执行start方法时,线程A对共享变量的修改对线程B可见

e) 传递性 A先于B ,B先于C 那么A必然先于C

f) 线程终止规则 线程的所有操作先于线程的终结,Thread.join()方法的作用是等待当前执行的线程终止。假设在线程B终止之前,修改了共享变量,线程A从线程B的join方法成功返回后,线程B对共享变量的修改将对线程A可见。

g) 线程中断规则 对线程 interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测线程是否中断。

h) 对象终结规则 对象的构造函数执行,结束先于finalize()方法

 

参考文献:

https://blog.csdn.net/javazejian/article/details/72772461?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-72772461-blog-119212455.pc_relevant_aa&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-72772461-blog-119212455.pc_relevant_aa&utm_relevant_index=1

posted on 2022-08-16 11:38  colorfulworld  阅读(138)  评论(0编辑  收藏  举报