java中对 “锁”的一些认识

本文的一些认识皆以Jdk 8为基础。对一些锁的知识点,以及不同的适用场景。结合自己和他人的文章对它进行重新的认知与疏导。非原创 ,引用自:https://tech.meituan.com/Java_Lock.html?utm_source=tuicool&utm_medium=referral 

 

下图:通过锁的特性来进行分类

 可总结出,6大特性。a.根据对资源的锁住情况分 乐观锁与悲观锁,b.根据不阻塞情况分 自旋锁和适应性自旋锁 ,c. 根据锁的程度由轻到重分 无锁,偏向锁,轻量级锁,重量级锁。d. 根据竞争时排队情况分,公平锁,非公平锁。e.根据同一线程可重复获取锁的情况分,可重入锁,非可重入锁。f.不同线程共享一把锁的情况分,共享锁,排他锁。

1.乐观锁与悲观锁

含义就不多说了,这里主要说 两者的代表区别,

悲观锁,我们接触最多的synchronized关键字和 Lock的实现类都是典型代表。悲观锁认为,我在使用数据的时候一定有别的线程对它进行修改,因此对它修改的操作进行加锁的。

// synchronized
public synchronized void testMethod() {
    // 操作同步资源
}
// ReentrantLock
private ReentrantLock lock = new ReentrantLock(); // 需要保证多个线程使用的是同一个锁
public void modifyPublicResources() {

  try{
     lock.lock();
     // 操作同步资源

    }finally{
     lock.unlock();
     }
}

乐观锁,在Java中通过无锁编程来实现,比如 CAS算法,Concurrent包下的原子类,就是通过CAS自旋实现的,它在更新内存的同步资源之前先判断资源是否被其他线程修改,Unsafe类的compareAndSwap*()方法。native方法。

public final int getAndAdd(int delta) {  
  return unsafe.getAndAddInt(this, valueOffset, delta);
}
 
//unsafe.getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4) {
  int var5;
  do {
  /**获取原始值*/
    var5 = this.getIntVolatile(var1, var2);
  /**确认原始值没有被其它线程修改时,再执行更新var5+var4操作*/
  } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
  return var5;
}

CAS虽然很高效,但也存在三大问题。1.ABA 问题,解决思路:在变量前面添加版本号,jdk提供了AtomicStampedReference类来解决。

2.CAS如果长时间不成功,会导致其一直自旋,循环时间长开销大

这里就不深入的阐述 CAS的利弊性了。

3.只能保证一个共享变量的原子操作。AtomicReference类可以保证引用对象之间的原子性,可以把多个变量放在一个对象来进行CAS操作。

适用场景:

悲观锁适合 写操作多的场景,先加锁可以保证写操作时取的数据正确。

乐观锁适合 读操作多的场景,不加锁的特点使其读操作的性能提升。

2.自旋锁和适应性自旋锁

阻塞或唤醒一个Java线程需要切换CPU状态来完成,这种切换带来的消耗相对于同步代码块中的简单的内容,用户代码执行的时间来说的话 还要长,则我们可采用自旋的方式来避免阻塞。如果自旋超过了限度次数(默认10次,可以使用-XX:PreBlockSpin来更改)

自旋锁在JDK1.4.2中就引入,使用-XX:+UseSpinning来开启,JDK 6中默认开启,并且也引入了适应性自旋锁。自适应意味着自旋的次数不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。交给虚拟机来判断了。

3.无锁,偏向锁,轻量级锁,重量级锁

这四种锁是指锁的状态,专门针对synchronized的。

先了解什么是 Monitor:

Monitor可以理解为一个同步工具或一种同步机制,通常被描述为一个对象。每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁。

Monitor是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联,同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。

synchronized通过Monitor来实现线程同步,Monitor是依赖于底层的操作系统的 互斥锁 来实现线程同步的。

在JDK 6 之前,synchronized依赖的这种互斥的实现方法称之为 “重量级锁”,之后为了减少获得锁和锁释放带来额性能消耗,引入了”偏向锁“和”轻量级锁“。

所有目前锁一共有4种状态,锁状态只能升级不能降级。

无锁:

无锁没有对竞争资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。上面我们介绍的CAS原理及应用就是无锁的实现。

偏向锁:

偏向锁是 指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。引用偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级 锁执行路径。

偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。偏向锁的撤销,需要等待全局安全点(safePoint),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态。撤销偏向锁后恢复到无锁(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

偏向锁在JDK 6及以后的JVM里是默认启用的。可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态。

轻量级锁:

当锁是偏向锁时,被另一个线程访问,就会升级成轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。若当前只有一个等待线程,则该线程通过自旋进行等待,但是当自旋超过一定的次数,或者一个线程在持有锁,又有第三个来访时,轻量级锁升级为重量级锁。

重量级锁:

将除了拥有锁的线程以外的线程都阻塞,线程的状态改变了。

4.公平锁和非公平锁

这里看到不错的图很好表述了含义。

 

公平锁是指多个线程按照申请锁的顺序来获取锁。

非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。

这里我们通过ReentrantLock的源码来

ReentrantLock 默认为公平锁,可通过构造器来显示指定使用哪种锁。

下面我们来看看这两种获取锁的不同的地方:

 区别在于 hasQueuedPredecessors() 判断。

 public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

判断当前线程是否在同步队列中的第一个。

5.可重入锁和非可重入锁

可重入锁,是指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象的是同一个对象或者class)。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

非可重入锁,当前线程是持有锁的线程的时候,再进入该线程的内层方法去获取锁则无法获取锁,阻塞该线程。

看代码可知:

 6.独享锁和共享锁

独享锁也叫排他锁。是指该线程一次只能被一个线程所拥有。synchronized 和JUC的Lock的实现类就是互斥锁。

共享锁是指该锁可被多个线程所持有,如果线程A对数据a加上共享锁后,则其他线程只能对a再加共享锁,不能加排他锁,获得共享锁的线程只能读数据,不能修改数据。

我们看个 运用 volatile 和 synchronized 实现的 “开销较低的 读-写锁”

@ThreadSafe
public class CheesyCounter {

    private volatile int value;

    public int getValue() { return value; }

    public synchronized int increment() {
        return value++;
    }
}

写操作 只能有一个线程获取到锁,对value进行修改,读操作可以有多个线程获取数据value。而这里的volatile 确保了当前值的可见性。对比下 ReentrantReadWriteLock 的读-写操作。

我们发现 读锁和写锁的锁主体都是Sync,但读锁和写锁的加锁方式不一样,读锁是共享锁,写锁是独享锁。读锁的共享锁可以保证并发读非常高效,而读写,写读,写写的过程互斥,因为读锁和写锁是分离的,所以 ReentrantReadWriteLock 的并发性相比一般的互斥锁有了很大的提升。

重点来看 读操作如何获取锁

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;                                   // 如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

可以看到tryAcquireShared()方法中,如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态,如果当前线程获取了写锁或者写锁未被获取,则当前线程增加读状态,成功获取读锁,读锁的每次释放均减少读的状态,以达到读读的过程共享,而其他操作互斥。

结语:

后续会增加一些项目实例来结合来说说好点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
图像识别技术在病虫害检测的应用是一个快速发展的领域,它结合了计算机视觉和机器学习算法来自动识别和分类植物上的病虫害。以下是这一技术的一些关键步骤和组成部分: 1. **数据收集**:首先需要收集大量的植物图像数据,这些数据包括健康植物的图像以及受不同病虫害影响的植物图像。 2. **图像预处理**:对收集到的图像进行处理,以提高后续分析的准确性。这可能包括调整亮度、对比度、去噪、裁剪、缩放等。 3. **特征提取**:从图像提取有助于识别病虫害的特征。这些特征可能包括颜色、纹理、形状、边缘等。 4. **模型训练**:使用机器学习算法(如支持向量机、随机森林、卷积神经网络等)来训练模型。训练过程,算法会学习如何根据提取的特征来识别不同的病虫害。 5. **模型验证和测试**:在独立的测试集上验证模型的性能,以确保其准确性和泛化能力。 6. **部署和应用**:将训练好的模型部署到实际的病虫害检测系统,可以是移动应用、网页服务或集成到智能农业设备。 7. **实时监测**:在实际应用,系统可以实时接收植物图像,并快速给出病虫害的检测结果。 8. **持续学习**:随着时间的推移,系统可以不断学习新的病虫害样本,以提高其识别能力。 9. **用户界面**:为了方便用户使用,通常会有一个用户友好的界面,显示检测结果,并提供进一步的指导或建议。 这项技术的优势在于它可以快速、准确地识别出病虫害,甚至在早期阶段就能发现问题,从而及时采取措施。此外,它还可以减少对化学农药的依赖,支持可持续农业发展。随着技术的不断进步,图像识别在病虫害检测的应用将越来越广泛。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值