synchronized背后不为人知的秘密

Synchronized

为什么要学习Synchronized?

在我们学习多线程的时候,会遇到共享内存两个重要的问题。一个是竞态条件,另一个是内存可见性。解决这两个问题的一种方案是使用Synchronized。
在介绍什么是竞态条件,什么是内存可见性之前,我们先讲解一下synchronized的用法和基本原理。

用法 (synchronized可以用于修饰类的实例方法、静态方法和代码块)

  • synchronized修饰普通同步方法:锁对象为当前实例对象
public synchronized void sayHello(){
	System.out.println("Hello World");
}
  • synchronized修饰静态同步方法:锁对象为当前的类Class对象
public static synchronized void sayHello(){
	System.out.println("Hello World");
}
  • synchronized修饰同步代码块:锁对象是synchronized后面括号里配置的对象这个对象可以使某个对象,也可以是某个类。
synchronized(this){}
synchronized(""){}
synchronized(xxx.class){}

注意事项:

1.使用synchronized修饰非静态方法或者使用synchronized修饰代码块时制定的为实例对象时,同一个类的不同对象拥有自己的锁,因此不会相互阻塞。

下面代码中synchronized中参数为this,而我创建了2个实例对象,此时锁对象为this。代码结果表明了同一个类的不同对象拥有自己的锁,因此不会相互阻塞。

public class ThreadTest03 extends Thread{
    private int number=10;
    @Override
    public void run() {
        synchronized (this){
            say();
            for(int i=10;i>0;i--){
                if(number>0){
                    System.out.println(Thread.currentThread().getName()+"  "+--number);
                }
            }
        }
    }
    public synchronized  void say(){
        System.out.println(Thread.currentThread().getName()+"我会说话");
    }
    public static void main(String[] args) {
        ThreadTest03 t1=new ThreadTest03();
        ThreadTest03 t2=new ThreadTest03();
        t1.start();
        System.out.println("t1启动");
        t2.start();
        System.out.println("t2启动");
    }
}

结果展示:
在这里插入图片描述
2. 使用synchronized修饰类和对象时,由于类对象和实例对象分别拥有自己的监视器锁,因此不会相互阻塞。

类对象只有一个,而实例对象可以有多个。当synchronized参数为类对象时,因为类对象只有一个,当其中一个A线程拿到这把锁时,另一个B线程会被阻塞,因为这个线程拿不到这把锁。只能等A线程释放这把锁。而synchronized参数为实例对象时,下边的代码的实例对象有2个,所以当synchronized的参数为this时,谁调用,这个this就是哪一个实例对象。对象不同,所以他们拥有自己的监视器锁,因为不会产生相互阻塞的情况。

public class ThreadTest03 extends Thread{
    private int number=10;
    @SneakyThrows
    @Override
    public void run() {
        getThreadClass();
        synchronized (ThreadTest03.class){
            for(int i=10;i>0;i--){
                if(number>0){
                    System.out.println(Thread.currentThread().getName()+"  "+--number);
                }
            }
        }
    }
    public  void getThreadClass() throws InterruptedException {
        synchronized (this){
            System.out.println(Thread.currentThread().getName()+" "+this.getState());
            Thread.sleep(1000);
            for(int i=0;i<5;i++){
                System.out.println(this.getName());
            }
        }
    }
    public static void main(String[] args) {
        ThreadTest03 t1=new ThreadTest03();
        ThreadTest03 t2=new ThreadTest03();
        t1.start();
        System.out.println("t1启动");
        t2.start();
        System.out.println("t2启动");
    }
}

结果展示:
在这里插入图片描述
这里就展示了一部分结果,从线程状态来看,当线程调用synchronized参数为this的代码块时,t1,t2为2个不同实例对象,因为各自有自己的锁,互不阻塞。

