前言

做完lab5开始做lab6了鸭,哈工大计算机学院的学生永不停歇。在做lab6的时候,我在想移动猴子是锁一整个ladder(ADT)还是只锁一个ladder的一个域Monkey数组呢?这两个好像差不多,然后就开始思考锁一个对象和锁其中一个域有什么区别?如果锁了对象对于方法调用有什么影响?回想起上课老师讲的一个类的synchronized方法之间是mutual excluded我就不太明白这到底是怎么回事

我问了隔壁大佬如果锁了对象,类似于:

  1. synchronized (object) {
  2. ...
  3. }

这样的做法,两个线程能不能同时调用一个对象的方法,大佬给我的答案是不可以。

但是又回想起上课的时候老师给我们做了练习,大致如下:

  1. List<String> list = ArrayList<>();
  2. synchronized(list){
  3. ...
  4. }

在这个例子中别的线程是可以同时调用list.get()方法的

然后再问大佬,大佬就跟我说了一堆我听不懂的东西,然后说自己去吃饭去了,让我自己理解一下

凭借我足以过六一儿童节的智商我表示无法理解这件事

然后我就做了些实验。

实验内容

实验一

如果在两个线程中,A线程中用锁锁了一个对象a,B线程没有锁a,然后调用a的方法,会发生什么?我就用刚刚那个list作为实验内容,代码如下:

  1. public class ServiceA extends Thread {
  2. private List<String> list;
  3. public ServiceA(List<String> list) {
  4. super();
  5. this.list = list;
  6. }
  7. public void run() {
  8. System.out.println("here is thread a runnning");
  9. synchronized (list) {
  10. for(int i = 0;i<100;i++) {
  11. list.add(String.valueOf(i));
  12. System.out.println("thread a added "+i);
  13. try {
  14. Thread.sleep(1000);
  15. } catch (InterruptedException e) {
  16. }
  17. }
  18. }
  19. }
  20. }
  1. public class ServiceB extends Thread{
  2. private List<String> list;
  3. public ServiceB(List<String> list) {
  4. super();
  5. this.list = list;
  6. }
  7. public void run() {
  8. System.out.println("here is thread b runnning");
  9. System.out.println(list.get(0));
  10. }
  11. }

简单解释一下这两个类,可以看出ServiceA的操作是一个mutator,ServiceB的操作是一个observer接下来的main函数中我会把同一个list传入这两个类的实例中,然后让这两个类作为两个线程跑,且A会在B之前跑,我在A中每次插入之后都会让这个线程sleep,强迫系统调度B线程,如果能同时调用这两个方法的话A中的输出会穿插着B中的输出

接下来是main函数:

  1. public class SynchronizedTestMain {
  2. public static void main(String[] args) {
  3. List<String> list = new ArrayList<>();
  4. ServiceA a = new ServiceA(list);
  5. ServiceB b = new ServiceB(list);
  6. a.start();
  7. try {
  8. Thread.sleep(1000);
  9. } catch (InterruptedException e) {
  10. }
  11. b.start();
  12. }
  13. }

失误了……应该少设置点i的,但是结果很明显,如果在一个线程中用synchronized锁住了一个对象,另一个线程是可以调用这个对象的方法的

那么大佬的归不太可能出错的咯,大佬什么意思呢?由此我做了实验二

实验二

我把ServiceB中的语句也用synchronized包起来:

  1. public class ServiceB extends Thread{
  2. private List<String> list;
  3. public ServiceB(List<String> list) {
  4. super();
  5. this.list = list;
  6. }
  7. public void run() {
  8. System.out.println("here is thread b runnning");
  9. synchronized (list) {
  10. System.out.println(list.get(0));
  11. }
  12. }
  13. }

结果就是顺序执行

这里虽然输出了threadB在运行,但是并没有输出list的第一个元素,说明被block了(才不是我写代码失误然后懒得改了哼!)

那么结论已经出来了,如果两个线程同时锁了一个对象,那么一次只有一个线程能够使用这个对象的方法,如果只有一个线程锁了,另一个线程是能够使用这个对象的方法的。

照这个理论来说,两个线程是不可以分别同时使用一个对象的两个synchronized方法的,因为synchronized方法相当于在函数体最外围包一个synchronized(this){}

那么就来做实验看看吧~

