文档章节

Java多线程之内存可见性

w
 wall--e
发布于 2016/05/07 17:06
字数 1342
阅读 204
收藏 11

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

Java内存模型( JMM ) :

    1) 所有的变量都存储在主内存中

    2) 每个线程都有自己独立的工作内存, 里面保存该线程使用到的变量的副本 ( 主内存中该变量的一份拷贝 )

JMM 两条规定:

    1) 线程对共享变量的所有操作都必须在自己的工作内存中进行

    2) 不同线程之间无法直接访问其他线程工作内存中的共享变量, 线程间共享变量值的传递必须通过主内存 

线程间共享变量可见性实现的原理:

    线程A 对共享变量的修改想被线程B 及时看到, 必须要经过以下2个步骤:

        1) 把线程A 工作内存中更新过的共享变量刷新到主内存中( store )

        2) 将主内存中最新的共享变量的值共享到线程B 工作内存中( load )

 

Java 语言层面支持的可见性实现方式:

    1) synchronized

    2) volatile

JUC 包下的类也可以实现可见性

    1) Atomic

    2) ReentrantLock

    3) Semaphore

 

1. synchronized 实现可见性

    JMM 关于 synchronized 的两条规定:

        1) 线程释放锁前, 必须把共享变量的最新值从该线程的工作内存刷新到主内存中

        2) 线程持有锁时, 将清空该线程工作内存中共享变量的值, 从主内存中读取最新的值

 

synchronized 实现可见性的原因:

    线程释放锁前对共享变量的修改在下次持有锁时对其他线程可见

public class SynchronizedDemo {
	// 共享变量
	private int result = 0;

	// 共享变量执行自增操作
	public synchronized void increase() {
		result++;
	}

	public int getResult() {
		return result;
	}

	public static void main(String[] args) throws InterruptedException {
		final SynchronizedDemo demo = new SynchronizedDemo();
		// 设置启动500个线程
		int count = 500;
		// 创建一个 JUC 包下的同步同步计数器, 设置计数次数为线程数500
		final CountDownLatch cdl = new CountDownLatch(count);
		for (int i = 0; i < count; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					demo.increase();
					cdl.countDown();
				}
			}).start();
		}
		cdl.await();
		System.out.println(demo.getResult());
	}
}

Console 输出: 500

synchronized 不仅可以实现可见性, 还可以实现原子性

 

2. volatile 实现可见性

 

volatile 如何实现可见性:

    通过加入内存屏障和禁止指令重排序

    1) 对 volatile 变量执行写操作时, 会在写操作后加入一条 store 屏障指令

    2) 对 volatile 变量执行读操作时, 会在读操作前加入一条 load 屏障指令

 

public class VolatileDemo {
	// 使用 volatile 修饰共享变量
	private volatile int result = 0;

	// 共享变量 result 执行自增操作, 无法保证原子性
	public void increase() {
		result++;
	}

	public int getResult() {
		return result;
	}

	public static void main(String[] args) throws InterruptedException {
		final VolatileDemo demo = new VolatileDemo();
		// 设置启动500个线程
		int count = 500;
		// 创建一个 JUC 包下的同步同步计数器, 设置计数次数为线程数500
		final CountDownLatch cdl = new CountDownLatch(count);
		for (int i = 0; i < count; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					demo.increase();
					cdl.countDown();
				}
			}).start();
		}
		cdl.await();
		System.out.println(demo.getResult());
	}
}

    Console 输出: 498

volatile 关键字, 能保证 volatile 变量的可见性, 不能保证 volatile 变量操作的原子性( 如 ++/-- )

 

3. AtomicInteger 实现可见性

    用 Atomic 类实现共享变量在线程中的原子性( 通过 CAS, 自旋 实现)

public class AtomicIntegerDemo {
	// 共享变量
	private AtomicInteger result = new AtomicInteger(0);

	// 使用 Atomic 类的 incrementAndGet() 方法( 原子操作 ), 实现自增
	public void increase() {
		result.incrementAndGet();
	}

	public int getResult() {
		return result.get();
	}

	public static void main(String[] args) throws InterruptedException {
		final AtomicIntegerDemo demo = new AtomicIntegerDemo();
		// 设置启动500个线程
		int count = 500;
		// 创建一个JUC包下的同步同步计数器, 设置计数次数为线程数500
		final CountDownLatch cdl = new CountDownLatch(count);
		for (int i = 0; i < count; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					demo.increase();
					cdl.countDown();
				}
			}).start();
		}
		cdl.await();
		System.out.println(demo.getResult());
	}
}

Console 输出: 500

 

4. JUC 包下的 Lock 实现可见性

    用 ReentrantLock 实现共享变量在线程中的原子性

public class LockDemo {
	// 共享变量
	private int result = 0;
	// 可重入锁
	private Lock lock = new ReentrantLock();

	// 使用锁机制, 保证锁内代码的原子性
	public void increase() {
		// 加锁
		lock.lock();
		try {
			result++;
		} finally {
			// 释放锁
			lock.unlock();
		}
	}

	public int getResult() {
		return result;
	}

