美文网首页
4.synchronized关键字

4.synchronized关键字

作者: xialedoucaicai | 来源:发表于2018-05-19 20:36 被阅读0次

1.谁是那把锁

上节买票示例中,通过加上synchronized关键字,给代码加锁,只有拿到锁的线程才能执行目标代码,也可以使用synchronized(this){}将buy()方法包住,这两种方式有什么区别?为什么要用this,我用其他Object可以吗?可以换成Object.class吗?有时候看到synchronized加到静态方法上,这和加到非静态方法上效果一样吗?
要回答这些问题,首先就要明白谁是那把锁,这对于正确使用synchronized关键字显得尤为重要。

  1. synchronized关键字加到非静态方法上,锁是方法所在对象,该效果等同于同步代码块 synchronized(this){}
  2. synchronized关键字加到了静态方法上,锁为当前类,该效果等同于synchronized (MyObject.class) {}
  3. 只有是同一把锁的代码在多线程调用时才会互斥,这一点非常重要,它包含两重意思
  • 对于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的可重入性

  1. 在加了synchronized关键字的方法中可以调用该对象其他加了synchronized关键字的方法
  2. 在继承关系中,子类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是一个思想,和数据库中的悲观锁也是一个思想,都是强制排队,虽然效率低了点,但最为强有力,有点一刀切的感觉。其实感觉各种技术核心思想都是互通的,真正重要的是理解这些思想,这才是日后解决问题的金钥匙啊。

相关文章

网友评论

      本文标题:4.synchronized关键字

      本文链接:https://www.haomeiwen.com/subject/fabgdftx.html