目录
2.修饰静态方法,锁的是当前class对象(全局唯一,相当于把类锁上了)
JMM是描述线程的工作内存(概念,并不真实存在,就是一系列CPU的寄存器或高速缓存)和主内存(真实存在的RAM)之间的关系。
每个线程都有自己的工作内存,当访问共享变量时(类中的成员变量,常量,静态变量),会先将主内存中的共享变量值拷贝一份放到线程自己的工作内存中,之后对此共享变量的读取操作都是在当前工作内存中进行的。
当两个进程同时访问同一共享变量时,会出现各种各样的问题,因为每个线程实际上都是将共享变量加载到自己的工作内存中进行各种操作的,这就导致了,各个线程之间的工作内存可能会出差错,有可能数据在主内存更新后,某一个线程的工作内存仍保存着之前还未更新的数据,并且用这个数据进行操作再写回主内存。也有可能,其他线程此刻还没有加载共享变量,此时从主内存读共享变量值有可能还没读到新修改的值。(脏读)
一个线程对于共享变量的修改可以让其他线程立刻感知并可见(synchronized-上锁,volatile关键字,final关键字也可以保证可见性)
一个操作在进行中,无法被中断和打扰称为原子性,例如:
int i = 10; 这行代码就是原子性的,因为它顺时发生,没有被打断的机会。
i++ ; 就不是原子性的,因为在它加的时候可能会被别的线程读取。
简单理解来说就是,在一个方法中,不影响最终结果的代码顺序是随意的,这种现象称为指令重排,但是如果在多线程中则会出现各种问题,例如此图:

synchronized-监视器锁 monitor lock
举一个不是很恰当的例子,我们可把每一个线程比作一个人,把上锁的代码块比作厕所,若这些个线程需要上同一个厕所,那么可以假设先进去一个线程,进去之后线程就会把这个厕所的门锁上,这期间别的线程想进也是进不去的,这个就是线程间的互斥现象,只有等这个线程出来才能进去。
如果线程间不需要抢同一个厕所,那么就可以说是不存在互斥问题,因为一个线程进一个厕所上不同的锁。

当某个线程需要获取对象的锁时,其他线程若也要获取同一个线程的锁,就会处于阻塞状态。
一定要注意,互斥现象只发生在线程对于同一对象的操作,不同的对象间是不存在互斥现象的。

每个线程进入synchronized代码块,都会尝试执行加锁操作。
退出代码块的时候,就是释放这个锁。

线程执行synchronized代码块的流程
a.获取对象锁
b.从主内存拷贝变量值到工作内存
c.执行代码
d.将更改后的值写回主内存
e.释放对象锁
因为在同一时间内,只有一个线程能进入上锁代码,保证互斥,此时这个代码块是一个单线程操作,不涉及线程不安全的问题。所以上锁操作也是天然的原子性和可见性的体现。



此时只要调用此类对象,同一时间只有一个线程可以执行increase2方法

锁的粒度更细,用的最多,只要在需要锁上的若干代码加上synchronzied关键字即可
我们可以通过this锁定每一个对象

也可以通过.class直接锁定整个类

甚至可以自己来传递参数,自定义线程间的互斥关系

我们之前学习的集合类在多线程中都是不安全的

所以为了保证线程安全,我们可以使用如下几种类:

这些类全部来自java.util.concurrent类(Java并发工具包)

这些类可以保证线程安全,例如:
ConcurrentHashMap类:把方法全部上了锁

volatile关键字可以强制线程读写主内存的变量值
相当于普通的共享变量,此关键字可以保证共享变量的可见性

a.当线程读取的是此关键字修饰的内容时,线程直接从主内存读取该值到工作内存,无论当前工作内存是否由该值
b.当线程写此关键字变量时,将当前修改后的变量值立刻从工作内存中刷新到主内存,并且在此过程中其他线程会等待(不是阻塞),直到写回主内存的操作完成,保证读的一定是刷新后的值
对于同一个volatile变量,他的写操作一定是发生在读操作前的,保证读到的是主内存刷新后的数据。
volatile只能保证可见性,不能保证原子性,所以如果线程不是原子性操作依旧不安全。
volatile修饰的代码可以防止指令重排,也就是它一定是在前面的代码执行结束后,后面的代码执行前执行,无论CPU觉得哪种方式更优,volatile修饰的代码执行位置是固定的。
