java lock 如何保证共享对象可见性

youshu2011 2015-09-29 08:38:01
synchronized 可以保证代码块中所有被访问的变量将会从主存中读入,当线程退出同步代码块时,所有被更新的变量都会被刷新回主存中去,不管这个变量是否被声明为volatile。
那么java中的Lock系列是如何实现lock的时候所有被访问的变量是最新值,以及unlock的时候所有被更新的变量都刷新回主存中去的?
谢谢!
...全文
749 17 打赏 收藏 转发到动态 举报
写回复
用AI写文章
17 条回复
切换为时间正序
请发表友善的回复…
发表回复
走向自由 2019-07-25
  • 打赏
  • 举报
回复
楼主,我也有相同的疑问,请问你最后找到答案了吗?
xfeng_lalala 2019-01-26
  • 打赏
  • 举报
回复
在java并发编程实战中,有句话是这样的说的 "线程A拿到lock对象对数据进行修改, 线程B拿到lock对象后,对于线程A的修改线程B是可见的, 线程B可以看到线程A的所有操作结果"
xfeng_lalala 2019-01-26
  • 打赏
  • 举报
回复
private static volatile int value = 0; private static ReentrantLock reentrantLock = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("lock外的value : " + value); reentrantLock.lock(); System.out.println(value); value = value + 1; try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } reentrantLock.unlock(); } }); t1.start(); } } 结果: lock外的value : 92 lock外的value : 95 lock外的value : 95 lock外的value : 95 lock外的value : 95 95 lock外的value : 95 lock外的value : 93 lock外的value : 93 lock外的value : 93 lock外的value : 93 lock外的value : 93 lock外的value : 93 lock外的value : 93 lock外的value : 93 lock外的value : 93 lock外的value : 93 lock外的value : 93 lock外的value : 93 lock外的value : 93 lock外的value : 93 lock外的value : 92 lock外的value : 92 lock外的value : 92 lock外的value : 92 lock外的value : 92 lock外的value : 92 lock外的value : 92 lock外的value : 92 lock外的value : 92 96 97 98 99 100 . . . 992 993 994 995 996 997 998 999 不难发现, juc包下的lock其实是具有线程可见性的, 具体这么实现的我就不知道了,才疏学浅...
RODMAN_91 2018-12-02
  • 打赏
  • 举报
回复
楼主,跟你有同样的疑问。最近两天在看了比较多的博客,发现网上的博客对于volatile的相关内存语义问题上有分歧,一种说法认为会仅是volatile变量被写后对读取该变量的线程可见,另一种说法是线程执行volatile变量的写操作,会导致该线程的工作内存同步回主存,执行读volatle变量的线程会将工作内存同步主存。然后刚翻了下jsr 133,发现一时半会没法找到答案,打算后面认真读一下jsr 133,希望能自己找到一个确切的答案。还有我也偏向相信上面的第二种说法,如果看过jsr133还没得到确切的答案,就打算去浏览一下LinkedBlockingDeque(也可是其它类)的源码,如果这个类的代码及其import的类中没有用到synchronized关键字的话(毕竟考虑LinkedBlockingDeque的使用场景,必须保证可见性),那我就认同了第二种观点了。看到这个贴,有点不那么孤单的感觉,哈哈,还有也挺感谢这个贴以及后面给出干货的答住,例如《java并发编程的艺术3.5.3节》中对volatile的描述。
灰色调 2017-10-31
  • 打赏
  • 举报
回复
------上面人的回答都基本没理解题目的意思,或者说未挖的这么深。 这个问题困扰我很久,终于解决了,jsr133之后增强了volatile语义,禁止了volatile变量与普通变量之间的重排序,根据volatile的happens-before原则,释放锁的线程在写volatile变量之前的共享变量(非volatile),在获取锁的线程读取同一个共享变量后将立即对获取锁的线程可见。----------参照《java并发编程的艺术3.5.3节》
shixiangweii 2017-07-18
  • 打赏
  • 举报
回复
引用 10 楼 qq_34640619 的回复:
[quote=引用 8 楼 a327369238 的回复:] [quote=引用 7 楼 a327369238 的回复:] [quote=引用 5 楼 youshu2011 的回复:] [quote=引用 3 楼 a327369238 的回复:] 各个lock上层采用的策略不一样 lock底层采用的是CAS 一种称为乐观锁的机制,多线程中内存对所有可见,但每次更新的时候都会对要更新的值与其期望值对比,若相同更新,不相同则 用这个值再做这个线程的计算(并保存改值为期望值),一直这样直到成功,所以也叫自旋锁 貌似是这样的,你可以自己百度查下
谢谢!但你说的貌似不是我想问的。 如下代码,如何保证lock后读到count值是最新的,以及unlock时及时将修改的值刷新回主内存中,必须给count加上volatile关键字吗?

