1.谁是那把锁
上节买票示例中,通过加上synchronized关键字,给代码加锁,只有拿到锁的线程才能执行目标代码,也可以使用synchronized(this){}将buy()方法包住,这两种方式有什么区别?为什么要用this,我用其他Object可以吗?可以换成Object.class吗?有时候看到synchronized加到静态方法上,这和加到非静态方法上效果一样吗?
要回答这些问题,首先就要明白谁是那把锁,这对于正确使用synchronized关键字显得尤为重要。
- synchronized关键字加到非静态方法上,锁是方法所在对象,该效果等同于同步代码块 synchronized(this){}
- synchronized关键字加到了静态方法上,锁为当前类,该效果等同于synchronized (MyObject.class) {}
- 只有是同一把锁的代码在多线程调用时才会互斥,这一点非常重要,它包含两重意思
- 对于1.和2.的情况,方法之间没有任何关联,怎么调都不会发生排队,因为锁不是同一把
- 互斥是针对多线程调用的,都只有一个线程了,跟谁互斥啊
看例子:
public class MyObject {
/**
* synchronized关键字加到非静态方法上,对应的是方法所在对象的锁,即是对象锁
* 该效果等同于同步代码块 synchronized(this){}
* @throws InterruptedException
*/
public synchronized void methodA() throws InterruptedException{
System.out.println("methodA() start..");
Thread.sleep(3000);
System.out.println("methodA() end...");
}
/**
* 这样写,锁也是当前对象
* @throws InterruptedException
*/
public void methodB() throws InterruptedException{
synchronized (this) {
System.out.println("methodB() start..");
Thread.sleep(3000);
System.out.println("methodB() end...");
}
}
/**
* synchronized关键字加到了静态方法上,锁为当前类
* @throws InterruptedException
*/
public static synchronized void methodC() throws InterruptedException{
System.out.println("methodC() start..");
Thread.sleep(3000);
System.out.println("methodC() end..");
}
/**
* 这样写,锁也是当前类
* @throws InterruptedException
*/
public void methodD() throws InterruptedException{
synchronized (MyObject.class) {
System.out.println("methodD() start..");
Thread.sleep(3000);
System.out.println("methodD() end..");
}
}
public class MyThread1 extends Thread{
MyObject myObject;
public MyThread1(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
try {
myObject.methodA();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyThread2 extends Thread{
MyObject myObject;
public MyThread2(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
try {
myObject.methodB();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyThread3 extends Thread{
MyObject myObject;
public MyThread3(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
try {
myObject.methodC();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyThread4 extends Thread{
MyObject myObject;
public MyThread4(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
try {
myObject.methodD();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyMain {
public static void main(String[] args) {
MyObject myObject = new MyObject();
MyThread1 myThread1 = new MyThread1(myObject);
MyThread2 myThread2 = new MyThread2(myObject);
MyThread3 myThread3 = new MyThread3(myObject);
MyThread4 myThread4 = new MyThread4(myObject);
/**
* 由于methodA methodB都加了synchronized关键字,锁对应的是对象myObject
* 所以如果有线程进入了A方法,其他线程不能立即运行B方法,只能等A方法占用的对象锁释放了,B方法才能被运行
*/
myThread1.start();
myThread2.start();
/**
* methodC methodD都加了synchronized关键字,锁对应的是MyObject类
* 所以C D两个方法是同步的,但和A B是异步的
*/
myThread3.start();
myThread4.start();
}
}
2.synchronized的可重入性
- 在加了synchronized关键字的方法中可以调用该对象其他加了synchronized关键字的方法
- 在继承关系中,子类synchronized方法可以调用父类synchronized关键字修饰的方法
这两个结论是作者原书中的,但我认为本质还是因为上面的第三点:
只有是同一把锁的代码在多线程调用时才会互斥
看代码:
public class FatherObject {
public synchronized void methodFather() throws InterruptedException{
System.out.println(Thread.currentThread().getName()+" methodFather() start..");
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName()+" methodFather() end...");
}
}
/**
* synchronized关键字可重入锁
* ①在methodA中调用methodB,是可以获得对象锁的,即自己可以再次获取自己的内部锁,如果没有这个机制会造成死锁
* 我认为本质还是因为两个方法对应的是同一个锁
* ②在methodB中调用methodFather,即使已经启动了其他线程调用了methodFather方法,并一直保持不释放获得FatherObject对象锁,
* 但methodB仍然可以调用methodFather,说明可重入也适用于继承关系
* 但我认为更真实的原因是因为当前这次调用拿到的锁不是上面的FatherObject对象(Main中第一行代码创建的对象),而是创建MyObject对象时,默认创建的FatherObject对象
* 也就是我们常说的,创建子类对象时,会默认调用父类无参构造方法
* 所以当然可以调了,你可以把methodB去掉同步关键字,发现仍然能调,这时候总和重入锁没关系了吧,我都没关键字了,在调用时也就没有拿到子类对象的锁了,但调用methodFather()
* 还是需要有FatherObject对象的锁啊,可是锁被MyThread2拿着,并未释放,我却仍能调用,说明这时候的锁是创建MyObject对象时,默认创建的FatherObject对象
*/
public class MyObject extends FatherObject{
public synchronized void methodA() throws InterruptedException{
System.out.println("methodA() start..");
Thread.sleep(1000);
methodB();
System.out.println("methodA() end...");
}
public synchronized void methodB() throws InterruptedException{
System.out.println("methodB() start..");
Thread.sleep(1000);
methodFather();
System.out.println("methodB() end...");
}
}
public class MyThread1 extends Thread{
MyObject myObject;
public MyThread1(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
try {
myObject.methodA();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyThread2 extends Thread{
FatherObject fatherObject;
public MyThread2(FatherObject fatherObject) {
this.fatherObject = fatherObject;
}
@Override
public void run() {
try {
fatherObject.methodFather();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyMain {
public static void main(String[] args) throws InterruptedException {
FatherObject fatherObject = new FatherObject();
MyObject myObject = new MyObject();
MyThread1 myThread1 = new MyThread1(myObject);
myThread1.setName("myThread1");
MyThread2 myThread2 = new MyThread2(fatherObject);
myThread2.setName("myThread2");
myThread2.start();
//确保FatherObject对象锁已被占用,锁对应的对象是第一行的fatherObject
Thread.sleep(500);
//这时候调用FatherObject的methodFather(),锁对应的对象是继承时自动创建的对象,而不是第一行的fatherObject
//所以两者相互不影响,当然能调用了
myThread1.start();
}
}
3.触类旁通
synchronized是不是和数据库事务隔离级别中的Serializable是一个思想,和数据库中的悲观锁也是一个思想,都是强制排队,虽然效率低了点,但最为强有力,有点一刀切的感觉。其实感觉各种技术核心思想都是互通的,真正重要的是理解这些思想,这才是日后解决问题的金钥匙啊。
网友评论