程序员_简单帮你理解Synchronized关键字,类锁,方法锁,重入锁

放大字体  缩小字体 发布日期:2019-11-27  来源:来自互联网  作者:来自互联网  浏览次数:642
导读

在修饰类的时候,默认是当前类的class对象作为锁的对象。当一个线程访问一个带synchronized方法时,由于对象锁的存在,所有加synchronized的方法都不能被访问(前提是在多个线程调用的是…

synchronized关键字

  1. synchronized,这个东西我们一般称之为同步锁,是一个重量级锁,在修饰代码块的时候需要传入一个引用对象作为锁的对象。
  2. Synchronized是非公平锁。 Synchronized在线程进入ContentionList时,等待的线程会先尝试自旋获取锁,如果获取不到就进入ContentionList,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占OnDeck线程的锁资源。
  3. 如何获取锁:当前已经获取到锁资源的线程(Owner)并不直接把锁传递给正在竞争锁资源的线程(OnDeck)线程,而是把锁竞争的权利交给OnDeck,OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的提升系统的吞吐量,在JVM中,也把这种选择行为称之为“竞争切换”。
  4. 我们这里提到的锁,是把所需要的代码块,资源,或数据锁上,在操作他们的时候只允许一个线程去做操作。最终结果是为了保证cpu计算结果的正确性。
  5. synchronized可以把任意一个非NULL的对象当作锁。
  • 作用于方法时,锁住的是对象的实例(this);
  • 作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带PermGen(jdk1.8则是metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程;
  • synchronized作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。

类锁,方法锁,对象锁

在修饰方法的时候默认是当前对象作为锁的对象。

在修饰类的时候,默认是当前类的class对象作为锁的对象。

注意:在修饰代码块时,需要一个reference对象作为锁的对象。

所以存在类锁,方法锁,对象锁这样的概念。

类锁,方法锁,对象锁

方法锁

通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。

synchronized 方法控制对类成员变量的访问:

每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态,从而有效避免了类成员变量的访问冲突。

对象锁

当一个对象中有同步方法或者同步块,线程调用此对象进入该同步区域时,必须获得对象锁。如果此对象的对象锁被其他调用者占用,则进入阻塞队列,等待此锁被释放(同步块正常返回或者抛异常终止,由JVM自动释放对象锁)。

注意,方法锁也是一种对象锁。当一个线程访问一个带synchronized方法时,由于对象锁的存在,所有加synchronized的方法都不能被访问(前提是在多个线程调用的是同一个对象实例中的方法)。

对象锁有两种形式:

第一种

public class object {

public synchronized void method{

System.out.println("我是对象锁也是方法锁");

}

}

第二种

public class object {

public void method{

synchronized(this){

System.out.println("我是对象锁");

}

}

}

类锁

一个class其中的静态方法和静态变量在内存中只会加载和初始化一份,所以,一旦一个静态的方法被申明为synchronized,此类的所有的实例化对象在调用该方法时,共用同一把锁,称之为类锁。

也是两种形式:

第一种

public class object {

public static synchronized void method{

System.out.println("我是第一种类锁");

}

}

第二种

public class object {

public void method{

synchronized (object.this) {

System.out.println("我是第二种类锁");

}

}

}

重入锁

重入锁包括可重入锁和不可重入锁。

不可重入锁:只判断这个锁有没有被锁上,只要被锁上申请锁的线程都会被要求等待。实现简单。

当A方法获取lock锁去锁住一段需要做原子性操作的B方法时,如果这段B方法又需要锁去做原子性操作,那么A方法就必定要与B方法出现死锁。这种会出现问题的重入一把锁的情况,叫不可重入锁。

  • A方法需要等B方法执行完才能解锁,但是B方法想执行完代码又必须要lock锁来加锁。A的锁未解锁前,其他代码块无法使用此锁来加锁。这是由这个不可重入锁决定的。
  • 可重入锁:不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当就是自己锁上的时候,那么他依旧可以再次访问临界资源,并把加锁次数加一。设计了加锁次数,以在解锁的时候,可以确保所有加锁的过程都解锁了,其他线程才能访问。不然没有加锁的参考值,也就不知道什么时候解锁?解锁多少次?才能保证本线程已经访问完临界资源了可以唤醒其他线程访问了。实现相对复杂。
  • 总结:这个重入的概念就是,拿到锁的代码能不能多次以不同的方式访问临界资源而不出现死锁等相关问题。经典之处在于判断了需要使用锁的线程是否为加锁的线程。如果是,则拥有重入的能力。

Synchronized关键字底层原理属于JVM层面。

修饰同步语句块

synchronized 同步语句块的实现,使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时,线程试图获取锁,也就是获取 monitor ( monitor 对象存在于每个 Java 对象的对象头中,synchronized 锁便是通过这种方式获取锁的,这也是为什么 Java 中任意对象都可以作为锁的原因) 的持有权。当计数器为0,则可以成功获取,获取后将锁计数器设为1,也就是加1;相应的,在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。

修饰方法

synchronized 修饰方法,并没有 monitorenter 指令和 monitorexit 指令,取得代之的是 ACC_SYNCHRonIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRonIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

 
 
免责声明
• 
本文为会员免费发布,仅代表发布者个人观点,本站未对其内容进行核实,请读者仅做参考,如若文中涉及有违公德、触犯法律的内容,一经发现,立即删除,作者需自行承担相应责任。涉及到版权或其他问题,请及时联系我们删除处理。