实验三

  1. public class TextExample {
  2. String text = "text";
  3. public synchronized void insert(String ins, int pos) {
  4. System.out.println("here is "+Thread.currentThread().getName()+" inserting");
  5. System.out.println("now "+Thread.currentThread().getName()+" starts sleeping");
  6. try {
  7. int i = 0;
  8. while(i<3) {
  9. Thread.sleep(1000);
  10. System.out.println(Thread.currentThread().getName()+" has slept for "+i+" secs");
  11. i++;
  12. }
  13. } catch(InterruptedException e) {
  14. }
  15. text = text.substring(0,pos)+ins+text.substring(pos);
  16. System.out.println("now "+ Thread.currentThread().getName()+" has finished inserting");
  17. }
  18. public synchronized void delete(int pos, int len) {
  19. System.out.println("here is "+Thread.currentThread().getName()+" deleting");
  20. System.out.println("now "+Thread.currentThread().getName()+" starts sleeping");
  21. try {
  22. int i = 0;
  23. while(i<3) {
  24. Thread.sleep(1000);
  25. System.out.println(Thread.currentThread().getName()+" has slept for "+i+" secs");
  26. i++;
  27. }
  28. } catch(InterruptedException e) {
  29. }
  30. text = text.substring(0, pos)+text.substring(pos+len);
  31. System.out.println("now "+ Thread.currentThread().getName()+" has finished deleting");
  32. }
  33. }
  1. public class ThreadExample extends Thread {
  2. private TextExample txteg;
  3. private int choice;
  4. public ThreadExample(TextExample txteg, int i) {
  5. super();
  6. this.txteg = txteg;
  7. choice = i;
  8. }
  9. @Override
  10. public void run() {
  11. if(choice == 1) {
  12. txteg.insert("====", 2);
  13. }
  14. else if(choice == 2) {
  15. txteg.delete(2, 4);
  16. }
  17. }
  18. }
  1. public class SynchronizedExampleTest {
  2. public static void main(String[] args) {
  3. TextExample textExample = new TextExample();
  4. ThreadExample threadExample1 = new ThreadExample(textExample, 1);
  5. threadExample1.setName("thread1");
  6. ThreadExample threadExample2 = new ThreadExample(textExample, 2);
  7. threadExample2.setName("thread2");
  8. threadExample1.start();
  9. try {
  10. Thread.sleep(1000);
  11. }catch(InterruptedException e) {
  12. }
  13. threadExample2.start();
  14. }
  15. }

这次我用了老师上课给的synchronized方法的例子,具体见MIT 6.031 Reading 21

我在textExample里面让他多睡了一会儿然后输出,方便观察,如果能同时运行的话,输出应该是在1睡的时候2能够对字符串进行修改,否则2只能等1睡完才能对字符串进行修改

顺序执行,说明我之前想的没错,问题完美解决~

结语

这三个实验得出来的结论如下:

  1. 如果两个线程A和B同时锁了一个对象Object,那么一次只有一个线程能够使用Object的方法,如果只有一个线程,比如A锁了Object,另一个线程B没有锁Object,那么B是能够使用Object的方法的。
  2. 两个线程不能同时使用一个对象的两个synchronized方法,因为synchronized方法相当于在函数体外面包一个synchronized(this){}

这次实验做下来感觉很舒服,自己探索问题并且得出结论的感觉真的很棒,虽然这个问题好像很基础的样子,我可真是太菜了……

果然老师说的有什么问题不懂java都能告诉我答案,想不明白就去做实验吧~

手动分割线

经过和FGS同学的探讨之后,他提供给了我一个新的角度去理解这个问题,我觉得很不错:

本质上说synchronized(Object){A}锁的就是代码块A,可以将Object看作是一个钥匙,用来开这个锁,一个钥匙一次只能在一个线程的手上。

如果两个线程,比如A和B都用了同一个锁锁住了各自的代码块,假设A先抢到了钥匙,那么没抢到钥匙的那个线程B就得等抢到钥匙的那个线程A先执行完自己代码块当中的内容后,A将钥匙归还,B才能拿到钥匙,才能执行自己代码块中的内容。

锁本质上锁的是对代码块中的内容的权限,所以就算换一个对象锁也没什么问题。这也就解释了为什么如果线程B中不用锁锁上,是不影响调用Object的方法的。