3.使用使用synchronized修饰实例对象时,如果一个线程正在访问实例对象的一个synchronized方法时,其它线程不仅不能访问该synchronized方法,该对象的其它synchronized方法也不能访问,因为一个对象只有一个监视器锁对象,但是其它线程可以访问该对象的非synchronized方法。

public class SynLock02 {
    public static void main(String[] args) {
        Phone1 p1=new Phone1();
        Phone1 p2=new Phone1();
        new Thread(()->p.SendSms(),"A").start();
        new Thread(()->p.call(),"B").start();
        new Thread(()->p2.sayHello(),"C").start();
    }
}

class Phone1{
    // synchronized 锁的是方法的调用者,谁先调用,谁先执行
    public synchronized  void call(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我会打电话");
    }
    public synchronized void sendSms(){
        System.out.println("我会发短信");
    }
    // 普通方法不受锁的控制
    public void sayHello(){
        System.out.println("hello");
    }
}

结果展示:
在这里插入图片描述
可以很清楚的看到没有被synchronized修饰的方法,不受约束,当CPU分给调用此方法的时间片后,即可执行此方法。

4.线程A访问实例对象的非static synchronized方法时,线程B也可以同时访问实例对象的static synchronized方法,因为前者获取的是实例对象的监视器锁,而后者获取的是类对象的监视器锁,两者不存在互斥关系。

public class SynLock03 {
    public static void main(String[] args) {
        Phone3 p1=new Phone3();
        Phone3 p2=new Phone3();
        new Thread(()->p1.sendSms(),"A").start();

        new Thread(()->p2.call(),"B").start();
    }
}
class Phone3{
    // 静态  类加载 锁的是class 类模板
    public static synchronized  void call(){
        System.out.println("我会打电话");
    }
    public synchronized void sendSms(){
        try {
        	// 休眠,来此判断B线程状态是否为RUNNABLE
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我会发短信");
    }
}

结果展示:
在这里插入图片描述
从结果看来,已证实此说法“.线程A访问实例对象的非static synchronized方法时,线程B也可以同时访问实例对象的static synchronized方法”。

synchronized实现原理

synchronized的实现原理要从Java对象头(32为例)来讲起,我们先来看一下Java的对象头
Java的对象头有两种方式,一种是普通对象,另一种为数组对象。
Java的普通对象组成:

|--------------------------------------------------------------|
|                     Object Header (64 bits)                  |
|------------------------------------|-------------------------|
|        Mark Word (32 bits)         |    Klass Word (32 bits) |
|------------------------------------|-------------------------|

Java的数组对象组成:

|---------------------------------------------------------------------------------|
|                                 Object Header (96 bits)                         |
|--------------------------------|-----------------------|------------------------|
|        Mark Word(32bits)       |    Klass Word(32bits) |  array length(32bits)  |
|--------------------------------|-----------------------|------------------------|

Mark Word

|-------------------------------------------------------|--------------------|
|                  Mark Word (32 bits)                  |       State        |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 |       Normal       |
|-------------------------------------------------------|--------------------|
|  thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 |       Biased       |
|-------------------------------------------------------|--------------------|
|               ptr_to_lock_record:30          | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
|               ptr_to_heavyweight_monitor:30  | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
|                                              | lock:2 |    Marked for GC   |
|-------------------------------------------------------|--------------------|

Mark Work:

  • identity_hashcode:每一个对象都会有一个自身的hashcode
  • age:分代年龄(关于垃圾回收GC),4位,对象在幸存区复制1次,年龄就会+1,然后对象从新生代到老年代会存在一个关于年龄的阈值,如果达到了这个阈值,这个对象就会放到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。
  • thread:持有偏向锁的线程ID
  • lock::2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个mark word表示的含义不同。
  • biased_lock:对象是否启用了偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
biased_locklock状态
001无锁
101偏向锁
000轻量级锁
010重量级锁
011GC标记

关于这些锁升级的描述,在这里不在叙述,后续也会出一篇关于锁升级的文章。

