Synchronized关键字

思考:不使用并发手段会有什么后果?
一、一句话总结其作用:
能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。
二、Synchronized 的两个用法

  1. 对象锁
    1.1 代码块形式
    栗子:
public class SynchronizedObjectCodeBlock2 implements Runnable{

    static SynchronizedObjectCodeBlock2 instance =  new SynchronizedObjectCodeBlock2();
    Object lock1 = new Object();//自己设置对象,自己设置锁,等同于this
    Object lock2 = new Object();
    @Override
    public void run() {
        synchronized (lock1){//synchronized (this)
            System.out.println("我是lock1。我叫"+Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"lock1部分运行结束。");
        }

        synchronized (lock2){//synchronized (this)
            System.out.println("我是lock2。我叫"+Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"lock2部分运行结束。");
        }
    }
    public static void main(String[] args){
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){
        }
       System.out.println("finished!");
    }

1.2 方法锁示例:
栗子:

public class SynchronizedObjectMethod3 implements  Runnable{
    static SynchronizedObjectMethod3 instance =  new SynchronizedObjectMethod3();

    @Override
    public void run() {
       method();
    }
    public synchronized  void method(){
        System.out.println("我是方法锁形式。我叫"+Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"运行结束。");
    }
    public static void  main(String[] args){
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){
        }
        System.out.println("finished!");
    }
}
  1. 类锁
    2.1 概念(重要):Java类可能有很多对象,但只有一个Class对象
    本质:所谓类锁,不过是Class 对象的锁而已。
    用法和效果:类锁只能在同一个时刻被一个对象拥有
    2.2 形式1:synchronized加在static 方法上
**
 * 类锁的第一种形式,static形式
 */
public class SynchronizedClassStatic4 implements  Runnable{
    static SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4();
    static SynchronizedClassStatic4 instance2 = new SynchronizedClassStatic4();
    public void run(){
        method();
    }
    public static synchronized  void method(){
    //不加static 关键字 时,2个线程几乎同时运行,加了static 第一个线程运行结束后,第二个才运行
        System.out.println("我是类锁的第一种形式。我叫"+Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"运行结束。");
    }
    public static void  main(String[] args) throws InterruptedException{
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){
        }
        System.out.println("finished!");
    }
}

加了static 关键字就是方法:
Synchronized关键字
不加static 关键字:
Synchronized关键字
形式2:

public class SynchronizedClassClass5 implements  Runnable{
   static SynchronizedClassClass5 instance1 = new SynchronizedClassClass5();
   static SynchronizedClassClass5 instance2 = new SynchronizedClassClass5();
   public void  run(){
       try {
           method();
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
   private void method() throws InterruptedException{
       //无论哪个对象,应用的都是同一个SynchronizedClassClass5.class实例
       synchronized (SynchronizedClassClass5.class){
           System.out.println("我是类锁的第二种形式(*.class)。我叫"+Thread.currentThread().getName());
           Thread.sleep(3000);
           System.out.println(Thread.currentThread().getName()+"运行结束!");
       }
   }
   public static void  main(String[] args) throws InterruptedException{
       Thread t1 = new Thread(instance1);
       Thread t2 = new Thread(instance2);
       t1.start();
       t2.start();
       while (t1.isAlive() || t2.isAlive()){
       }
       System.out.println("finished!");
   }
}

三、七种常见情况:

  1. 两个线程同时访问一个对象的同步方法

  2. 两个线程访问的是两个对象(指两个实例)的同步方法
    他们之间不受干扰,因为采用的锁不是同一个

  3. 两个线程访问的是synchronized 的静态方法
    一个一个执行,锁生效

  4. 同时访问同步非同步方法
    非同步方法不受到影响

  5. 访问同一个对象的不同的普通同步方法
    串行的,不能同步运行

  6. 同时访问静态synchronized非静态synchronized方法
    可以并行执行

  7. 方法抛异常后,会释放锁

三点核心思想:
1.一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应1,5情况)
2.每个实例都对应有自己的一把锁,不同实例之间互不影响;例外锁对象时*.class以及synchronized修饰的是static方法的时候,所有的对象共用一把类锁(对应2,3,4,6情况)
3.无论是方法正常执行完毕,或者方法抛出异常,都会释放锁(对应7情况)

四、Synchronized的性质:

  1. 可重入(递归锁)
    定义:指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。
    好处:避免死锁、提升封装性
    粒度(范围):线程而非调用
    • 情况一:证明同一个方法是可重入
    • 情况二:证明可重入不要求是同一个方法
    • 情况三:证明可重入不要求是同一个类中的
  2. 不可中断
    一旦这个锁已经被别人获得,如果我还想获得,我只能选择等待或者阻塞,直到别的线程释放这个锁。如果别人永远不释放这个锁,那么我只能永远地等下去。
    而Lock类: a.可以中断别的线程,b.不想等待可以退出

五、原理:

  1. 加锁和释放锁的原理:现象、获取和释放锁的时机
    现象:
    获取和释放锁的时机:内置锁。深入JVM看字节码,锁信息都是保存在对象头中的
    synchronized字段:是通过一个对象头来实现的。
    当线程执行到moniterenter,就会尝试获取该对象的锁,而moniterexit是释放锁的
/**
 * 反编译字节码
 * 打开终端-》进入到类路径--》编译该类==》输入 javap -verbose "类.class"文件
 */
public class DeCompilation14 {
    private Object object = new Object();
    public void insert (Thread thread){
        synchronized (object){
        }
    }
}

上面方法反编译后:
Synchronized关键字
解读moniterenter指令:加锁,会在执行的时候让对象的锁计数器加1
每一对象都与moniterenter有关
解读moniterexit指令:会在执行的时候让对象的锁计数器减1
释放锁的所有权

  1. 可重入原理:加锁次数计数器(可加可减的计数器)
    2.1 JVM负责跟踪对象被加锁的次数
    2.2 线程第一次给对象加锁的时候,计数变为1.。每当这个相同的线程在此对象上再次获得锁时,计数会递增
    2.3 当任务离开时,计数递减,当计数为0时,锁完全被释放
  2. 可见性原理:Java内存模型
    每个线程都有独立的本地内存副本,每当副本改变,都会同步到主内存中。其他线程也会去刷新本地内存的副本,通过这个方法,来实现线程之间同步。

六、缺陷:

  1. 效率低(Lock效率更高):锁的释放情况少、试图获得锁时不能设定超时、不能中断一个正试图获得锁的线程。
  2. 不够灵活(读写锁更灵活):加锁和释放锁的时机单一
  3. 无法知道是否成功获取到锁

七、常见面试问题:

  1. 使用synchronized对象注意点:锁对象不能为空、作用域不易过大、避免死锁
  2. 如何选择Lock和synchronized关键字?核心思想:避免出错
    尽量是用包装的类,不易出错。如果非要选择的话,优先选择synchronized,可以减少代码编写,非要使用Lock的话,才使用Lock接口
  3. 多线程访问同步方法的各种具体情况

八、总结:
一句话介绍synchronized:
JVM会自动通过使用moniter来加锁和解锁,保证了同时只有一个线程可以执行指定代码,从而保证了线程安全,同时具有可重入和不可中断的性质。

相关文章:

猜你喜欢
  • 2021-05-04
  • 2022-01-10
  • 2021-07-08
相关资源
相似解决方案