前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >synchronized工作原理(二)

synchronized工作原理(二)

作者头像
keithl
发布2020-03-10 12:10:57
3410
发布2020-03-10 12:10:57
举报
文章被收录于专栏:疾风先生疾风先生

基于工作原理一可知同步关键字底层是基于JVM操作监视器的同步指令原语monitorenter和monitorexit来实现,这次将会通过抽象的内存语义来说明侧面说明加锁和解锁的方式

1. 工作内存与主内存

定义

  • 主内存: 一般就是计算机操作系统上的物理内存,简言之,即是一般我们所说的计算机的内存含义
  • 工作内存: 基于JMM(Java内存模型)规范规定,线程使用的变量将会把主内存的数据变量复制到自己线程栈的工作空间

线程工作内存与主内存的读写示意图

前面已经有介绍到CPU高速缓存的知识点,以下是CPU简单的架构图以及工作内存与主内存的读写流程

从上述我们可以看到,CPU高速缓存包含L1-L3的Cache,线程每次读写都需要先经过CPU高速缓存,这样便会产生数据缓存的不一致,前面已经有讲到CPU厂商针对这类问题做了改进,运用缓存一致性来达到最终数据的一致性,那么此时如果有一个需求是强一致性,即使是很短的时间内,我也需要保证写数据之后立马看到写数据成功后的效果,这时候怎么办呢?在JMM规范中为了解决这类内存共享的数据在不同线程不可见的问题,就制定一种规范来强制java程序中的线程直接跳过CPU高速缓存数据去读取主内存的数据,这就是解决内存数据的不可见的一种手段.

2. synchronized的代码演示
  • 场景: 现在有一个共享变量sharedVar,thread-1执行写操作需要耗时500ms,而有一个线程thread-2由于网络原因延迟读操作耗时600ms,另一个线程thread-3正常读操作
  • 期望的场景是希望写数据之后其他线程也知道数据已经发生改变了,需要读取最新的数据
代码语言:javascript
复制
// Sync2memory.java
public class Sync2memory {

    private static Integer sharedVar = 10;

    public static void main(String[] args) throws Exception {
        testForReadWrite();
//        testForReadWriteWithSync();
 		TimeUnit.SECONDS.sleep(2L);
        System.out.printf("finish the thread task,the final sharedVar %s ....\n", sharedVar);
    }

    private static void testForReadWriteWithSync() throws Exception {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                	// modify the sharedVar
                    TimeUnit.MICROSECONDS.sleep(500L);
                    synchronized (sharedVar){
                        System.out.printf("%s modify the shared var ...\n", "thread-1");
                        sharedVar = 20;
                    }
                }catch (Exception e){
                    System.out.println(e);
                }
            }
        });


        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                   // network delay
                   TimeUnit.MICROSECONDS.sleep(600L);
                   synchronized (sharedVar){
                       System.out.printf("%s read the shared var %s \n", "thread-2", sharedVar);
                   }
                }catch (Exception e){
                    System.out.println(e);
                }
            }
        });

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (sharedVar){
                        System.out.printf("%s read the shared var %s \n",  "thread-3", sharedVar);
                    }
                }catch (Exception e){
                    System.out.println(e);
                }
            }
        });

        thread2.start();
        thread3.start();
        thread1.start();

        thread1.join();
        thread2.join();
        thread3.join();
    }

    private static void testForReadWrite() throws Exception {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                	// modify the sharedVar
                    TimeUnit.MICROSECONDS.sleep(500L);
                    System.out.printf("%s modify the shared var ...\n", "thread-1");
                    sharedVar = 20;
                }catch (Exception e){
                    System.out.println(e);
                }
            }
        });


        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                	// network delay
                    TimeUnit.MICROSECONDS.sleep(600L);
                    System.out.printf("%s read the shared var %s \n", "thread-2", sharedVar);
                }catch (Exception e){
                    System.out.println(e);
                }
            }
        });

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.printf("%s read the shared var %s \n", "thread-3" , sharedVar);
                }catch (Exception e){
                    System.out.println(e);
                }
            }
        });

       //thread1-3 start and join ....
    }
}
  • 没有加synchronized方式的执行产生的一种结果(多次运行)
代码语言:javascript
复制
## 执行结果如下
thread-3 read the shared var 10
thread-1 modify the shared var to 20  ...
thread-2 read the shared var 10
finish the thread task,the final sharedVar 20 ....

Process finished with exit code 0

## 分析
线程3正常执行,并且还没有在发生写操作之前就已经读取数据,属于正常输出
线程1执行写操作耗时500ms并将数据进行修改同步到主内存中
线程2由于网络延迟600ms,但是此时写操作已经完成,这时候读取出来的数据是属于脏数据,并不正确,因此线程2读取是其还没有被刷新的工作内存数据
最后看到执行的结果输出是写操作之后的数据,说明了CPU最终会保证缓存数据的一致性
最后的最后,这里仅仅是阐述上述问题,上述运行结果也可能发生thread-2会读取到正常的数据,只是在上述编码情况我们是无法保证线程2一定可以读取到正确的数据
  • 添加synchronized方式的执行结果(多次执行)
代码语言:javascript
复制
## 多次执行结果如下:
thread-3 read the shared var 10
thread-1 modify the shared var ...
thread-2 read the shared var 20
finish the thread task,the final sharedVar 20 ....

Process finished with exit code 0

## 分析
线程1执行写操作之后,我们可以看到线程2获取到的数据是线程1执行写操作之后的数据,现在程序可以保证线程2读取的数据是正常的
3. synchronized内存语义的理解

内存语义小结

  • 基于上述代码的执行结果可以看出,我们使用synchronized块内的共享变量将从线程的工作内存中清除或者称为失效,此时程序就不会从工作内存中进行读取数据,而是直接从主内存中读取数据,从而保证缓存数据的强一致性
  • 由此可知道,synchronized从内存语义上可以解决共享变量的内存可见性问题
  • 从另一个角度而言,使用synchronized相当于jvm获取monitorenter的指令,此时会将该共享变量的缓存失效直接从主内存中加载数据到锁块的内存中,同时在进行monitorexit操作的指令时会将锁块的共享变量数据刷新到主内存中

synchronized不足

  • 使用monitor的方式是属于metux lock的方式(重量级锁),会降低程序的性能(响应时间可能会变慢,相当于利用性能来换取数据的强一致性问题)
  • 另外一个就是线程是由CPU进行调度,来回切换线程会带来额外的调度开销

谢谢阅读,如果有帮助,欢迎转发或者点击好看,感谢!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 疾风先生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 工作内存与主内存
  • 2. synchronized的代码演示
  • 3. synchronized内存语义的理解
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档