synchronized底层的锁


前言

          在计算机科学中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。锁旨在强制实施互斥排他、并发控制策略。在JDK1.5之前都是使用synchronized关键字保证同步的。它可以把任意一个非NULL的对象当作锁。


一、synchronized是什么?

         synchronized是Java中的关键字,是一种同步锁。synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。它包括两种用法:synchronized 方法和 synchronized 块。同时它的底层又可以分为四种状态(偏向锁 无锁  轻量级锁   重量级锁)。

锁状态                    是否是偏向锁                锁标志

 无锁不可偏向                                                01            

 无锁可偏向                        1                         01
 轻量级锁                                                       00
 重量级锁                                                       10

注意事项:
             修改偏向锁的延迟时间的参数:-XX:BiasedLockingStartupDelay=0
             关闭指针压缩: -XX:-UseCompressedOops    

1.无锁状态

//在测试之前,先引入包jol--core-0.9.jar,通过ClassLayout.parseInstance(obj)查看对象所处的锁的状态

import org.openjdk.jol.info.ClassLayout

        没有开启偏向锁的情况下:一个对象没有被作为锁对象,处于无锁状态

public class Test02 {
	public static void main(String[] args) throws InterruptedException {
		A a = new A();
		String str = ClassLayout.parseInstance(a).toPrintable();
		System.out.println(str);//01	无锁状态
		
		
	}
}

           运行结果示意:

        现在a对象处于无锁不可偏向状态

com.apesource.demo04.synchronized锁的描述.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c0 00 20 (01000011 11000000 00000000 00100000) (536920131)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

2.轻量级锁

                一个对象被一个线程获取作为锁对象,处于轻量级锁状态

代码测试:

public class Test02 {
	public static void main(String[] args) throws InterruptedException {
		A a = new A();
		String str = ClassLayout.parseInstance(a).toPrintable();
		new Thread(){
			public void run() {
				//把a作为锁对象
				synchronized(a) {
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}.start();
		Thread.sleep(10);
		str = ClassLayout.parseInstance(a).toPrintable();
		System.out.println(str);//00	轻量级锁
	}
}

运行结果示意


         此时对象a被匿名对象获取成为锁对象,处于轻量级锁,没有偏向,后两个字节表示对象头中锁添加的id

3.重量级锁状态

        在 上面代码的基础上新增此代码,加入访问锁,此时a对象被第一个线程持有,所以线二应该处于重量级锁状态

	new Thread(){
			public void run() {
				//把a作为锁对象
				synchronized(a) {
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}.start();
		Thread.sleep(10);
		//这个时候第一个线程正在使用a这个锁,第二个线程也要去获取a这个锁
		str = ClassLayout.parseInstance(a).toPrintable();
		System.out.println(str);//10	重量级锁

运行代码展示:

 偏向锁

   :
          大部分情况下,锁不仅仅不存在多线程竞争,而是总是由同一个线程多次获得,
         为了让线程获取锁的代价更低就引入了偏向锁的概念。
          怎么理解偏向锁呢?当一个线程访问加了同步锁的代码块时,
          会在对象头中存储当前线程的 ID,
         后续这个线程进入和退出这段加了同步锁的代码块时,
         不需要再次加锁和释放锁。
         而是直接比较对象头里面是否存储了指向当前线程的偏向锁。
         如果相等表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了。

无锁可偏向

public static void main(String[] args) throws InterruptedException {
		A a = new A();
		String str = ClassLayout.parseInstance(a).toPrintable();
		System.out.println(str);//101  无锁可偏向
		
		new Thread() {
			public void run() {
				synchronized(a) {
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
				/*
				 * 	显示的让当前线程不结束
				 * 	如果结束了,下一个线程的id和刚才这个线程的id是一样的。
				 */
				try {
					Thread.sleep(100000000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}.start();
		Thread.sleep(100);
		str = ClassLayout.parseInstance(a).toPrintable();
		System.out.println(str);//101  无锁可偏向
		
		Thread.sleep(1000);//保证上一个线程已经把任务执行完毕了
		new Thread() {
			public void run() {
				synchronized(a) {
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}.start();
		Thread.sleep(100);
		str = ClassLayout.parseInstance(a).toPrintable();
		System.out.println(str);//
	}


                     显示的让当前线程不结束
                     如果结束了,下一个线程的id和刚才这个线程的id是一样的。
                    Thread.sleep(100000000);

会导致下一个线程到来时依旧是无锁可偏向状态,可能第二个线程id和上面第一个线程的id相同

 黄色框内是显示对象头中存储当前线程的 ID,

只有当上一个锁没有执行完毕(已经出synchronized代码块)下一个锁到来时才会升级为轻量级线程


总结

        Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。锁的状态总共有四种,级别从低到高依次是:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以升级但不能降级。 偏向锁是Java 6之后加入的新锁,它是一种针对加锁操作的优化手段,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的性能。现在经过不断的优化synchronized可能会进行更加高效的优化,对synchronized的学习也是不能忽视的。

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...