3

07.synchronized都问啥? - 王有志

 1 year ago
source link: https://www.cnblogs.com/wyz1994/p/17028834.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

大家好,我是王有志。关注王有志,一起聊技术,聊游戏,从北漂生活谈到国际风云。最近搞了个抽奖送书的活动,欢迎点击链接参与。

如果Java面试有什么是必问的,synchronized必定占据一席之地。初出茅庐时synchronized的用法,成长后synchronized的原理,可谓是Java工程师的“一生之敌”。

synchronized都问啥?

按照惯例,先来看synchronized的常见问题:

3063031-20230105204643894-1873233022.png

根据统计数据可以总结出synchronized的5大考点:

  • synchronized的使用方式:
    • synchronized是什么?
    • synchronized怎么用?
    • 不同用法都有什么效果?
  • synchronized的实现原理:
    • synchronized的特性是如何实现的?
    • synchronized锁升级的原理。

今天我们先来看synchronized的基础部分。

synchronized是什么?

synchronized是Java中的关键字,提供了原生同步机制,实现互斥语义和可见性保证,通常称为互斥锁

  • 互斥指的是,当线程获取到锁后,其它试图获取锁的线程只能阻塞;
  • 可见性指的是,synchronized修饰的语句内修改共享变量可以立即被其它线程获取。

互斥就意味着,同一时间只有一个线程执行synchronized修饰的代码,那么:

  • 无论怎么重排序,都会遵循as-if-serial语义,因此synchronized中不存在有序性问题;
  • 不主动释放锁,其他线程无法执行synchronized中代码,无需考虑原子性问题。

因此synchronized互斥就代表了对有序性问题和原子性问题的保证。不过前提是JSR-133中反复提到的correctly synchronized(正确的同步),举个例子:

public class IncorrectlySynchronized {

    private Integer count = 0;

    public  void add() {
        synchronized (count) {
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        IncorrectlySynchronized incorrectlySynchronized = new IncorrectlySynchronized();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                incorrectlySynchronized.add();
            }
        });
        Thread t2 = new Thread(()-> {
            for (int i = 0; i < 10000; i++) {
                incorrectlySynchronized.add();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(incorrectlySynchronized.count);
    }
}

看似该加synchronized的地方都加了,但是结果却会出乎意料,这就典型的错误同步的例子。

synchronized锁什么?

既然是锁,那么synchronized锁的是什么呢?

《The Java Language Specification》中描述(节选)到:

Each object in Java is associated with a monitor, which a thread can lock or unlock.
The synchronized statement computes a reference to an object; it then attempts to perform a lock action on that object's monitor and does not proceed further until the lock action has successfully completed.

Java中每个对象都与一个监视器关联,线程可以锁定或者解锁该监视器。synchronized语句尝试锁定与对象关联的监视器,锁定成功后才可以继续执行

通常,我们将synchronized锁定与对象关联的监视器理解为synchronized锁定对象本身

在我们知道synchronized锁什么后,再去看用法,很多内容就会一目了然了。

synchronized怎么用?

作为关键字,synchronized有两种用法:

  • 修饰代码块
  • 修饰方法
    • 修饰成员方法
    • 修饰静态方法

之前有个同事特别迷信“背技术”,为了区分不同用法的效果,背了某机构的“线程八锁”,但每过一段时间就会忘记。

其实,知道了synchronized锁什么,不同用法的效果自然就出来了,看一个例子:

public class SynchronizedDemo {
	public static void main(String[] args) throws InterruptedException {
	    SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
	    Thread t1 = new Thread(synchronizedDemo::lockMemberMethod1);
	    Thread t2 = new Thread(synchronizedDemo::lockMemberMethod2);
	    t1.start();
	    // 确保t1先执行
	    TimeUnit.SECONDS.sleep(1);
	    t2.start();
	}

	private synchronized void lockMemberMethod1() {
	    System.out.println("方法1");
	    try {
	        TimeUnit.SECONDS.sleep(10);
	    } catch (InterruptedException e) {
	        e.printStackTrace();
	    }
	}

	private synchronized void lockMemberMethod2() {
	    System.out.println("方法2");
	}
}

通过实例变量调用成员方法时,会隐式的传递this。这个例子中,t1和t2想锁定的监视器是谁的?synchronizedDemo对象的。t1先获取到,那么t2只能等待t1释放后再获取了。

那此时的锁定范围是什么?synchronizedDemo对象。

修改下代码:

public static void main(String[] args) throws InterruptedException {
	SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
	SynchronizedDemo synchronizedDemo2 = new SynchronizedDemo();
	Thread t1 = new Thread(synchronizedDemo::lockMemberMethod1);
	Thread t2 = new Thread(synchronizedDemo2::lockMemberMethod2);
	t1.start();
	t2.start();
}

t2不再争夺synchronizedDemo而是争夺synchronizedDemo2,结果上也能看出t1和t2之间不存在竞争关系。

那么使用synchronized修饰静态方法和代码块是什么效果呢?

private static synchronized void lockStaticMethod() {
    System.out.println("静态方法!"); 
}

private void lockCodeBlock(int count) {
    synchronized (this) {
        System.out.println("成员方法的代码块!");
    }
}

使用synchronized修饰静态方法,锁定的对象是SynchronizedDemo.class。所有SynchronizedDemo的实例对象共用同一个SynchronizedDemo.class,同一时间不同变量,只有一个线程可以执行lockStaticMethod方法。

至于synchronized修饰代码块,就比较灵活了,括号中是谁就锁定谁。如果是this就锁定实例变量,如果是SynchronizedDemo.class效果就和修饰静态方法一样。

至于前面错误的同步的例子,它的问题是count对象在不断变化(Integer实现相关)的,因此synchronized锁定的并不是同一个对象。

今天的内容非常基础,难度也不大。

重点可以放在synchronized锁什么的部分,以及是如何推导出synchronized不同用法产生的不同效果的。这样的方式更接近于问题的本质,也能更好的举一反三,而不是死记硬背“线程八锁”这种东西。


好了,今天就到这里了,Bye~~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK