详解synchronized关键字及锁的基本概念

在我们的Java体系中共有

  1. 乐观锁和悲观锁
  2. 独占锁和共享锁
  3. 互斥锁和读写锁
  4. 公平锁和非公平锁
  5. 可重入锁(ReentrantLock)
  6. 自旋锁(spinlock)
  7. 分段锁(segment)
  8. 锁升级(无锁|偏向锁|轻量级锁|重量级锁)
  9. 锁优化技术(锁粗化、锁消除)

 27a0304811d84dcdbbcb4bfd1e8e292c.jpeg

 

而我们今天要讲的synchronized同步锁。就是一种典型的悲观锁。

一,悲观锁

          下面用图解的方式看一下:

           1d866c8fc25243fd90dbc155e70a0cc1.png

 

    在Java语言中,synchronized就是典型的悲观锁。

二,synchronized同步锁

           Synchronized同步锁,简单来说,使用Synchronized关键字将一段代码逻辑,用一把锁给锁起来,只有获得了这把锁的线程才访问。并且同一时刻, 只有一个线程能持有这把锁, 这样就保证了同一时刻只有一个线程能执行被锁住的代码,从而确保代码的线程安全。

1. 什么是锁

      锁就是并发机制中的一种概念,在多个线程需要访问同一个共享数据时,为了避免产生各类问题,出现了锁的概念,锁加在共享数据上时,就能够防止多个线程共同访问数据。Java中提供了各类的锁,应用在不同的场景下,可以让高并发程序的效率和稳定性得到显著提升。

2. synchronized的关键字用法

  • 修饰实例方法:synchronized修饰实例方法, 则用到的锁,默认为this当前方法调用对象;
  • 修饰静态方法:synchronized修饰静态方法, 则其所用的锁,默认为Class对象;
  • 修饰代码块:synchronized修饰代码块, 则其所用的锁,是某个指定Java对象;

3.实例

下面我们来具体看一组程序:

(1)修饰实例方法 

public class Demo01 {
	public static void main(String[] args) {
	    // 实例化一个对象
	    DO fa = new DO();
		
	    // 创建不同的线程1
	    Thread thread01 = new Thread() {
	        public void run() {
	            // 使用相同的对象访问synchronized方法
	            fa.doSth1();
	        }
	    };
		
	    // 创建不同的线程2
	    Thread thread02 = new Thread() {
	        public void run() {
	            // 使用相同的对象访问synchronized方法
	            fa.doSth1();
	        }
	    };
	    
	    // 启动线程
	    thread01.start();
	    thread02.start();
	}
}

class DO {
    // 实例方法
    public synchronized void doSth1() {
        // 获取this锁,才能执行该方法
    }
    
    // 实例方法
    public void doSth2() {
        synchronized(this) {
           // 获取this锁,才能执行该代码块
        }
    }

(2) 修饰静态方法


public class Demo01 {
	public static void main(String[] args) {
	    // 创建不同的对象(相同类型)
		Do a = new Do();
		Do b = new Do();

	    // 创建不同线程1
	    Thread thread01 = new Thread() {
	        public void run() {
	            // 使用不同的对象访问synchronized方法
	            a.doSth2();
	        }
	    };

	    // 创建不同线程2
	    Thread thread02 = new Thread() {
	        public void run() {
	            // 使用不同的对象访问synchronized方法
	            b.doSth2();
	        }
	    };

	    // 启动线程
	    thread01.start();
	    thread02.start();
	}
}

class Do {
    // 实例方法
    public synchronized void doSth1() {
        // 获取this锁,才能执行该方法
    }
    
    // 实例方法
    public void doSth2() {
        synchronized(this) {
           // 获取this锁,才能执行该代码块
        }
    }
}

(3)修饰代码块

synchronized(自定义对象) {
          //临界区
}

三, synchronized关键字补充

          

  • 当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。

          在没有加锁的情况下, 所有的线程都可以自由地访问对象中的代码, 而synchronized关键字只            是限制了线程对于已经加锁的同步代码块的访问,并不会对其他代码做限制。所以,同步代             码块应该越短小越好。

  • 父类中synchronized修饰的方法,如果子类没有重写,则该方法仍然是线程安全性;如果子类重写,并且没有使用synchronized修饰,则该方法不是线程安全的;
  • 在定义接口方法时,不能使用synchronized关键字;
  • 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步;
  • 离开同步代码块后,所获得的锁会被自动释放;

四,synchronized底层实现原理

        通 过下面的代码案例,观察一下synchronized的用法以及底层实现。

       案例:通过两个线程对变量sharedState进行10w次操作,观察每次操作后formerlatter之                      间的关系。


public class ThreadSafeSample {
	public int sharedState;
    
	public synchronized void nonSafeAction() {
    	while (sharedState < 100000) {
            
        	int former = sharedState++;
        	int latter = sharedState;
            
        	if (former != latter - 1) {
            	System.out.printf("数据观察结果: former is %d,latter is %d",					former,latter);
        	}
    	}
	}

	public static void main(String[] args) throws InterruptedException {
    	ThreadSafeSample sample = new ThreadSafeSample();
    	Thread threadA = new Thread(){
        	public void run(){
            	sample.nonSafeAction();
        	}
    	};
    	Thread threadB = new Thread(){
        	public void run(){
            	sample.nonSafeAction();
        	}
 	   };
        
    	threadA.start();
    	threadB.start();
        
    	threadA.join();
    	threadB.join();

        System.out.println(sample.sharedState);
	}
}

 然后我们利用Java反编译,可以看到:

11: astore_1
12: monitorenter
13: aload_0
14: dup
15: getfield      #2                  // Field sharedState:I
18: dup_x1

56: monitorexit

 利用monitorenenter/monitorexit实现了同步的语义。

          所以,synchronized代码块是由一对monitorenter/monitorexit指令实现,synchronized是通过对象内部的叫做监视器(monitor)来实现的,线程通过执行monitorenter指令尝试获取monitor的所有权,当monitor被占用时就会处于锁定状态。

监视器(monitor):

          在JVM中实现的规范中监视器的描述为:每个对象有一个监视器(monitor),线程通过执行monitorenter指令尝试获取monitor的所有权,当monitor被占用时就会处于锁定状态。

  获取monitor所有权的过程如下

  • 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者,代表持有锁;
  • 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;
  • 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权

295d1ad7e0e7408a9cfb18db72e3ac79.png

 

 

 

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

#0000FF格子衫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值