java锁之-Synchronized

216 阅读5分钟

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

一、Synchronized的作用

  主要是保证多线程环境下的线程安全。

二、Synchronized种类

1. 对象锁

  1. 包含方法锁(默认锁对象为this当前实力对象),方法锁形式:synchronized修饰普通方法,锁默认对象为this
public class MySynchronized {
  public synchronized void m1(){

  }
}
上面这种方法可以被:下面这种方式替代:
 public  void m3(){
	synchronized(this){ //synchronized 需要包住m3 所有的内部代码片段
	
	  }
}
  1. 同步代码块锁(自己制定锁对象)代码块形式:手动指定锁对象
public class MySynchronized {
  public void m1(){
	 synchronized (this){
	 
	  }
}
或者如下
public class MySynchronized {
Object obj= new Object();
  public void m1(){
	 synchronized(obj){
	 
	  }
}

 

2. 类锁

指sychronized修饰静态的方法或指锁为Class对象, 概念: java类可能有有很多个对象,但是只有一个class对象 本质: 所以所谓的类锁,不过是Class对象的锁而已

用法和效果:类锁只能在同一时刻被一个对象拥有   形式1:synchronized加载static方法上

public class MySynchronized {
    public synchronized  static  void m1() {
        
    }
}

  形式2:synchronized(*.class)代码块

public class MySynchronized {
    public void m2() {
        synchronized (MySynchronized.class) {

        }
    }
}

三、常见多线程情况下Synchronized的举例

  1. 两个线程同时访问一个对象的相同的synchronized方法 串行

  2. 两个线程同时访问两个对象的相同的synchronized方法 锁对象不同,互不干扰,并行

  3. 两个线程同时访问两个对象的相同的static的synchronized方法 串行

  4. 两个线程同时访问同一对象的synchronized方法与非synchronized方法 并行

  5. 两个线程访问同一对象的不同的synchronized方法 同一对象锁,串行

  6. 两个线程同时访问同一对象的static的synchronized方法与非static的synchronized方法 锁不同,并行

  7. 方法抛异常后,会释放锁吗 如果一个线程在进入同步方法后抛出了异常,则另一个线程会立刻进入该同步方法

  8. 目前进入到被synchronized修饰的方法,这个方法里边调用了非synchronized方法,是线程安全的吗? 线程安全

四、synchronized的性质

1. 可重入性

指的是同一线程的外层函数获取锁之后,内层函数可以直接再次获取该锁

Java典型的可重入锁:synchronized、ReentrantLock 好处:避免死锁,提升封装性 粒度范围证明: 情况一:同一方法是可重入的---递归调用本方法

/**
 * 可重入测试----使用递归方式   
 * 情况1:同一个类中,同一方法可重入
 */
public class SynchronizedTest {
    int i = 0;
    //主线程可以重入以this为锁对象的method方法
    public static void main(String[] args) {
        SynchronizedTest s1 = new  SynchronizedTest();
        s1.method();
    }
    // 同步方法
    private synchronized void method() {
        if (i <=3) {
            i++;
            System.out.println(i);
            method();
        }
    }
}
输出:
1
2
3
4

情况二:可重入不要求是同一个方法

/**
 * 可重入测试----   
 * 情况2:同一个类中,不同方法可重入
 */
public class SynchronizedTest2 {
    //主线程可以重入以this为锁对象的method方法
    public static void main(String[] args) {
        SynchronizedTest2 s1 = new  SynchronizedTest2();
        s1.method1();
    }
    // 同步方法
    private synchronized void method1() {
        System.out.println("method1");
            method2();
        
    }
    private synchronized void method2() {
        System.out.println("method2");
    }
}
输出:
method1
method2

情况三:可重入不要求是同一个类中的

public class Demo1 {
    public synchronized void method(){
        System.out.println("我是Demo1");
    }

}
class Demo1Zi extends  Demo1{
    public synchronized void method(){
        System.out.println("我是Demo1儿子");
        super.method();
    }

    public static void main(String[] args) {
        Demo1Zi zi = new Demo1Zi();
        zi.method();
    }
}
输出:
我是Demo1儿子
我是Demo1

2. 不可中断性

一旦这个锁被别的线程获取了,如果我现在想获得,我只能选择等待或者阻塞,直到别的线程释放这个锁,如果别的线程永远不释放锁,那么我只能永远的等待下去。

相比之下,Lock类可以拥有中断的能力,第一点:如果我觉得我等待的时间太长了,有权中断现在已经获取到锁的线程执行;第二点:如果我觉得我等待的时间太长了不想再等了,也可以退出。

五、synchronized的缺点

1、效率低(jdk1.6对其进行了优化)

1)、锁的释放情况少(线程执行完成或者异常情况释放)

2)、试图获得锁时不能设定超时(只能等待)

3)、不能中断一个正在试图获得锁的线程(不能中断)

==jdk1.6对synchronized进行了优化,有一个锁升级的过程,使得效率有一个提升,但是锁升级不可逆。== 在这里插入图片描述

2、不够灵活

加锁和释放的时机比较单一,每个锁仅有单一的条件(某个对象),可能是不够的

比如:读写锁更灵活

六、如何选择Lock和synchronized关键字

本着优先避免出错的原则,得出以下结论:

  1. 如果可以的话,尽量优先使用java.util.concurrent各种类(不需要考虑同步工作,不容易出错)
  2. 优先使用synchronized,这样可以减少编写代码的量,从而可以减少出错率
  3. 若用到Lock或Condition独有的特性,才使用Lock或Condition

七. 总结

JVM会自动通过使用monitor来加锁和解锁,保证了同一时刻只有一个线程可以执行指定的代码,从而保证线程安全,同时具有可重入和不可中断的特性。

八. 参考链接

阿里面试:跟我死磕Synchronized底层实现,我满分回答拿了Offer

Java之戳中痛点 - (8)synchronized深度解析

Synchronized的作用