07.synchronized都问啥?

简介: 大家好,我是王有志。经过JMM和锁的铺垫,今天我们正式进入synchronized的内容,来看看关于synchronized面试中都会问啥?

大家好,我是王有志,欢迎和我聊技术,聊漂泊在外的生活。快来加入我们的Java提桶跑路群:共同富裕的Java人

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

synchronized都问啥?

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

图1:synchronized都问啥?.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~~

目录
相关文章
|
Java 测试技术 应用服务中间件
常见 Java 代码缺陷及规避方式(下)
常见 Java 代码缺陷及规避方式(下)
285 0
|
安全 算法 编译器
【C++ 泛型编程 入门篇】 C++ 模板元编程之枚举内嵌 实战教程
【C++ 泛型编程 入门篇】 C++ 模板元编程之枚举内嵌 实战教程
294 0
|
监控 安全 Java
常见 Java 代码缺陷及规避方式(中)
常见 Java 代码缺陷及规避方式(中)
256 1
|
11月前
|
监控 Java 数据中心
微服务架构系统稳定性的神器-Hystrix
Hystrix是由Netflix开源的库,主要用于微服务架构中的熔断器模式,防止服务调用失败引发级联故障。它通过监控服务调用的成功和失败率,在失败率达到阈值时触发熔断,阻止后续调用,保护系统稳定。Hystrix具备熔断器、资源隔离、降级机制和实时监控等功能,提升系统的容错性和稳定性。然而,Hystrix也存在性能开销、配置复杂等局限,并已于2018年进入维护模式。
207 0
|
负载均衡 网络协议 应用服务中间件
如何配置 DNS 轮询负载均衡以实现高可用性
如何配置 DNS 轮询负载均衡以实现高可用性
945 4
|
小程序 数据挖掘 UED
餐饮店小程序开发定制桌边二维码点餐系统
随着技术不断进步,各行各业都在使用新工具来提高效率和服务质量。餐饮业也不例外。餐饮点餐小程序系统是基于微信公众平台开发的在线点餐方式。顾客可以通过手机微信扫描餐桌上的二维码,进入餐厅的点餐小程序,选择菜品、数量和口味,直接完成点餐。点餐系统会自动保存并发送给厨房,避免了传统手工点餐容易出错的问题。
|
前端开发 UED Python
Wagtail-基于Python Django的内容管理系统CMS实现公网访问
Wagtail-基于Python Django的内容管理系统CMS实现公网访问
329 0
|
数据采集 存储 搜索推荐
使用Python构建自定义搜索引擎:从数据抓取到索引与搜索
使用Python构建自定义搜索引擎:从数据抓取到索引与搜索
|
Kubernetes 云计算 开发者
云计算中的容器化技术:Docker与Kubernetes的实践
云计算中的容器化技术:Docker与Kubernetes的实践
1297 0
|
缓存 固态存储 关系型数据库
从索引到事务,MySQL高可用调优全攻略
MySQL是目前最流行的关系型数据库之一,但是在高并发情况下,它的性能和可靠性会受到很大的挑战。本文将介绍MySQL高可用方案中的索引优化、事务管理以及调优技巧,帮助读者提升MySQL应用的性能和可靠性。
282 0