首页 > 编程语言 >Java关键词synchronized解读

Java关键词synchronized解读

时间:2022-12-30 00:13:20浏览次数:60  
标签:Synchronized Java synchronized Thread 对象 关键词 线程 new

目录

1 引入Synchronized

  1. Synchronized是java虚拟机为线程安全而引入的。
  2. 互斥同步是一种最常见的并发正确性的保障手段。同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条线程使用。
  3. synchronized是最基本的互斥同步手段,它是一种块结构的同步语法。
  4. synchronized修饰代码块,无论该代码块正常执行完成还是发生异常,都会释放锁

synchronized对线程访问的影响:

  • 被synchronized修饰的同步块在持有锁的线程执行完毕并释放锁之前,会阻塞其他线程的进入。
  • 被synchronized修饰的同步块对同一条线程是可重入

2 Synchronized的使用

可以作用在方法上或者方法里的代码块:

  1. 修饰方法,包括实例方法和静态方法
  2. 修饰方法里的代码块,这时需要一个引用作为参数
  3. Synchronized作用地方不同,产生的锁类型也不同,分为对象锁和类锁

2.1 对象锁

Synchronized修饰实例方法或者代码块(锁对象不是*.class),此时生产对象锁。多线程访问该类的同一个对象的sychronized块是同步的,访问不同对象不受同步限制。

2.1.1 Synchronized修饰实例方法

public static void main(String[] args){
        TempTest tempTest = new TempTest();
        Thread t1 = new Thread(() -> {
            tempTest.doing(Thread.currentThread().getName());
        });
        Thread t2 = new Thread(() -> {
            tempTest.doing(Thread.currentThread().getName());
        });
        t1.start();
        t2.start();
    }

    //同一时刻只能被一个线程调用
    private synchronized void doing(String threadName){
        for(int i=0;i<3;i++){
            System.out.println("current thread is : "+threadName);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {}
        }
    }

运行结果:

current thread is : Thread-0
current thread is : Thread-0
current thread is : Thread-0
current thread is : Thread-1
current thread is : Thread-1
current thread is : Thread-1

2.1.2 Synchronized修饰代码块

public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence = new SynchronizedObjectLock();
    @Override
    public void run() {
        // 同步代码块形式:锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行
        synchronized (this) {
            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(instence);
        Thread t2 = new Thread(instence);
        t1.start();
        t2.start();
    }
}

运行结果:

我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束

2.2 类锁

synchronize修饰静态方法或指定锁对象为Class,此时产生类锁。多线程访问该类的所有对象的sychronized块是同步的

2.2.1 synchronize修饰静态方法

    public static void main(String[] args){
        TempTest tempTest1 = new TempTest();
        TempTest tempTest2 = new TempTest();
        //虽然创建了两个TempTest实例,但是依然是调用同一个doing方法(因为是个static);因此doing还是会依次执行
        Thread t1 = new Thread(() -> tempTest1.doing(Thread.currentThread().getName()));
        Thread t2 = new Thread(() -> tempTest2.doing(Thread.currentThread().getName()));
        t1.start();
        t2.start();
    }

    //修饰静态方法,则是类锁;
    private static synchronized void doing(String threadName){
        for(int i=0;i<3;i++){
            System.out.println("current thread is : "+threadName);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {}
        }
    }

运行结果:有序输出 【如果去掉static ,则线程会交替执行doing】

current thread is : Thread-0
current thread is : Thread-0
current thread is : Thread-0
current thread is : Thread-1
current thread is : Thread-1
current thread is : Thread-1

2.2.2 synchronize指定锁对象为Class

public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
    static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();

    @Override
    public void run() {
        // 所有线程需要的锁都是同一把
        synchronized(SynchronizedObjectLock.class){
            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(instence1);
        Thread t2 = new Thread(instence2);
        t1.start();
        t2.start();
    }
}

结果:

我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束

3 Synchronized原理分析

3.1 虚拟机如何辨别和处理synchronized

  • 虚拟机可以从常量池中的方法表结构中的ACC_ SYNCHRONIZED访问标志区分一个方法是否是同步方法。
  • 当调用方法时,调用指令将会检查方法的ACC_ SYNCHRONIZED访问标志是否设置,如果设置了,执行线程将先持有同步锁,然后执行方法,最后在方法完成时释放同步锁。
  • 在方法执行期间,执行线程持有了同步锁,其他任何线程都无法再获得同一个锁。
  • 如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的锁将在异常抛到同步方法之外时自动释放。

3.2 虚拟机对synchronized的编译处理

以下代码:

public class Foo {
    void onlyMe(Foo f) {
        synchronized(f) {
            doSomething();
        }
    }
    private void doSomething(){ }
}

编译后,这段代码生成的字节码序列如下:

  1. synchronized关键字经过Javac编译之后,会在同步块的前后生成monitorentermonitorexit两个字节码指令。
  2. 指令含义:monitorenter:获取对象的锁monitorexit:释放对象的锁
  3. 执行monitorenter指令时,首先尝试获取对象的锁。如果对象没被锁定,或者当前线程已经持有了对象的锁,就把锁的计数器的值增加1
  4. 执行monitorexit指令时,将锁计数器的值减1,一旦计数器的值为零,锁随即就被释放
  5. 如果获取对象锁失败,那当前线程阻塞等待,直到锁被释放。
  6. 为了保证在方法异常完成时monitorenter和monitorexit指令依然可以正确配对执行,编译器会自动产生一个异常处理程序,它的目的就是用来执行monitorexit指令。

3.3 虚拟机执行加锁和释放锁的过程

