Java并发——synchronized关键字

Java并发——synchronized关键字

1.synchronized作用的范围

synchronized有两种作用范围:对象锁类锁

对象锁

使用方式: 在普通方法上加synchronized(默认锁对象为this)和同步代码块(自己指定锁对象)

在普通方法上加锁(默认锁对象为this)

public class SynchronizedLock implements Runnable{
    
    

    static  SynchronizedLock instance = new SynchronizedLock();

    @Override
    public void run() {
    
    
        method();
    }

    // 修饰普通方法 默认锁this
      public synchronized void method (){
    
    
            System.out.println("线程"+Thread.currentThread().getName()+"开始执行");

            try {
    
    
                Thread.sleep(1000);
            } 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();
    }
}

/* 执行结果
 *
 *线程Thread-0开始执行
 *线程Thread-0结束
 *线程Thread-1开始执行
 *线程Thread-1结束
 *
 */

同步代码块指定锁对象this

public class SynchronizedLock implements Runnable{
    
    

    static  SynchronizedLock instance = new SynchronizedLock();

    @Override
    public void run() {
    
    
        //同步代码块——锁this,两个线程使用的锁都是instance对象的锁,线程1需要等线程0释放锁

        synchronized (this){
    
    
            System.out.println("线程"+Thread.currentThread().getName()+"开始执行");

            try {
    
    
                Thread.sleep(1000);
            } 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();
    }
}

/* 执行结果
 *
 *线程Thread-0开始执行
 *线程Thread-0结束
 *线程Thread-1开始执行
 *线程Thread-1结束
 *
 */

类锁

synchronized修饰静态方法或者指定锁对象为Class对象

修饰静态方法

public class SynchronizedLock implements Runnable{
    
    
  // 使用两个不同的对象 证明结果是由类锁造成的
    static  SynchronizedLock instance1 = new SynchronizedLock();
    static  SynchronizedLock instance2 = new SynchronizedLock();
    @Override
    public void run() {
    
    
        method();
    }
  // synchronized修饰静态方法,默认锁的就是当前class类
      public static synchronized void method (){
    
    
            System.out.println("线程"+Thread.currentThread().getName()+"开始执行");

            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("线程"+Thread.currentThread().getName()+"结束");
        }



    public static void main(String[] args) {
    
    

        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);

        t1.start();
        t2.start();
    }
}

/* 执行结果
 *
 *线程Thread-0开始执行
 *线程Thread-0结束
 *线程Thread-1开始执行
 *线程Thread-1结束
 *
 */

注意: 结果虽然是和锁普通方法相同,但是锁静态方法使用的是两个不同的对象instance1 instance2,造成两个线程串行执行是因为sychronized锁的是当前类,两个对象使用一把锁

指定锁对象为Class类

public class SynchronizedLock implements Runnable{
    
    
  // 使用两个不同的对象 证明结果是由类锁造成的
    static  SynchronizedLock instance1 = new SynchronizedLock();
    static  SynchronizedLock instance2 = new SynchronizedLock();
    @Override
    public void run() {
    
    
        // synchronized修饰指定类,锁对应的Class类
        synchronized (SynchronizedLock.class){
    
    
            System.out.println("线程"+Thread.currentThread().getName()+"开始执行");

            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("线程"+Thread.currentThread().getName()+"结束");
        }
    }



    public static void main(String[] args) {
    
    

        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);

        t1.start();
        t2.start();
    }
}

/* 执行结果
 *
 *线程Thread-0开始执行
 *线程Thread-0结束
 *线程Thread-1开始执行
 *线程Thread-1结束
 *
 */

2.synchronized原理

synchronized基于JVM实现,随着JVM的升级不断优化。

在JVM虚拟机中,对像在内存中的存储分为三个部分:

  • 对象头
  • 实例数据
  • 对齐填充

在这里插入图片描述

标记字段Mark Word

用于存储对象自身的运行时数据,HashCodeGC Age锁标记位是否为偏向锁

64位JVM中Mark Word组成
在这里插入图片描述

加锁

每一个对象在同一时间只与一个Monitor监视器相关联,而一个Monitor在同一时间只能被一个线程获得,一个对象在尝试获取与这个对象相关联的Monitor锁的所有权时,Monitorenter指令会发生三种情况

  • Monitor计数器为0,表示目前没有其他线程获取,该线程会立刻获取并把锁计数器加1,之后其他线程想要获取这个monitor就需要等待
  • 如果这个Monitor已经获取了这个锁的所有权,又重入了这把锁,锁计数器进行累加
  • 这把锁已经被其他线程获取了,等待锁释放

释放锁

释放对于monitor的所有权,将monitor的计数器减1,如果减完之后计数器不为0,代表是重入的当前线程还继续持有这把锁,如果计数器为0,表示当前线程不再拥有monitor的所有权,就是释放锁

在这里插入图片描述

总结:

任意一个线程对某个对象的访问,首先要获取对象的监视器,如果获取失败,线程就进入同步队列中等待,当对象的监视器被其他线程释放后,在同步队列中的线程就重新尝试获取该监视器

可重入原理:

当一个线程获取对象锁成功后,该对象的monitor锁计数器+1,当该线程再次获取时计数器再+1,释放时释放一次就计数器-1,直到减为0,锁被释放(计数器次数就是重入次数)

3.锁升级

无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁

  • 无锁

没有对资源进行锁定,所有线程都能访问修改同一个资源,但只有一个线程能修改成功

  • 偏向锁

大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得,为了让线程获取锁的代价更低而引入了偏向锁。当一个线程访问同步代码块并获取锁时,会在对象头和栈帧中记录锁偏向的线程ID。在线程访问同步代码块时先判断对象头的Mark Word里面是否存储指向当前线程的偏向锁,如果存在就直接获取锁。

  • 轻量级锁

当另一个线程尝试竞争偏向锁时,锁升级为轻量级锁。线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于锁记录的空间(Lock Record),并采用CAS将对象头中的Mark Word替换成指向锁记录的指针。如果成功,当前线程获取锁,如果失败,标识其他线程竞争锁,锁升级为重量级锁。

  • 重量级锁

在重量级锁时,线程通过自旋获取锁,在自旋的过程中占用消耗CPU资源,如果长时间自旋会影响性能。因此需要设定自旋次数,默认10次,如果10次自旋依旧没获得锁,则会被阻塞等待。当其他线程尝试获取锁时,都会被阻塞,当持有锁的线程释放锁后,才会唤醒这些线程。

4.synchronized的优缺点

synchronized的优点

  • 使用简单,不需要手动去释放锁
  • 基于JVM实现,在迭代中不断优化升级

synchronized的缺点

  • 效率低:锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁,尝试获取锁时不能设置超时,也不能中断一个正在使用锁的线程
  • 不够灵活:加锁和释放锁的时机单一,没有Lock灵活

使用synchronized的注意点

  • 锁对象不能为空,因为锁的信息都保存在对象头中
  • 作用范围不宜过大,影响程序执行的效率
  • 避免死锁

参考文章

《Java并发编程的艺术》

https://pdai.tech/md/java/thread/java-thread-x-key-synchronized.html

猜你喜欢

转载自blog.csdn.net/qq_52595134/article/details/128390813
今日推荐