JMM(Java内存模型Java Memory Model,简称JMM) 本身是一种抽象的概念并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
JMM关于同步的规定:
1、线程解锁前,必须把共享变量的值刷新回主内存
2、线程加锁前,必须读取主内存的最新值到自己的工作内存
3、加锁解锁是同一把锁
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中你那个进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写会主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工资内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图:
import java.util.concurrent.TimeUnit;
class MyData {
volatile int number = 0;
// int number = 0;
public void addT060(){
this.number = 60;
}
}
public class VolatileDemo {
public static void main(String[] args){
MyData myData = new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "\t start");
try{ TimeUnit.SECONDS.sleep(3);} catch(InterruptedException e){e.printStackTrace();}
myData.addT060();
System.out.println(Thread.currentThread().getName() + "\t updated numer value :"
+ myData.number);
},"one").start();
//此线程为main线程
while (myData.number == 0){
}
System.out.println(Thread.currentThread().getName() + ":over");
}
}
当 volatile int number = 0时:
//one start
//one updated numer value :60
//main over
//此处证明了 加了volatile后的保证可见性的效果,当 one 线程开始调用addT060()方法时把 60
//赋值给 number 顺带着 通知其他一起调用 number变量 的线程 number值已经变成60了
当 int number = 0时:
//one start
//one updated numer value :60
//停在while 死循环 不会走下去了 因为 main函数的线程 里的私有数据区域里的number 永远为0
volatile是java虚拟机提供的轻量级的同步机制
1️⃣、保证可见性
2️⃣、不保证原子性
3️⃣、禁止指令重排
JMM三大特性
1️⃣、可见性
2️⃣、原子性
不可分割、完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者分割。需要整体完整,要么同时完成,要么同时失败。(数据的完整性)
3️⃣、有序性
CAS算法(Compare-And-Swap)是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问
算法保证数据的原子性
CAS算法是硬件对于并发操作共享数据的支持
CAS是一种无所的非阻塞算法的实现
CAS包含了三个操作:
内存值V
预估值A
更新值B
当且仅当 V == A时,V = B ,否则将不进行操作。
简单来说这是分两步操作:
第一步读取内存值赋值给V
第二步是同时读取内存值复制给A 并且 判断 V 是否 等于A 等于把B的值赋值到主内存,否则不进行操作
public class TestAtomicDemo{
public static void main(String[] args){
AtomicDemo ad = new AtomicDemo();
for(int i = 0; i < 10; i++){
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable{
private AtomicInteger serialNumber = new AtomicInteger();
public void run (){
try{
Thread.sleep(200);
}catch(InterruptedException e){
}
}
System.out.println(getSerialNumber());
}
public int getSerialNumber(){
//获取和自增
return serialNumber.getAndIncrement();
}
4-CountDownLatch闭锁
java5.0在 java.util.concurrent 包中提供了多种并发容器类来改进同步容器的性能。
CountDownLatch一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行:
确保某个计算在其需要的所有资源都被初始化之后才继续执行;
确保某个服务在其依赖的所有其他服务都已经启动之后才启动;
等待直到某个操作所有参与者都准备就绪再继续执行
CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待
package src;
import java.util.concurrent.CountDownLatch;
public class TestCountDownLatch {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(5);
LatchDemo ld = new LatchDemo(latch);
long start = System.currentTimeMillis();
for (int i = 0; i < 5; i++) {
new Thread(ld).start();
}
try {
//等待线程完成
latch.await();
} catch (InterruptedException e) {
}
long end = System.currentTimeMillis();
System.out.println("耗费时间:" + (end - start));
}
}
class LatchDemo implements Runnable {
private CountDownLatch latch;
public LatchDemo(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
synchronized (this){
try {
for (int i = 0; i < 50000; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
} finally {
//递减1
latch.countDown();
}
}
}
}
生产者与消费者模式Sychronized锁
// 生产者和消费者案例
public class TestProductorAndConsumer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor productor = new Productor(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(productor, "生产者A").start();
new Thread(consumer, "消费者B").start();
new Thread(productor, "生产者C").start();
new Thread(consumer, "消费者D").start();
}
}
//店员
class Clerk {
private int product = 0;
//进货
public synchronized void get() { //循环次数:0
while (product >= 10) { //为了避免虚假唤醒问题,应该总是使用在循环中
System.out.println("库存已满,不再进货");
try {
//等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//进货库存 + 1
System.out.println(Thread.currentThread().getName() + ":" + ++product);
//唤起
this.notifyAll();
}
//出售
public synchronized void sale() {
while (product <= 0) {
System.out.println("暂无库存,不进行出售");
try {
//等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//出售库存 -1
System.out.println(Thread.currentThread().getName() + ":" + --product);
this.notifyAll();
}
}
//生产者
class Productor implements Runnable {
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
// try {
// Thread.sleep(200);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// //
clerk.get();
}
}
}
//消费者
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
// 购买
clerk.sale();
}
}
}
CyclicBarrier
CyclicBarrier的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法。
Semaphore
信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。