多线程编程中的一些“锁”事

简介: 在Java多线程中独占锁的实现可以使用synchronized关键字来实现线程之间的同步互斥,但在JDK1.5中新增加ReentranLock(可重入锁)类也可以达到同样的效果,并且在扩展功能上更加强大,比如具有嗅探锁定、多路分支通知等,而且在使用上也比synchronized更加灵活,也更适合复杂的并发场景。

1 前言


在Java多线程中独占锁的实现可以使用synchronized关键字来实现线程之间的同步互斥,但在JDK1.5中新增加ReentranLock(可重入锁)类也可以达到同样的效果,并且在扩展功能上更加强大,比如具有嗅探锁定、多路分支通知等,而且在使用上也比synchronized更加灵活,也更适合复杂的并发场景。


2 正文


ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。


ReentranLock的基本语法如下:


private Lock lock = new ReentrantLock();
lock.lock();//用来获取锁。
lock.unlock();//用来释放锁
复制代码


比如:


public class Demo12 {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Thread t1 = new Demo01Thread(lock);
        Thread t2 = new Demo01Thread(lock);
        Thread t3 = new Demo01Thread(lock);
        t1.start();
        t2.start();
        t3.start();
    }
}
class Demo01Thread extends Thread{
    private Lock lock;
    public Demo01Thread(Lock lock){
        this.lock = lock;
    }
    @Override
    public void run() {
        // 加上同步锁
        lock.lock();
        for (int i=0; i< 5; i++){
            System.out.println(Thread.currentThread().getName() + ", " + (i + 1));
        }
        // 解开同步锁
        lock.unlock();
    }
}
复制代码


结果如下:


b6d5a03934db481987d3326d31a852e3~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


从上面的运行结果来看,当前线程打印完毕之后将锁进行释放,其它线程才可以继续 打印。线程打印的数组是分级打印,因为当前线程已经持有锁,但线程之间打印的顺序是随机的。


调用lock方法的线程就会持有对象锁,其它的线程只能等待锁被释放(调用unlock方法)才可以再次争抢锁。效果和使用synchronized关键字一样,线程之间还是按照顺序执行。


Lock分为公平锁与非公平锁两种。公平锁表示线程获取锁的顺序是按照加锁的顺序来分配的,也就是先来先得。而非公平锁就是一种获取锁的抢占机制 ,是随机获得锁,与公平锁不一样的是先来的不一定先得到锁,这种方式可能会造成某些线程一直都拿不到锁,结果也就是不公平的。


进入ReentranLock的源码:


dfdf2dd2778d4b898e2623b837d8bd18~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


通过ReentranLock的源码可以发现无参的ReentrantLock是非公平锁,而通过boolean参数可以控制锁的类型,如果是true会使用公平锁,否则就是非公平锁。


package com.jiangxia.chap3;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 公平锁和非公平锁
 */
public class Demo13 {
    public static void main(String[] args) {
        //公平锁
        Demo13Service demo13Service = new Demo13Service(true);
        //非公平锁
        //Demo13Service demo13Service = new Demo13Service(false);
        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Demo13Thread(demo13Service);
        }
        for (int i = 0; i <threads.length ; i++) {
            threads[i].start();
        }
    }
}
class Demo13Service{
    private Lock lock;
    /**
     * 通过isFair参数控制锁的类型,true就是公平锁,false为非公平锁
     * @param isFair
     */
    public Demo13Service(boolean isFair) {
        this.lock = new ReentrantLock(isFair);
    }
    public void hello(){
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"获得锁。。。");
        lock.unlock();
    }
}
class Demo13Thread extends Thread{
    private Demo13Service demo13Service;
    public Demo13Thread(Demo13Service demo13Service) {
        this.demo13Service = demo13Service;
    }
    @Override
    public void run() {
        demo13Service.hello();
    }
}
复制代码


公平锁输出结果如下:


db044f883897456da4e3262587775f5c~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


非公平锁输出结果如下:


5d04c063b0824d6e83ab98344dad5177~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


在java 1.5中才出现的Condition,它可以用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。


Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,也就是在一个Lock对象里面可以创建多个Condition实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调试线程上更加灵活。