  • 关于state状态描述
    • Normal:正常(无状态)
    • Biased:偏向锁
    • Lightweight Locked:轻量级锁
    • Heavyweight Locked:重量级锁
    • Marked for GC:GC

这是对象头(64位) ,与对象头(32位)相似。
Mark Word的位长度为JVM的一个Word大小,32位JVM的Mark Word为32位,64位JVM的Mark Word为64位。

|------------------------------------------------------------------------------|--------------------|
|                                  Mark Word (64 bits)                         |       State        |
|------------------------------------------------------------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |       Normal       |
|------------------------------------------------------------------------------|--------------------|
| thread:54 |       epoch:2        | unused:1 | age:4 | biased_lock:1 | lock:2 |       Biased       |
|------------------------------------------------------------------------------|--------------------|
|                       ptr_to_lock_record:62                         | lock:2 | Lightweight Locked |
|------------------------------------------------------------------------------|--------------------|
|                     ptr_to_heavyweight_monitor:62                   | lock:2 | Heavyweight Locked |
|------------------------------------------------------------------------------|--------------------|
|                                                                     | lock:2 |    Marked for GC   |
|------------------------------------------------------------------------------|--------------------|

关于Java对象头相关文章可以看一下这篇:https://www.jianshu.com/p/3d38cba67f8b

Monitor 监视器

Monitor被翻译为监视器或管程,如果涉及到操作系统,Monitor通常翻译为管程。每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Workd就被设置指向Monitor对象的指针

Monitor大致结构如下:

  • WaitSet:当有线程调用wait()方法时,线程将会进入到waiting中进行等待唤醒。
  • EntryList:可以看作是一个阻塞等待队列,非公平。
  • Owner:所有者,如果其中一个线程指向了Owner,那么需要等待这个线程执行完他所要做的任务。这时如果有其他线程(同一个对象)进来,那么需要阻塞等待,进入到EntryLis队列当中。
    在这里插入图片描述

下图解释: 如果有一个Thread-01的线程进来,那么对象头里面的Mark Word会指向这个监视器,当这个线程执行完所需要执行的任务后,就会唤醒阻塞队列中的某一个线程(Thread-04、Thread-05、Thread-06)。

Waiting与Blocked有什么不同之处?

  • Waiting里面的线程是已经拿到过锁的,只不过因为调用了wait()方法,释放了锁。等待其他线程来调用notify()或notifyAll()方法来进行唤醒,但是唤醒后并不意味着直接可以拿到锁,还是需要进入到EntryList阻塞等待队列中进行竞争
  • Blocked里面的线程从来都没有拿到过锁

注意:

  • 不同对象有不同的监视器
  • 下图中的sychronized必须进入到同一个对象的monitor才有上述的效果
  • 不加synchronized的对象不会关联监视器
    在这里插入图片描述

Monitor字节码分析
看如下代码:

public class ThreadTest04 {
    static final Object o=new Object();
    static int number=0;
    public static void main(String[] args) {
        synchronized (o){
            number++;
        }
    }
}

这里分析的main方法里面的字节码

0 getstatic #2    // object引用(从synchronized开始)
 3 dup   // 复制最高操作位数堆栈值 (这里就是复制一份然后存储到astore_1临时变量当中)
 4 astore_1  // lock引用给到-> slot 1
 5 monitorenter  // 这里将lock对象 MarkWord置为Monitor指针
 6 getstatic #3   // 这里对number变量进行操作 
 9 iconst_1 // 准备常数 number
10 iadd  // 进行++操作
11 putstatic #3  // 赋值给number
14 aload_1   // lock引用
15 monitorexit  // 将lock对象Mark Word重置,唤醒EntryList
16 goto 24 (+8)  // 如果没有异常,直接return结束
19 astore_2   // slot 2 异常 exception对象
20 aload_1    // lock的引用
21 monitorexi t  // 将lock对象Mark Word重置,唤醒EntryList
22 aload_2    // slot 2 (e) exception对象
23 athrow   // 抛出异常 throw e
24 return

异常表:
这里的异常会有一个范围从6到16、从19到22 ,如果出现异常就跳转到19行。
在这里插入图片描述
好啦,Java对象以及Monitor工作原理,想必大家应该有所收获。

下面我们来讲讲什么是竞态条件和内存可见性?

什么是竞态条件?

竞态条件指的是当多个线程访问和操作同一个对象时,最终结果与执行顺序有关,可能正确也可能不正确。看下面代码。

public class ThreadTest02 extends Thread {
    private static int number=0;
    @Override
    public void run() {
        for(int i=0;i<1000;i++){
            number++;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        int num=1000;
        Thread[] t=new Thread[num];
        for (int i = 0; i <num ; i++) {
            t[i]=new ThreadTest02();
            t[i].start();
        }
        for (int i=0;i<num;i++){
            t[i].join(); // 为了让main线程等待他们执行完,然后输出此结果
        }
        System.out.println(number);
    }
}

运行结果:
在这里插入图片描述
这段代码很容易理解,有一个共享静态变量number,初始值为0,在main方法中创建了1000个线程,每个线程对counter循环加1000次,main线程等待所有线程结束后输出counter的值。 期望的结果是100万,但实际执行,每次输出的结果都不一样,大多数情况下是99万吧。为什么会这样?这是因为number++这个操作不是原子操作。

