2.Lock接口

本文探讨了Java中Synchronized关键字的使用及其局限性,随后介绍了Lock接口带来的灵活性。对比了Synchronized的自动释放与Lock的可手动管理,并讲解了可重入锁、公平锁、共享锁等概念。特别关注了读写锁和自旋锁在并发控制中的应用。
摘要由CSDN通过智能技术生成

2、Lock接口

2.1 Synchronized概述

Synchronized是java中一个重要的关键字。它的修饰对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块成称为同步语句块,其作用范围是大括号{}括起来的代码作用的对象是调用这个代码块的对象;

  2. 修饰一个方法,被修饰的方法称为同步方法,其作用范围是整个方法,作用对象是调用这个方法的对象

    虽然可以用 Synchronized来定义方法,但是 Synchronized并不属于方法的一部分,因此 Synchronized关键字不能被继承,如果在父类中使用了 Synchronized,而在子类中覆盖了这个方法,在子类中默认并不是同步的,所以必须显示的在子类方法中加上 Synchronized关键字

2.2 Lock接口,比Synchronized更强大的锁

Lock是一个接口,通过它的实现类可以实现同步访问

  • synchronized的缺陷

    我们了解到如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:

    1. 获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
    2. 线程执行发生异常,此时JVM(java虚拟机)会让线程自动释放锁。

    如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,那么其他线程也就只能一直无期限地等待下去,这多么影响程序的执行效率.

    因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。

    从java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock。

  • Lock提供了比synchronized更多的功能。但是要注意以下几点:

    1. Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个接口,通过它的实现类可以实现同步访问;
    2. Lock和synchronized有一点非常大的不同,synchronized采用不需要用户手动的释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
    3. synchronized是可重入锁,不可以中断,属于非公平锁,lock锁是可重入锁,可以判断锁,可以设置公平或者非公平锁。
    4. synchronized适合少量代码同步问题,lock适合大量同步代码。

    可重入锁

    可重入性:就是一个线程不用释放,可以重复获取一个锁n次,只是在释放的时候,也需要相应的释放n次。(简单来说:A线程在某上下文中获得了某锁,当A线程想要再次获取该锁时,不会因为锁已经被自己占用,而先等到锁的释放)假使A线程即获得了锁,又在等待锁的释放,就会造成死锁。

    package com.test.reen;
    
    // 演示可重入锁是什么意思,可重入,就是可以重复获取相同的锁,synchronized和ReentrantLock都是可重入的
    // 可重入降低了编程复杂性
    public class WhatReentrant {
    	public static void main(String[] args) {
    		new Thread(new Runnable() {
    			@Override
    			public void run() {
    				synchronized (this) {
    					System.out.println("第1次获取锁,这个锁是:" + this);
    					int index = 1;
    					while (true) {
    						synchronized (this) {
    							System.out.println("第" + (++index) + "次获取锁,这个锁是:" + this);
    						}
    						if (index == 10) {
    							break;
    						}
    					}
    				}
    			}
    		}).start();
    	}
    }
    
    
  • 公平锁、非公平锁

    公平锁:使用FIFO(先进先出原则),谁先来就在队列的最前面,就能优先获得锁,公平锁是有顺序的

    非公平锁:支持抢占模式,先来的不一定能得到锁,是无序的,Synchronized和ReentrantLock锁都是非公平锁

  • 独占锁和共享锁

    独占锁:独占锁也叫排他锁,是指该锁一次 只能被一个线程所持有。在写操作时一般会使用独占锁。

    (Synchronized和ReentrantLock、ReentrantReadWriteLock.WriteLock都属于独占锁)

    共享锁:是指该锁可以被多个线程所持有。读操作时一般使用共享锁(ReentrantReadWriteLock.ReadLock属于共享锁)

  • 乐观锁和悲观锁

    1. 乐观锁:基于乐观思想,认为冲突不总会发生,遇到并发的可能性低,所以不会上锁,只是在数据提交的时候才通过一种机制(CAS操作)来验证数据是否存在冲突。乐观锁通常是通过在表中增加一个版本(version)或时间戳(timestamp)来实现,其中,版本最为常用。乐观锁每次在执行数据的修改操作时,都会带上 一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作**并对版本号执行 +1 **操作,否则就执行失败。
    2. 悲观锁:基于悲观思想,认为冲突总会发生,遇到并发的可能性高。每次拿数据的时候搜认为其他用户会修改数据,所以每次在读写操作时都会上锁,这样别人在读写这个数据时就会拿到block锁。(Synchronized和 ReentrantLock锁都属 于悲观锁)
  • 读写锁

    读写锁分为两部分:读锁和写锁

    • 读锁允许多个线程同时获得,因为读操作本身是线程安全的,它是一种共享锁
    • 写锁是互斥锁,不允许读写个线程同时获得,同时写操作和读操作也是互斥的,它是一种独占锁
    • 读写锁的特点:读读不互斥,读写互斥,写写互斥。
  • 自旋锁

    • ­ 使用重量级锁,线程从运行状态进入阻塞状态的过程非常耗资源,不仅要保存线程的当前状态、切换上下文还 涉及到用 户态到内核态的转换。同样当从阻塞状态唤醒时也比较耗时。
    • ­ 自旋锁是一种轻量级锁,当一个线程没有获得资源时不会直接让该线程进入阻塞状态,而是通过自旋(有界的自循环),让线程继续执行,而不进入阻塞状态以减少资源的消耗。当自旋一定次数后,依然没有获得资源,在让该线程进入阻塞状态。自旋锁属于乐观锁,在ava.util.concurrent.atomic中的原子类就使用自旋锁实现 相关操作。
  • 偏向锁

    • 偏向锁认为一个方法或一个代码块在大多数情况下只有一个线程访问,很少会出现两个或多个线程访问,不需要不断的加锁和解锁。当出现竞争时再将偏向锁升级为轻量级锁

    • 偏向锁是一种多线程的访问机制,在JDK1.6以后,Synchronized和Lock锁的底层实现中使用偏向锁进行优化

    • 偏向锁的执行过程:当一个线程访问一个方法时或代码块时先用CAS检测当前方法是否被其他线程正在使用,如果没有使用就将自己线程的线程号写入到锁对象的对象头中,并访问这个方法,当方法执行结束线程退出该方法后,锁对象的中存储的线程号不会删除。这个对象下次再访问该方式时,会先检测锁对象的对象头中存储的线程号,如果是自己的线程号就认为该方法没有被其他线程访问,自己可以直接进入方法执行,无需检测和加锁。如果检测到锁对象的对象头中存储的线程号不是自己的就认为有线程访问过该方法,此时偏向锁失效,升级为轻量级锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值