在使用notify/notifyAll方法进行通知时,被通知线程是由JVM随机选择的,但使用ReentrantLock结合Condition类可以实现选择性通知,这个功能是非常重要的,而且在Condition在中是默认提供的。


而synchronized就相当于整个Lock对象中只有一个单一Condition对象,所有的线程都是注册在它一个对象的身上。线程开始notifyAll时需要通知所有正在等待的线程,没有选择权,会出现相当大效率问题。


condition可以通俗的理解为条件队列。当一个线程在调用了await方法以后,直到线程等待的某个条件为真的时候才会被唤醒。这种方式为线程提供了更加简单的等待/通知模式。Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此Condition一般都是作为Lock的内部实现。


Condition接口常用方法


1、await() :造成当前线程在接到信号或被中断之前一直处于等待状态。


2、await(long time, TimeUnit unit) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。


3、awaitNanos(long nanosTimeout) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。返回值表示剩余时间,如果在nanosTimesout之前唤醒,那么返回值 = nanosTimeout - 消耗时间,如果返回值 <= 0 ,则可以认定它已经超时了。


4、awaitUninterruptibly() :造成当前线程在接到信号之前一直处于等待状态。【注意:该方法对中断不敏感】。


5、awaitUntil(Date deadline) :造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。如果没有到指定时间就被通知,则返回true,否则表示到了指定时间,返回返回false。


6、signal() :唤醒一个等待线程。该线程从等待方法返回前必须获得与Condition相关的锁。


7、signal()All :唤醒所有等待线程。能够从等待方法返回的线程必须获得与Condition相关的锁。


ReentrantLock使用Condition:


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * condition的使用
 */
