多线程时,线程同步之———对象锁

时间:2021-07-22 13:21:54

多线程操作时,上篇博客写到了两种不同方式的线程同步方式:
方式1:同步代码块
一般写在方法内部,如:

synchronized (任意对象名){
...//同步代码块
}

方式2:同步方法
用synchronized或者static synchronized来修饰方法,如:

public synchronized method(){
...//方法体
}

或者,加static关键字

public static synchronized method(){
...//方法体
}

在java代码中,主方法开启多线程时,线程代码方法中synchronized就意味着是一把线程锁,当这段加锁的代码在被一个线程访问时,这段代码就会拒绝其他线程访问,直到上一个线程结束访问,才有可能轮到它来访问,也就是像只有一把钥匙能开这个小盒子,而有人把钥匙拿走了,那你就只能等那个人用完(用完把钥匙还回来),才能去拿钥匙开小盒子。

但是,,加上synchronized并不意味着完全解决线程同步问了,比如如下几种情况:
1、在run方法中有synchronized包裹的代码块,并且调用了synchronized修饰的方法

public class ThickesRunnable implements Runnable {
private Object obj = new Object();
public boolean flag = true;

@Override
public void run() {
if(flag){ //********代码(1)
flag = false;
synchronized (obj){
...//被包裹的同步代码块
}
}
else{ //********代码(2)
flag = true;
method();
}
}

public synchronized void methed(){
...//线程同步方法
}
}

run()方法这样写的想要的目的是:当一个线程访问了run()里面的代码后,下一个线程就会和前面启动的线程交替访问线程同步代码块线程同步方法,然后我们看代码(1)被线程访问时,代码(2)可不可以被另一个线程访问;
将同步代码块和同步方法中都写上卖票代码,分析运行结果,会发现,线程并没有达到同步安全,也就是说,代码(1)被访问时,并不影响代码(2)的访问,而1和2中都是卖票程序,那么就达不到线程同步安全的效果了。
说明:

synchronized (obj){
...//被包裹的同步代码块
}

只能阻塞这一段代码,并不能阻塞run()方法中的method()方法,这有时不是我们想要的结果,那么客官可能会问了,要是我就想同步synchronized(obj)代码块,怎么办?这种情况时有在对象中有两个或以上这样的代码块时,才能保证这几个代码块同步,也就是说这几个代码块共享的锁对象是obj,被拿走了,其他的就只能等着。我们有时想让它阻塞整个Runnable中的synchronized代码的访问,怎么办呢?
如下方式书写:

@Override
public void run() {
if(flag){ //********代码(1)
flag = false;
synchronized (this){ //括号中写this
...//被包裹的同步代码块
}
}
else{ //********代码(2)
flag = true;
method();
}
}

synchronized (this)代表获取整个对象的线程锁,这样写和method()方法是等效的,public synchronized void methed()也代表着获取到了整个对象的线程锁, 这个对象的线程锁只有一个,因此二者只要有一个被访问,那么另一个就不能再被同时访问,直到上一个访问结束,以此类推,run()中出现3个,4个等多个此种同步代码,都是这样,只要一个同步代码块被访问,即为获取整个对象的线程锁,其他线程无法再获取,就算一个代码块中调用了sleep()方法,也只能等待;
通过测试代码,可以发现:
1、对于同一对象,被多个线程访问时synchronized (this){ }和synchronized void methed()共享同一个线程锁,其中一个同步代码块被其中一个访问,那么另一个代码块就会拒绝其他线程访问,也就相当于阻塞访问,因为这个线程锁被没被释放,另一个线程无法获取,只能等待;但是和synchronized static void methed()、synchronized (XXX.class){ }不共享同一个线程锁,不影响和他们同时被访问;
2、对于同一个对象,被多个线程访问时synchronized (XXX.class){ }和synchronized static void methed()共享同一个线程锁,和上述情况一个道理,其中一个同步代码块被其中一个访问,那么另一个代码块就会拒绝其他线程访问;
3、对于多个对象,被多个线程访问时,synchronized (XXX.class){ }和synchronized static void methed()不能同时被访问,也就是说,不同对象中的这两种代码块共享同一个线程锁,或者可以说是锁住了整个字节码文件;
4、而对于多个对象,被多个线程访问时,不同线程中synchronized (XXX.class){ }和synchronized void methed()和synchronized (this){ }可以被同时访问,synchronized static void methed()和synchronized void methed()和synchronized (this){ }可以被同时访问
总而言之,只要synchronized (XXX.class){ }和synchronized static void methed()不能被同时访问就OK了。测试代码【day23_1】
怎么说呢,系统是外国人写的,jvm是歪果仁封装的,深究从顶层到底层的逻辑不太可能也没有必要,只能用自己的方式去理解了,对于一个类来说,每一个同步代码块就像一个房间,而不同类型的同步代码块就像大包间小包间这些不同类型的包间,大包间有好多间,小包间也有很多间,大包间的钥匙和小包间的钥匙不一样,而且每种包间只有一把钥匙,一旦一种类型的锁的钥匙被人拿去了,那么同种类型的其他锁就无法访问了,只能等别人把钥匙用完了你才能用,但是小包间钥匙拿走了,大包间钥匙还在,不会相互影响;