HIT Software Construction Lab6引发出来对锁的问题的探究的更多相关文章

  1. HIT Software Construction Lab 5_经验总结

    前言: 终于写完lab5了,这次lab5是基于lab3的一次实验,主要是王忠杰老师提供了4个大约有50w行的大文件让我们根据自己所选应用读取其中两个并且创建轨道系统. 这次lab5优化的我很崩溃,因为 ...

  2. HIT Software Construction Lab 3

    ​ 2019年春季学期 计算机学院<软件构造>课程 Lab 3实验报告 姓名 刘帅 学号 班号 1703008 电子邮件 1609192321@qq.com 手机号码 目录 1 实验目标概 ...

  3. HIT Software Construction Lab 2

    2019年春季学期 计算机学院<软件构造>课程 Lab 2实验报告 姓名 刘帅 学号 班号 1703008 电子邮件 1609192321@qq.com 手机号码 目录   1 实验目标概 ...

  4. Oracle 唯一主键引发的行锁

    SQL> create table test(id int PRIMARY KEY, name char(10)); 表已创建. Session 1: SQL> select * from ...

  5. Software Construction内容归纳

    本篇博文是对于2020春季学期<软件构造>课程的总结归纳,由于原先编辑于word,格式不方便直接导入该博客,可以到本人github中进行自取. https://github.com/zqy ...

  6. SQLServer锁的基础问题探究

    SqlServer需要在执行操作前对目标资源获取所有权,那么久发生锁定,是一个逻辑概念.为了保证事务的ACID特性设计的一种机制. 在多用户并发操作数据时,为了出现不一致的数据,锁定是必须的机制.使用 ...

  7. BP-Wrapper:无锁竞争的缓存替换算法系统框架

    BP-Wrapper:无锁竞争的替换算法系统框架 最近看了一个golang的高性能缓存ristretto,该缓存可以很好地实现如下功能: Concurrent High cache-hit ratio ...

  8. [转]了解SQL Server锁争用:NOLOCK 和 ROWLOCK 的秘密_Mr_Indigo的空间

    了解SQL Server锁争用:NOLOCK 和 ROWLOCK 的秘密 关系型数据库,如SQL Server,使用锁来避免多用户修改数据时的并发冲突.当一组数据被某个用户锁定时,除非第一个用户结束修 ...

  9. 了解SQL Server锁争用:NOLOCK 和 ROWLOCK 的秘密

    关系型数据库,如SQL Server,使用锁来避免多用户修改数据时的并发冲突.当一组数据被某个用户锁定时,除非第一个用户结束修改并释放锁,否则其他用户就无法修改该组数据. 有些数据库,包括SQL Se ...

随机推荐

  1. Python3编写自动签到服务程序

    公司加班的餐补需要登录网站签到领取,有时候会忘记,于是自己用Python写了小程序来自动签到.刚开始只是做了自己用,直接写的黑框程序,后来给同事用,就打包成exe.再后来有人说要写成window服务会 ...

  2. C#程序集版本控制文件属性祥解

    using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices ...

  3. OpenCV实现灰度直方图和直方图拉伸

    原文链接:http://blog.csdn.net/xiaowei_cqu/article/details/7600666 如有疑问或者版权问题,请移步原作者或者告知本人. 灰度直方图是数字图像中最简 ...

  4. (转)Arcgis for js加载天地图

    http://blog.csdn.net/gisshixisheng/article/details/44494715 综述:本节讲述的是用Arcgis for js加载天地图的切片资源. 天地图的切 ...

  5. layui 下拉框取值

    layui.use('form', function () { var form = layui.form; form.on('select(Status)', function (data) { c ...

  6. 新浪某个tab 页模仿

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  7. mac 上执行 rm -rf /

    # 很可怕的指令,清空磁盘所有资料,千万不要用 sudo 尝试,吓的小心肝差掉跳出来 rm -rf / 无聊,想执行rm -rf /会怎样,想起没加sudo时对~/download执行提示权限不足,被 ...

  8. scrapy-redis使redis不止保存url

    先看scrapy-redis源码 class RedisMixin(object): """Mixin class to implement reading urls f ...

  9. 如何查看系统的界面,比如费用申请单的序时簿界面引用的是哪一个ListUi.快捷键alt+shift+d 然后选中该ListUI大框框,就可以看到引用的是哪一个了.

    如何查看系统的界面,比如费用申请单的序时簿界面引用的是哪一个ListUi.快捷键alt+shift+d 然后选中该ListUI大框框,就可以看到引用的是哪一个了.

  10. [jzoj5786]【NOIP2008模拟】观察 (dfs序+lca)

    传送门 Description infleaking十分愉快地走在路上, 因为经过10^9^9^9年后, 他得到了一个新技能--观察大法. 刚出来的infleaking就想要挑战自我. 为什么infl ...