那么重点来了到这里,有几个问题需明确:

  1. 什么叫对象的锁
  2. 如何确定锁被线程持有
  3. 执行monitorenter后,对象发生什么变化
  4. 锁计数值保存在哪里,如何获取到?

1. 什么叫对象的锁?
对象的内存结构参考:2 Java内存层面的对象认识

  1. 锁,一种可以被读写的资源,对象的锁是对象的一部分。
  2. 对象的结构中有部分称为对象头
  3. 对象头中有2bit空间,用于存储锁标志,通过该标志位来标识对象是否被锁定。

2. 如果确定锁被线程持有?

  1. 代码即将进入同步块的时,如果锁标志位为“01”(对象未被锁定),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录的空间,存储锁对象Mark Word的拷贝。(线程开辟空间并存储对象头)
  2. 虚拟机将使用CAS操作尝试把对象的Mark Word更新成指向锁记录的指针(对象头的mw存储指向线程“锁记录”中的指针)
  3. 如果CAS操作成功,即代表该线程拥有了这个对象的锁,并且将对象的锁标志位转变为“00”
  4. 如果CAS操作失败,那就意味着至少存在一条线程与当前线程竞争获取该对象的锁。虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是,说明当前线程已经拥有了这个对象的锁,那直接进入同步块继续执行,否则就说明这个锁对象已经被其他线程抢占。
  5. 解锁过程:CAS操作把线程中保存的MW拷贝替换回对象头中。假如能够成功替换,那整个同步过程就顺利完成了;如果替换失败,则说明有其他线程尝试过获取该锁,就要在释放锁的同时,唤醒被挂起的线程。

3 执行monitorenter后,对象发生什么变化?

  1. 对象的锁标志位转变为“00”
  2. 拥有对象锁的线程开辟了新空间,保存了对象的Mark Word信息
  3. 对象的Mark Word保存了线程的锁记录空间的地址拷贝

4 锁计数值保存在哪里
我还没搞懂。

monitorenter指令执行的过程

4 Synchronized与Lock

synchronized的缺陷

  1. 在多线程竞争锁时,当一个线程获取锁时,它会阻塞所有正在竞争的线程,这样对性能带来了极大的影响。
  2. 挂起线程和恢复线程的操作都需要转入内核态中完成,上下文切换需要消耗很大性能。
  3. 效率低:锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时
  4. 不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活

5 使用Synchronized有哪些要注意的

  • 锁对象不能为空,因为锁的信息都保存在对象头里
  • 作用域不宜过大,影响程序执行的速度,控制范围过大,编写代码也容易出错
  • 在能选择的情况下,既不要用Lock也不要用synchronized关键字,用java.util.concurrent包中的各种各样的类,如果有必要,使用synchronized关键,因为代码量少,避免出错
  • synchronized实际上是非公平的,新来的线程有可能立即获得执行,而在等待区中等候已久的线程可能再次等待,这样有利于提高性能,但是也可能会导致饥饿现象。

标签:Synchronized,Java,synchronized,Thread,对象,关键词,线程,new
From: https://www.cnblogs.com/knowledgeispower/p/17013849.html

相关文章

  • Java面试题-数据库
    数据库的存储引擎以及锁机制锁的存在是为了数据的一致性,mysql在修改数据层面是支持并发修改的,那么在多个线程同时修改一个数据时产生的线程安全问题。我们知道MySQL主要有......
  • Java 遍历 Map 的 5 种方式
    Java中遍历Map有多种方法,从最早的迭代器Iterator,到JDK5开始支持的增强型for循环——即foreach,再到JDK8的Lambda表达式,让我们一起来看下具体的用......
  • JavaWeb_多级查询 案例_表数据及练习详解
    此案例来自于B站黑马程序员JavaWeb教程,由于视频未给表数据,故特此手打于此,大家有需要可自取练习(也付有相应多级查询练习题和解答,可供参考)黑马程序员JavaWeb教程地址:https......
  • 2.1JavaScript基础语法
    第一章1计算机编程基础什么是编程语言编程语言和标记语言的不同常见的数据存储单位及其换算关系内存的主要作用以及特点2编程语言编程:就是让计算机为解决某个问题而使用......
  • 4.1 Javascript高级
    JavaScript面向对象目标:能够说出什么是面向对象能够说出类和对象的关系能够使用class创建自定义类能够说出什么是继承面向对象编程介绍:两大编程思想:面向过程面向对象面向过......
  • Java开发学习(四十九)----MyBatisPlus更新语句之乐观锁
    1、概念在讲解乐观锁之前,我们还是先来分析下问题:业务并发现象带来的问题:秒杀假如有100个商品或者票在出售,为了能保证每个商品或者票只能被一个人购买,如何保证不会出......
  • Java SE:static package this关键字与访问权限
    JAVA课程-类和对象static修饰符1.static修饰变量在类中,被static修饰的变量叫做类变量,否则叫做实例变量。类变量:例如staticinta,为整个类中所有对象所共享,只能存在一......
  • Java面向对象:子类与继承
    今天听java课程感觉难度突然增加,因此注意上课记录关键笔记,提醒自己回顾。JAVA子类与继承子类与父类问题:哪些父类的变量与方法可被子类继承?与访问权限有关前提知识:jav......
  • java中的字符串反转
    本文主要讲述java中的字符串反转示例代码如下:1publicclassHomeWork01{2publicstaticvoidmain(String[]args){3Strings="abcdef";4......
  • 数组方法 JavaScript
    //连接两个数组consta1=[1,2,3];consta2=[4,5,6];consta3=a1.concat(a2);console.log(a3);console.log("----------------------------------------......