	public static void main(String[] args) throws InterruptedException {
		final LockDemo demo = new LockDemo();
		// 设置启动500个线程
		int count = 500;
		// 创建一个JUC包下的同步同步计数器, 设置计数次数为线程数500
		final CountDownLatch cdl = new CountDownLatch(count);
		for (int i = 0; i < count; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					demo.increase();
					cdl.countDown();
				}
			}).start();
		}
		cdl.await();
		System.out.println(demo.getResult());
	}
}

Console 输出: 500

 

5. Semaphore 实现可见性

    用信号量机制实现共享变量在线程中的原子性

public class SemaphoreDemo {
	// 共享变量
	private int result = 0;
	// 初始化信号量为1, 一次只能有1个线程访问共享变量, 相当于互斥锁
	private Semaphore semaphore = new Semaphore(1);

	// 使用信号量机制, 保证共享变量自增操作的原子性
	public void increase() {
		try {
			// 获取1个信号量
			semaphore.acquire();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		result++;
		// 释放1个信号量
		semaphore.release();
	}

	public int getResult() {
		return result;
	}

	public static void main(String[] args) throws InterruptedException {
		final SemaphoreDemo demo = new SemaphoreDemo();
		// 设置启动500个线程
		int count = 500;
		// 创建一个JUC包下的同步同步计数器, 设置计数次数为线程数500
		final CountDownLatch cdl = new CountDownLatch(count);
		for (int i = 0; i < count; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					demo.increase();
					cdl.countDown();
				}
			}).start();
		}
		cdl.await();
		System.out.println(demo.getResult());
	}
}

Console 输出: 500

 

总结:

synchronized 代码块具备 可见性 和 可见性

volatile 变量具备可见性, 不具备原子性

正确使用 volatile 变量

     

© 著作权归作者所有

w
粉丝 7
博文 31
码字总数 25016
作品 0
东城
程序员
私信 提问
加载中

评论(1)

b
base_
信号量不是同步的,共享变量无法得到预期结果。不知道你有没有测试过
求你了,再问你Java内存模型的时候别再给我讲堆栈方法区了…

GitHub 4.1k Star 的Java工程师成神之路 ,不来了解一下吗? GitHub 4.1k Star 的Java工程师成神之路 ,真的不来了解一下吗? GitHub 4.1k Star 的Java工程师成神之路 ,真的确定不来了解一下吗...

Hollis
07/02
0
0
java多线程详解一线程的内存模型和线程特性

这几天面试互联网公司的高级java工程师,多线程问的相对而言比较多。所以,从各种角度来看看java中多线程的实现方式。 一.Java多线程中的内存模型 1.java主内存和工作内存 根据java内存模型,...

onedotdot
2018/07/14
36
0
再有人问你Java内存模型是什么,就把这篇文章发给他!

前几天,发了一篇文章,介绍了一下JVM内存结构、Java内存模型以及Java对象模型之间的区别。有很多小伙伴反馈希望可以深入的讲解下每个知识点。Java内存模型,是这三个知识点当中最晦涩难懂的...

技术小能手
2018/09/30
0
0
再有人问你Java内存模型是什么,就把这篇文章发给他。

前几天,发了一篇文章,介绍了一下JVM内存结构、Java内存模型以及Java对象模型之间的区别。有很多小伙伴反馈希望可以深入的讲解下每个知识点。Java内存模型,是这三个知识点当中最晦涩难懂的...

Java架构
2018/07/11
0
0
再有人问你Java内存模型是什么,就把这篇文章发给他

网上有很多关于Java内存模型的文章,在《深入理解Java虚拟机》和《Java并发编程的艺术》等书中也都有关于这个知识点的介绍。但是,很多人读完之后还是搞不清楚,甚至有的人说自己更懵了。本文...

java高级架构牛人
2018/07/04
22
2

没有更多内容

加载失败,请刷新页面

加载更多

在C语言中“静态”是什么意思?

我已经在C代码的不同地方看到了static一词。 这就像C#中的静态函数/类(实现在对象之间共享)吗? #1楼 多文件变量作用域示例 在这里,我说明了静态如何影响多个文件中函数定义的范围。 交流...

javail
10分钟前
3
0
利用 FC + OSS 快速搭建 Serverless 实时按需图像处理服务

作者:泽尘 简介 随着具有不同屏幕尺寸和分辨率设备的爆炸式增长,开发人员经常需要提供各种尺寸的图像,从而确保良好的用户体验。目前比较常见的做法是预先为一份图像存放多份具有不同尺寸的...

阿里巴巴云原生
13分钟前
2
0
前端架构最佳实践

Folders-by-Feature Structure 胜过 Folders-by-Type Structure

lilugirl
23分钟前
3
0
Seata AT 模式启动源码分析

从上一篇文章「分布式事务中间件Seata的设计原理」讲了下 Seata AT 模式的一些设计原理,从中也知道了 AT 模式的三个角色(RM、TM、TC),接下来我会更新 Seata 源码分析系列文章。今天就来分...

后端进阶
25分钟前
4
0
Python中“自我”一词的目的是什么?

Python中self词的目的是什么? 我知道它是指从该类创建的特定对象,但是我看不到为什么要将它显式地作为参数添加到每个函数中。 为了说明这一点,在Ruby中,我可以这样做: class myClass ...

技术盛宴
26分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部