  • 首先去number的当前值
  • 在当前值的基础上加1
  • 将新值重新复制给number

因为竞态条件的产生可能会出现某两个线程同时执行第一步,取到了相同的number值,比如都取到了50,第一个线程执行完后number变为51,而第二个线程执行完后还是51,最终的结果就与期望不符。此时如果要解决这个问题,有多种方案,这里就是用synchronized解决。
解决方案:

public class ThreadTest02 extends Thread {
    private static int number=0;
    @Override
    public void run() {
    // 这里使用的synchronized的代码块
        synchronized (""){
            for(int i=0;i<1000;i++){
                number++;
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        int num=1000;
        Thread[] t=new Thread[num];
        for (int i = 0; i <num ; i++) {
            t[i]=new ThreadTest02();
            t[i].start();
        }
        for (int i=0;i<num;i++){
            t[i].join(); // 加入线程,谁调用让谁加入
        }
        System.out.println(number);
    }
}

上述代码中,synchronized参数中我使用的锁是同一个对象,我没有去使用this,因为在循环当中,我是new了1000个对象,所以去调用start的方法的是不同的对象,所以在这里使用this起不到任何用处。
如果还不是很懂,那么我在举一个生活当中的案例。

卖票案例

public class TicketTest {
    public static void main(String[] args){
        Ticket t = new Ticket();
        for(int i=0;i<4;i++){  // 模拟4家卖票机构
            new Thread(()-> {
                try {
                    t.sale();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },i+"").start();
        }
    }
}
class Ticket{
    // 假如一共100张票
    private static int ticketNumber=100;
    public void sale() throws InterruptedException {
       while(true){
           if(ticketNumber>0) {
               Thread.sleep(100);  // 这里停顿,是为了模拟出票的时间
               System.out.println("线程"+Thread.currentThread().getName()+"卖出了1张票还剩"+--ticketNumber+"张");
           }else{
               break;
           }
        }
    }
}

运行结果:
在这里插入图片描述
代码里面它们有一个共享的变量ticketNumber,初始化的值为100,main方法中创建了4个线程,每个线程启动后,都会对ticketNumber不停的-1,直到为0停止。

但是当运行出来后,结果与我们期望的结果不一致。为什么呢?

因为竞态条件的产生,可能会有多个线程同时执行第一步,取到了相同的ticketNumber值,比如第一个线程取到了100减去了1,还剩99张票。第二线程还是从100的基础上减1,没有在第一个线程执行后的结果后减1。导致出现了同一张票重复销售的情况。解决这种问题,可以尝试加锁,一种方案是使用synchronized

解决方案 (synchronized代码块)

public class TicketTest {
    public static void main(String[] args){
        Ticket t = new Ticket();
        for(int i=0;i<4;i++){  // 模拟4家卖票机构
            new Thread(()-> {
                try {
                    t.sale();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },i+"").start();
        }
    }
}
class Ticket{
    // 假如一共100张票
    private static int ticketNumber=100;
    public void sale() throws InterruptedException {
       while(true){
           synchronized (this){
               if(ticketNumber==0){
                   return;  // 当其中一个线程获取锁后,先检查票数是否为0,如果为0直接return
               }
               if(ticketNumber>0) {
                   Thread.sleep(100);  // 这里停顿,是为了模拟出票的时间
                   System.out.println("线程"+Thread.currentThread().getName()+"卖出了1张票还剩"+--ticketNumber+"张");
               }else{
                   break;
               }
           }
           if(ticketNumber==0){
               System.out.println("车票已售空!!!");
           }
        }
    }
}

结果展示:
在这里插入图片描述
这里没有使用synchronized修饰sale方法,因为不适合模拟抢票案例。目的是为了让多个线程同时去卖票。如果在方法中使用synchronized,那么其中一个线程会一直占有锁,其他线程只能被阻塞。大家可自行去尝试。

什么是内存可见性?

内存可见性就是多个线程共享访问和操作相同的变量,但一个线程对一个共享变量的修改,另一个线程并不能马上被看到,甚至永远也看不到。


public class ThreadTest01 {
    private  static  boolean flag=false;
    public static void main(String[] args) throws InterruptedException {
        Thread01 t=new Thread01();
        t.start();
        Thread.sleep(1000); // 主线程休息1秒
        flag=true;
        System.out.println("主线程修改flag值,主线程结束");
    }
    static class Thread01 extends Thread{
        @Override
        public void run() {
            while(!flag){
               /* try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
                //System.out.println("1");
            }
            System.out.println("子线程结束");
        }
    }
}

当我们去运行此代码时,你会发现主线程运行完毕,并修改了flag的值,子线程并没有结束。为什么会这样呢?
当主线程开始运行的时候,flag为false,并创建了一个子线程,这个子线程会将flag复制到运行的内存中,子线程在运行时,flag一直为false。进入while后,条件一直为true。主线程休息一会后,将flag变为了true,但是影响不了子线程的运行内存中的flag值,因此flag在子线程中一直为false。所以会陷入死循环。

在计算机的系统中,除了内存。数据还会被缓存在CPU的寄存器以及各种缓存中,当访问一个变量时,可能直接从寄存器或CPU缓存中获取,而不一定到内存中去取,当修改一个变量时,也可能是先写到缓存中,稍后才会同步更新到内存中。在单线程的程序中,这一般不是问题。但是在多线程的程序中,尤其是在有很多CPU的情况下,这就是严重的问题。一个线程对内存的修改,另一个线程看不到,一是修改没有及时同步到内存,二是另一个线程根本就没从内存读。

如何解决上述问题:

  1. synchronized或显示锁同步
  2. volatile关键字

synchronized解决上述问题

public class ThreadTest01 {
    private  static  boolean flag=false;
    public static void main(String[] args) throws InterruptedException {
        Thread01 t=new Thread01();
        t.start();
        Thread.sleep(1000); // 主线程休息1秒
        flag=true;
        System.out.println("主线程修改flag值,主线程结束");
    }
    static class Thread01 extends Thread{
        @Override
        public void run() {
            while(!flag){
                synchronized (this){

                }
               /* try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
                    //System.out.println("1");
            }
            System.out.println("子线程结束");
        }
    }
}

其实还有两种方法,在while循环中除了使用了synchronized的代码块,还有一个定时休眠以及一个打印语句(这两种方法我注释了)。后两种方法也能够结束循环。
具体原因:

先看一下多线程下的内存模型

在这里插入图片描述
对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。

println()方法为什么会结束循环?

我们来看一下println()方法的源码

public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

println方法中被synchronized加锁了。他会做出以下操作:

  • 获取同步锁
  • 清空内存
  • 从主内存中拷贝新的对象副本到工作线程中
  • 继续执行代码,刷新主内存的数据
  • 释放同步锁

在清空内存刷新内存的过程中,子线程有这么一个操作:获取锁到释放锁。子线程的Flag就变成了true(从主内存拷贝对象副本到线程工作内存中),所以就跳出了循环。指令重排序的情况也就不会出现了,这也是volatile关键字的两种特性之一,所以使用volatile关键字修饰flag变量也能解决此问题。

sleep()方法为什么也能结束循环?

子线程调用sleep()时,线程虽然休眠了,但是对象的机锁没有被释放。当锁释放后,又会从从主内存拷贝对象副本到线程工作内存中。

不过,如果只是为了保证内存可见性,使用synchronize的成本有点高,有一个轻量级的方式,那就是使用volatile关键字去修饰这个flag变量。具体volatile是做什么的?这里就不解释了。因为此文章是针对于synchronized,后期我会出一篇关于volatile关键字的文章。

死锁问题

使用synchronized,要注意死锁。所谓的死锁就是类似这种线程,比如有a和b两个线程。a持有锁对象lockA,b持有锁对象lockB,b在等待锁lockA时,a线程和b线程都陷入了相互等待,最后谁都执行不下去。

这种情况,应该尽量避免在持有在持有一个锁的同时去申请另一个锁,如果确实需要多个锁,所有代码都应该按照相同的顺序去申请锁。可以约定都先申请lockA,在申请lockB。

public class ThreadTest05 {
    private static Object lockA=new Object();
    private static Object lockB=new Object();
    private static void threadA(){
        new Thread(()->{
            synchronized (lockA){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockB){

                }
            }
        }).start();
    }
    private static void threadB(){
        new Thread(()->{
            synchronized (lockB){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockA){

                }
            }

        }).start();
    }
    public static void main(String[] args) {
        threadA();
        threadB();
    }
}

解决:

  • 应该尽量避免在持有一个锁的同时去申请另一个锁,如果确实需要多个锁,所有代码都应该按照相同的顺序去申请锁。
  • 使用显式锁接口Lock,它支持尝试获取锁(tryLock)和带时间限制的获取锁方法,使用这些方法可以在获取不到锁的时候释放已经持有的锁,然后再次尝试获取锁或干脆放弃,以避免死锁
// 可以都先约定好,都先申请了lockA,在去申请lockB
public class ThreadTest05 {
    private static Object lockA=new Object();
    private static Object lockB=new Object();
    private static void threadA(){
        new Thread(()->{
            synchronized (lockA){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockB){

                }
            }
        }).start();
    }
    private static void threadB(){
        new Thread(()->{
            synchronized (lockA){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockB){

                }
            }

        }).start();
    }
    public static void main(String[] args) {
        threadA();
        threadB();
    }
}

总结:

  • synchronized可以保证原子性操作
  • synchronized可以保证内存可见性,在释放锁时,所有写入都会写回内存,而获得锁后,都会从内存中读取最新的数据。如果只是简单操作变量的话,可以用volatile修饰该变量,替代synchronized来减少成本。
  • 使用synchronized要注意死锁问题
  • 可重入性:每个锁关联一个线程持有者和一个计数器。当计数器为0时表示该锁没有被任何线程持有,那么任何线程都都可能获得该锁而调用相应方法。当一个线程请求成功后,JVM会记下持有锁的线程,并将计数器计为1。此时其他线程请求该锁,则必须等待。而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增。当线程退出一个synchronized方法/块时,计数器会递减,如果计数器为0则释放该锁

一位还未大学毕业的老程序员,知识有限,有不对的地方,希望大家告知于我。我们一起进步,加油!!!

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值