前言

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

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

synchronized (object) {
...
}

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

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

List<String> list = ArrayList<>();
synchronized(list){
...
}

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

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

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

然后我就做了些实验。

实验内容

实验一

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

public class ServiceA extends Thread {
private List<String> list;
public ServiceA(List<String> list) {
super();
this.list = list;
}
public void run() {
System.out.println("here is thread a runnning");
synchronized (list) {
for(int i = 0;i<100;i++) {
list.add(String.valueOf(i));
System.out.println("thread a added "+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) { }
}
}
}
}
public class ServiceB extends Thread{
private List<String> list;
public ServiceB(List<String> list) {
super();
this.list = list;
}
public void run() {
System.out.println("here is thread b runnning");
System.out.println(list.get(0));
}
}

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

接下来是main函数:

public class SynchronizedTestMain {

  public static void main(String[] args) {
List<String> list = new ArrayList<>();
ServiceA a = new ServiceA(list);
ServiceB b = new ServiceB(list);
a.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
b.start();
}
}

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

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

实验二

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

public class ServiceB extends Thread{
private List<String> list;
public ServiceB(List<String> list) {
super();
this.list = list;
}
public void run() {
System.out.println("here is thread b runnning");
synchronized (list) {
System.out.println(list.get(0));
}
}
}

结果就是顺序执行

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

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

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

那么就来做实验看看吧~

实验三

public class TextExample  {
String text = "text";
public synchronized void insert(String ins, int pos) {
System.out.println("here is "+Thread.currentThread().getName()+" inserting");
System.out.println("now "+Thread.currentThread().getName()+" starts sleeping");
try {
int i = 0;
while(i<3) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" has slept for "+i+" secs");
i++;
}
} catch(InterruptedException e) {
}
text = text.substring(0,pos)+ins+text.substring(pos);
System.out.println("now "+ Thread.currentThread().getName()+" has finished inserting");
}
public synchronized void delete(int pos, int len) {
System.out.println("here is "+Thread.currentThread().getName()+" deleting");
System.out.println("now "+Thread.currentThread().getName()+" starts sleeping");
try {
int i = 0;
while(i<3) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" has slept for "+i+" secs");
i++;
}
} catch(InterruptedException e) { }
text = text.substring(0, pos)+text.substring(pos+len);
System.out.println("now "+ Thread.currentThread().getName()+" has finished deleting"); }
}
public class ThreadExample extends Thread {
private TextExample txteg;
private int choice;
public ThreadExample(TextExample txteg, int i) {
super();
this.txteg = txteg;
choice = i;
} @Override
public void run() {
if(choice == 1) {
txteg.insert("====", 2);
}
else if(choice == 2) {
txteg.delete(2, 4);
}
}
}
public class SynchronizedExampleTest {

  public static void main(String[] args) {
TextExample textExample = new TextExample();
ThreadExample threadExample1 = new ThreadExample(textExample, 1);
threadExample1.setName("thread1");
ThreadExample threadExample2 = new ThreadExample(textExample, 2);
threadExample2.setName("thread2");
threadExample1.start();
try {
Thread.sleep(1000);
}catch(InterruptedException e) { }
threadExample2.start();
}
}

这次我用了老师上课给的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的方法的。

最新文章

  1. Jsoup的demao
  2. [转]Perfmon - Windows 自带系统监测工具
  3. python-操作mssql数据库
  4. dll不同的调用方式
  5. 解决rsyslog 断电或者被kill 重发问题
  6. FusionCharts 学习总结
  7. SAP屏幕框架的创建
  8. PLSQL程序流程
  9. 2017ecjtu-summer training # 11 POJ 2492
  10. 微信小程序:微信登陆(ThinkPHP作后台)
  11. asp.net core NLog将日志写到文件
  12. 洛谷P3199 [HNOI2009]最小圈(01分数规划)
  13. JAVA自学笔记25
  14. 复习下C 链表操作(双向循环链表,查找循环节点)
  15. oracle 导出表
  16. Hibernate优化策略
  17. 使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(一)-- 起步
  18. Oracle中的数值处理方法
  19. Spring学习 6- Spring MVC (Spring MVC原理及配置详解)
  20. C++中添加配置文件读写方法

热门文章

  1. 4 Things I Wish I Would Have Known When I Started My Software Development Career【当我最开始从事软件工程师的时候我希望我知道的四件事】
  2. MAVEN - 生命周期(1)
  3. java反射_01
  4. VR: AR和VR演进哲学
  5. (转)C#开发微信门户及应用(6)--微信门户菜单的管理操作
  6. wx小程序开发 1:小程序代码构成
  7. Innodb 中的事务隔离级别和锁的关系
  8. grunt入门 出处:http://artwl.cnblogs.com
  9. [51Nod1486] 大大走格子 (dp+容斥)
  10. [CodeForces]1042D