private static Lock lock = new ReentrantLock();
	private static int count = 0;
	public static void main(String[] args) throws Exception {
		for (int i = 0; i < 10000; i++) {
			Thread t1 = new Thread(new Runnable() {
				@Override
				public void run() {
					System.out.println(count);
					lock.lock();
					count = count + 1;
					try {
						Thread.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					lock.unlock();
				}
			});
			t1.start();
		}
	}
[/quote] JVM碰到unlock的时候,就会释放锁,在释放锁之前会把相关变量(堆里面的也包括方法区的静态量)写会相应的内存区,然后释放锁,之后别的线程就可以重新获得这块内存的锁了 非栈对象更新完了都会写回内存啊,只是这过程不确定顺序,加锁就是为了在别的线程访问这块内存之前不能访问,不过,共享锁好像是可以读的(但你仍然得获得这块内存相应的读锁),不能写,当然这个时候你读的就不一定准确了,所以如果要确保实时读取数据正确的,还是不要共享锁 我的个人理解[/quote] 错了,非栈对象要等跳出栈帧才写回去的吧,而锁呢,应该是释放之前写会去的,要不然假如释放锁,但是方法没完(块锁),这对象的内存没锁,没写回去,锁就没意义了[/quote] 一帮傻x, 都没有明白题主的意思,我任务lock根本就没有保证可见性,只是实现了访问的原子性。要不并发库的源码里面也不会随处可见volatile了。[/quote] 同意楼上的看法,lock是用java代码实现的(concurrent包),不是jvm的内置关键字,lock只是保证操作的原子性,没有可见性的保证
qq_34640619 2016-07-03
  • 打赏
  • 举报
回复
引用 8 楼 a327369238 的回复:
[quote=引用 7 楼 a327369238 的回复:] [quote=引用 5 楼 youshu2011 的回复:] [quote=引用 3 楼 a327369238 的回复:] 各个lock上层采用的策略不一样 lock底层采用的是CAS 一种称为乐观锁的机制,多线程中内存对所有可见,但每次更新的时候都会对要更新的值与其期望值对比,若相同更新,不相同则 用这个值再做这个线程的计算(并保存改值为期望值),一直这样直到成功,所以也叫自旋锁 貌似是这样的,你可以自己百度查下
谢谢!但你说的貌似不是我想问的。 如下代码,如何保证lock后读到count值是最新的,以及unlock时及时将修改的值刷新回主内存中,必须给count加上volatile关键字吗?

private static Lock lock = new ReentrantLock();
	private static int count = 0;
	public static void main(String[] args) throws Exception {
		for (int i = 0; i < 10000; i++) {
			Thread t1 = new Thread(new Runnable() {
				@Override
				public void run() {
					System.out.println(count);
					lock.lock();
					count = count + 1;
					try {
						Thread.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					lock.unlock();
				}
			});
			t1.start();
		}
	}
[/quote] JVM碰到unlock的时候,就会释放锁,在释放锁之前会把相关变量(堆里面的也包括方法区的静态量)写会相应的内存区,然后释放锁,之后别的线程就可以重新获得这块内存的锁了 非栈对象更新完了都会写回内存啊,只是这过程不确定顺序,加锁就是为了在别的线程访问这块内存之前不能访问,不过,共享锁好像是可以读的(但你仍然得获得这块内存相应的读锁),不能写,当然这个时候你读的就不一定准确了,所以如果要确保实时读取数据正确的,还是不要共享锁 我的个人理解[/quote] 错了,非栈对象要等跳出栈帧才写回去的吧,而锁呢,应该是释放之前写会去的,要不然假如释放锁,但是方法没完(块锁),这对象的内存没锁,没写回去,锁就没意义了[/quote] 一帮傻x, 都没有明白题主的意思,我任务lock根本就没有保证可见性,只是实现了访问的原子性。要不并发库的源码里面也不会随处可见volatile了。
youshu2011 2015-10-10
  • 打赏
  • 举报
回复
在一篇文章中找到了答案: 1.释放锁的最后写volatile变量state;在获取锁时首先读这个volatile变量。根据volatile的happens-before规则,释放锁的线程在写volatile变量之前可见的共享变量,在获取锁的线程读取同一个volatile变量后将立即变的对获取锁的线程可见。​ 2.CAS具有 volatile 读和写的内存语义。
a327369238 2015-10-08
  • 打赏
  • 举报
回复
引用 7 楼 a327369238 的回复:
[quote=引用 5 楼 youshu2011 的回复:] [quote=引用 3 楼 a327369238 的回复:] 各个lock上层采用的策略不一样 lock底层采用的是CAS 一种称为乐观锁的机制,多线程中内存对所有可见,但每次更新的时候都会对要更新的值与其期望值对比,若相同更新,不相同则 用这个值再做这个线程的计算(并保存改值为期望值),一直这样直到成功,所以也叫自旋锁 貌似是这样的,你可以自己百度查下
谢谢!但你说的貌似不是我想问的。 如下代码,如何保证lock后读到count值是最新的,以及unlock时及时将修改的值刷新回主内存中,必须给count加上volatile关键字吗?

private static Lock lock = new ReentrantLock();
	private static int count = 0;
	public static void main(String[] args) throws Exception {
		for (int i = 0; i < 10000; i++) {
			Thread t1 = new Thread(new Runnable() {
				@Override
				public void run() {
					System.out.println(count);
					lock.lock();
					count = count + 1;
					try {
						Thread.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					lock.unlock();
				}
			});
			t1.start();
		}
	}
[/quote] JVM碰到unlock的时候,就会释放锁,在释放锁之前会把相关变量(堆里面的也包括方法区的静态量)写会相应的内存区,然后释放锁,之后别的线程就可以重新获得这块内存的锁了 非栈对象更新完了都会写回内存啊,只是这过程不确定顺序,加锁就是为了在别的线程访问这块内存之前不能访问,不过,共享锁好像是可以读的(但你仍然得获得这块内存相应的读锁),不能写,当然这个时候你读的就不一定准确了,所以如果要确保实时读取数据正确的,还是不要共享锁 我的个人理解[/quote] 错了,非栈对象要等跳出栈帧才写回去的吧,而锁呢,应该是释放之前写会去的,要不然假如释放锁,但是方法没完(块锁),这对象的内存没锁,没写回去,锁就没意义了
a327369238 2015-10-08
  • 打赏
  • 举报
回复
引用 5 楼 youshu2011 的回复:
[quote=引用 3 楼 a327369238 的回复:] 各个lock上层采用的策略不一样 lock底层采用的是CAS 一种称为乐观锁的机制,多线程中内存对所有可见,但每次更新的时候都会对要更新的值与其期望值对比,若相同更新,不相同则 用这个值再做这个线程的计算(并保存改值为期望值),一直这样直到成功,所以也叫自旋锁 貌似是这样的,你可以自己百度查下
谢谢!但你说的貌似不是我想问的。 如下代码,如何保证lock后读到count值是最新的,以及unlock时及时将修改的值刷新回主内存中,必须给count加上volatile关键字吗?

private static Lock lock = new ReentrantLock();
	private static int count = 0;
	public static void main(String[] args) throws Exception {
		for (int i = 0; i < 10000; i++) {
			Thread t1 = new Thread(new Runnable() {
				@Override
				public void run() {
					System.out.println(count);
					lock.lock();
					count = count + 1;
					try {
						Thread.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					lock.unlock();
				}
			});
			t1.start();
		}
	}
[/quote] JVM碰到unlock的时候,就会释放锁,在释放锁之前会把相关变量(堆里面的也包括方法区的静态量)写会相应的内存区,然后释放锁,之后别的线程就可以重新获得这块内存的锁了 非栈对象更新完了都会写回内存啊,只是这过程不确定顺序,加锁就是为了在别的线程访问这块内存之前不能访问,不过,共享锁好像是可以读的(但你仍然得获得这块内存相应的读锁),不能写,当然这个时候你读的就不一定准确了,所以如果要确保实时读取数据正确的,还是不要共享锁 我的个人理解
youshu2011 2015-10-08
  • 打赏
  • 举报
回复
引用 3 楼 a327369238 的回复:
各个lock上层采用的策略不一样 lock底层采用的是CAS 一种称为乐观锁的机制,多线程中内存对所有可见,但每次更新的时候都会对要更新的值与其期望值对比,若相同更新,不相同则 用这个值再做这个线程的计算(并保存改值为期望值),一直这样直到成功,所以也叫自旋锁 貌似是这样的,你可以自己百度查下
谢谢!但你说的貌似不是我想问的。 如下代码,如何保证lock后读到count值是最新的,以及unlock时及时将修改的值刷新回主内存中,必须给count加上volatile关键字吗?

private static Lock lock = new ReentrantLock();
	private static int count = 0;
	public static void main(String[] args) throws Exception {
		for (int i = 0; i < 10000; i++) {
			Thread t1 = new Thread(new Runnable() {
				@Override
				public void run() {
					System.out.println(count);
					lock.lock();
					count = count + 1;
					try {
						Thread.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					lock.unlock();
				}
			});
			t1.start();
		}
	}
youshu2011 2015-10-08
  • 打赏
  • 举报
回复
引用 1 楼 mg2flyingff 的回复:
这两点不都是java虚拟机内部完成得么。。既然第一个都可以那第二个也可以呀。。
synchronized是关键字,但lock不是。
0萌萌哒0 2015-10-08
  • 打赏
  • 举报
回复

    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
这样的实现难道不是只有虚拟机才能完成吗。。 类似可见性的事情也只有通过这样的native方法才能完成了。。 难道你会以为java代码能完成这样的功能。。 这个类的实现可没有使用synchronized。。 至于在虚拟机内部怎么实现,还需要去翻翻虚拟机的书。。
a327369238 2015-09-30
  • 打赏
  • 举报
回复
各个lock上层采用的策略不一样 lock底层采用的是CAS 一种称为乐观锁的机制,多线程中内存对所有可见,但每次更新的时候都会对要更新的值与其期望值对比,若相同更新,不相同则 用这个值再做这个线程的计算(并保存改值为期望值),一直这样直到成功,所以也叫自旋锁 貌似是这样的,你可以自己百度查下
tatas 2015-09-30
  • 打赏
  • 举报
回复
0萌萌哒0 2015-09-30
  • 打赏
  • 举报
回复
这两点不都是java虚拟机内部完成得么。。既然第一个都可以那第二个也可以呀。。

62,615

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