public class Demo14 {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public static void main(String[] args) throws InterruptedException {
        Mehtod14 mehtod14 = new Mehtod14();
        Thread t = new Demo14_Thread(mehtod14);
        t.start();
        Thread.sleep(2000);
        mehtod14.conditionsignal();
    }
}
class Mehtod14{
    private Lock lock = new ReentrantLock();
    //condition对象是依赖于lock对象的,condition对象需要通过lock对象进行创建出来(调用Lock对象的newCondition()方法)
    private Condition condition = lock.newCondition();
    public void conditionaWait(){
        //加锁
        lock.lock();
        System.out.println("conditionaWait方法开始:"+System.currentTimeMillis());
        System.out.println(Thread.currentThread().getName() + "拿到锁了");
        System.out.println(Thread.currentThread().getName() + "等待信号");
        try {
            //这里需要抛出异常
            condition.await();
            System.out.println("conditionaWait方法结束:"+System.currentTimeMillis());
            System.out.println(Thread.currentThread().getName() + "拿到信号");
            //解锁
            lock.unlock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void conditionsignal(){
        try {
            lock.lock();
            System.out.println("conditionsignal方法开始:" + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName() + "拿到锁了");
            condition.signal();
            System.out.println(Thread.currentThread().getName() + "发出信号");
            lock.unlock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class Demo14_Thread extends Thread{
    private Mehtod14 mehtod14;
    public Demo14_Thread(Mehtod14 mehtod14){
        this.mehtod14 = mehtod14;
    }
    @Override
    public void run() {
        mehtod14.conditionaWait();
    }
}
复制代码


运行结果如下:


7372361eeff94e67b0ba1dcf0f8b899c~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


一般都会将Condition对象作为成员变量。当调用await()方法后,当前线程会释放锁并在此等待,而其他线程调用Condition对象的signal()方法,通知当前线程后,当前线程才从await()方法返回,并且在返回前已经获取了锁。


使用Condition唤醒不同的线程:


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//condition唤醒不同线程
public class Demo15 {
    public static void main(String[] args) throws InterruptedException {
        Method15 method15 = new Method15();
        Thread t1 = new Demo15Thread_A(method15);
        t1.setName("A");
        t1.start();
        Thread t2 = new Demo15Thread_B(method15);
        t2.setName("B");
        t2.start();
        Thread.sleep(2000);
        method15.conditionSignalAll_A();
    }
}
class Method15{
    private Lock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();
    public void conditionAwiat_A(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"开始执行conditionAwiat_A");
            conditionA.await();
            System.out.println(Thread.currentThread().getName()+"结束执行conditionAwiat_A");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void conditionAwiat_B(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"开始执行conditionAwiat_B");
            conditionA.await();
            System.out.println(Thread.currentThread().getName()+"结束执行conditionAwiat_B");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void conditionSignalAll_A(){
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "唤醒所有的线程在");
            //唤醒所有的线程
            conditionA.signalAll();
        }finally {
            lock.unlock();
        }
    }
    public void conditionSignalAll_B(){
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "唤醒所有的线程在");
            conditionB.signalAll();
        }finally {
            lock.unlock();
        }
    }
}
class Demo15Thread_A extends Thread{
    private Method15 method15;
    public Demo15Thread_A(Method15 method15){
        this.method15 = method15;
    }
    @Override
    public void run() {
        method15.conditionAwiat_A();
    }
}
class Demo15Thread_B extends Thread{
    private Method15 method15;
    public Demo15Thread_B(Method15 method15){
        this.method15 = method15;
    }
    @Override
    public void run() {
        method15.conditionAwiat_B();
    }
}
复制代码


结果如下:


09a296176609448489ffbaeb8a687277~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


分别唤醒不同的线程,就需要使用多个Condition对象,也就是Condition对象可以唤醒部分指定的线程,有助于提升程序的运行效率。


b39c790299a64882a341b7f7a712cbbe~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


1b3bba9e471e4eb9b96e0ed4f0031fec~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


通过上面源码分析可以看出Condition是AQS的内部类。每个Condition对象都包含一个等待队列,在队列中的每个节点都包含了一个线程引用,该线程就是在Condition对象上等待的线程,如果一个线程调用了Condition.await()方法,那么该线程将会释放锁、构造成节点加入等待队列并进入等待状态。


等待分为首节点和尾节点。当一个线程调用Condition.await()方法,将会以当前线程构造节点,并将节点从尾部加入等待队列。新增节点就是将尾部节点指向新增的节点。节点引用更新本来就是在获取锁以后的操作,所以不需要CAS保证。同时也是线程安全的操作。


当线程调用了await方法以后。线程就作为队列中的一个节点被加入到等待队列中去了。同时会释放锁的拥有。当从await方法返回的时候。一定会获取condition相关联的锁。当等待队列中的节点被唤醒的时候,则唤醒节点的线程开始尝试获取同步状态。如果不是通过 其他线程调用Condition.signal()方法唤醒,而是对等待线程进行中断,则会抛出InterruptedException异常信息。


而调用Condition的signal()方法,将会唤醒在等待队列中等待最长时间的节点(条件队列里的首节点),在唤醒节点前,会将节点移到同步队列中。在调用signal()方法之前必须先判断是否获取到了锁。接着获取等待队列的首节点,将其移动到同步队列并且利用LockSupport唤醒节点中的线程。被唤醒的线程将从await方法中的while循环中退出。随后加入到同步状态的竞争当中去。成功获取到竞争的线程则会返回到await方法之前的状态。


使用Lock的Condition实现生产者与消费者模型:


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo16 {
    public static void main(String[] args) {
        Demo16Service service = new Demo16Service();
        // 一生产一消费
//        Thread producer = new Demo05ProducerThread(service);
//        producer.start();
//
//        Thread consumer = new Demo05ConsumerThread(service);
//        consumer.start();
        // 多生产多消费
        int size = 2;
        Thread[] producers = new Thread[size];
        Thread[] consumers = new Thread[size];
        for (int i = 0; i < size; i++) {
            char c = (char)('A' + i);
            producers[i] = new Demo16ProducerThread(service);
            producers[i].setName("生产者" + c);
            producers[i].start();
            consumers[i] = new Demo16ConsumerThread(service);
            consumers[i].setName("消费者" + c);
            consumers[i].start();
        }
    }
}
class Demo16Service{
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private String val = "";
    public void set(){
        try{
            lock.lock();
            while(!"".equals(val)){
                System.out.println(Thread.currentThread().getName() + "开始等待");
                condition.await();
            }
            val = System.currentTimeMillis() + "-" + System.nanoTime();
            System.out.println(Thread.currentThread().getName() + "生产值:" + val);
            //condition.signal();
            condition.signalAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void get(){
        try{
            lock.lock();
            while("".equals(val)){
                System.out.println(Thread.currentThread().getName() + "开始等待");
                condition.await();
            }
            System.out.println(Thread.currentThread().getName() + "消费值:" + val);
            val = "";
//            condition.signal();
            condition.signalAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
class Demo16ProducerThread extends Thread{
    private Demo16Service service;
    public Demo16ProducerThread(Demo16Service service){
        this.service = service;
    }
    @Override
    public void run() {
        while(true){
            service.set();
        }
    }
}
class Demo16ConsumerThread extends Thread{
    private Demo16Service service;
    public Demo16ConsumerThread(Demo16Service service){
        this.service = service;
    }
    @Override
    public void run() {
        while(true){
            service.get();
        }
    }
}
复制代码


部分结果如下:


c1d45c8d0b0245d994c3bf9abbe7da9c~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


类ReentrantLock具有完全互斥排他锁,也就是同一时间内只有一个线程可以在执行Reentrant.lock方法后面的任务。这样虽然可以保证实例变量的线程安全性,但是效率却是非常的低下。所以在JDK中提供了一种读写锁ReentrantReadWriteLock类,使用它可以加快运行的效率,在某些不需要操作实例变量的方法中,完全可以使用读写锁来提升方法的代码运行效率。


读写锁表示有两个锁,一个是读操作也称作共享锁,另一个是写操作也叫排他锁。也就是多个读锁之间不互斥,读锁与写锁是互斥,写锁与写锁之间也是互斥的。在没有线程进入定入操作时,进行读取操作的多个线程都可以获取 读取锁,而进行写入操作时只有在获取写锁后才能进行写入操作。即可以有多个线程同时进行读取操作,但同一时内只能有一个线程进行写操作。

ReentrantReadWriteLock源码:


461527bbc05c46a09754cd90fcf696ec~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


package com.jiangxia.chap3;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * ReentrantReadWriteLock
 */
public class Demo17 {
    public static void main(String[] args) {
        Demo17Service service = new Demo17Service();
        Thread t1 = new Demo17ThreadA(service);
        t1.setName("读线程A");
        t1.start();
        Thread t2 = new Demo17ThreadA(service);
        t2.setName("读线程B");
        t2.start();
        Thread t3 = new Demo17ThreadB(service);
       t3.setName("写线程A");
       t3.start();
       Thread t4 = new Demo17ThreadB(service);
       t4.setName("写线程B");
       t4.start();
    }
}
class Demo17Service{
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public void read(){
        try{
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + "获得读锁于" + System.currentTimeMillis());
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "解除读锁于" + System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }
    }
    public void write(){
        try{
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + "获得写锁于" + System.currentTimeMillis());
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "解除写锁于" + System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();
        }
    }
}
class Demo17ThreadA extends Thread{
    private Demo17Service service;
    public Demo17ThreadA(Demo17Service service){
        this.service = service;
    }
    @Override
    public void run() {
        service.read();
    }
}
class Demo17ThreadB extends Thread{
    private Demo17Service service;
    public Demo17ThreadB(Demo17Service service){
        this.service = service;
    }
    @Override
    public void run() {
        service.write();
    }
}
复制代码


结果如下:


f5c6e621fa984251be5929cec1d0859b~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


通过上述的结果可以看出读写或写读操作都是互斥的,只要出现 写操作的过程,就是互斥的。读读是共享的。


3 锁的相关概念介绍


1、可重入锁


如果锁具备可重入性,则称作为可重入锁 。像 synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。比如下面的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。


class MyClass {
    public synchronized void method1() {
        method2();
    }
    public synchronized void method2() {
    }
}
复制代码


上述代码中的两个方法method1和method2都用synchronized修饰了。假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是,这就会造成死锁,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。


2、可中断锁


可中断锁就是可以响应中断的锁。在Java中,synchronized就不是可中断锁,而Lock是可中断锁。


如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。


3、公平锁/非公平锁


公平锁即尽量以请求锁的顺序来获取锁。比如,同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。而非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。


在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于ReentrantLock 和 ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。


4、独享锁/共享锁


独享锁是指该锁一次只能被一个线程所持有。共享锁是指该锁可被多个线程所持有。


Java中的ReentrantLock是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。而Synchronized是独享锁。


5、互斥锁/读写锁


前面的独享锁/共享锁就是一种广义的说法,而互斥锁/读写锁就是具体的实现。


互斥锁在Java中的具体实现就是ReentrantLock。


读写锁在Java中的具体实现就是ReadWriteLock。


6、乐观锁/悲观锁


乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。


悲观锁会认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。悲观的认为,不加锁的并发操作一定会出问题。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。


而乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。


悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。


悲观锁在Java中的使用,就是利用各种锁。


乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。


7、偏向锁/轻量级锁/重量级锁


这三种锁是指锁的状态,并且是针对Synchronized。在Jdk1.5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。


偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。


轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。


重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。


8、分段锁


分段锁是一种锁的设计,并不是具体的一种锁,最常见的ConcurrentHashMap的并发的实现就是通过分段锁的形式来实现高效的并发操作。


ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK1.7与JDK1.8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。


分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。


9、自旋锁


自旋锁是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。


获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting。


所以这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。


4 总结


本篇文章主要介绍了java多线程编程中lock的相关概念以及其实现类ReentrantLock的使用,以及如何使用condition进行线程之间的通信还有读写锁ReentrantReadWriteLock类的简单使用。在多线程同步中可以使用Lock和synchronized,但是Lock和synchronized有一点非常大的不同,就是采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

目录
相关文章
|
1月前
|
Java 开发者
Java多线程教程:使用ReentrantLock实现高级锁功能
【4月更文挑战第6天】`ReentrantLock`是Java并发编程中一个强大的同步工具,比`synchronized`提供更丰富功能。它支持可响应性、可中断性、公平性选择及条件变量。通过示例展示了创建、公平性设置、可中断锁定、尝试锁定及条件变量的使用。`ReentrantLock`使线程同步更灵活,适用于高性能应用,但使用需谨慎,理解其原理并恰当使用。
|
1月前
|
安全 Java
java保证线程安全关于锁处理的理解
了解Java中确保线程安全的锁机制:1)全局synchronized方法实现单例模式;2)对Vector/Collections.SynchronizedList/CopyOnWriteArrayList的部分操作加锁;3)ConcurrentHashMap的锁分段技术;4)使用读写锁;5)无锁或低冲突策略,如Disruptor队列。
20 2
|
10天前
|
Java 程序员 开发者
深入理解Java并发编程:线程同步与锁机制
【4月更文挑战第30天】 在多线程的世界中,确保数据的一致性和线程间的有效通信是至关重要的。本文将深入探讨Java并发编程中的核心概念——线程同步与锁机制。我们将从基本的synchronized关键字开始,逐步过渡到更复杂的ReentrantLock类,并探讨它们如何帮助我们在多线程环境中保持数据完整性和避免常见的并发问题。文章还将通过示例代码,展示这些同步工具在实际开发中的应用,帮助读者构建对Java并发编程深层次的理解。
|
10天前
|
Linux API C++
c++多线程——互斥锁
c++多线程——互斥锁
|
1天前
|
安全 Java 程序员
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
5 0
|
1天前
|
Java
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
9 1
|
3天前
|
算法 安全 Linux
【探索Linux】P.20(多线程 | 线程互斥 | 互斥锁 | 死锁 | 资源饥饿)
【探索Linux】P.20(多线程 | 线程互斥 | 互斥锁 | 死锁 | 资源饥饿)
9 0
|
11天前
|
消息中间件 安全 算法
通透!从头到脚讲明白线程锁
线程锁在分布式应用中是重中之重,当谈论线程锁时,通常指的是在多线程编程中使用的同步机制,它可以确保在同一时刻只有一个线程能够访问共享资源,从而避免竞争条件和数据不一致性问题。
|
16天前
|
Java 调度
Java中的并发编程:从线程到锁的探索
在Java的并发编程中,线程与锁是构建高效、稳定多线程应用程序的关键要素。本文将带你走进Java并发编程的世界,从线程的基础知识讲起,逐步深入到锁的实现与应用,旨在让读者对Java并发编程有更深入的理解,并能在实际开发中灵活运用。
|
21天前
|
算法 Java 编译器
【JavaEE多线程】掌握锁策略与预防死锁
【JavaEE多线程】掌握锁策略与预防死锁
23 2