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

简介: 在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则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

目录
相关文章
|
11天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
14天前
|
数据采集 存储 数据处理
Python中的多线程编程及其在数据处理中的应用
本文深入探讨了Python中多线程编程的概念、原理和实现方法,并详细介绍了其在数据处理领域的应用。通过对比单线程与多线程的性能差异,展示了多线程编程在提升程序运行效率方面的显著优势。文章还提供了实际案例,帮助读者更好地理解和掌握多线程编程技术。
|
13天前
|
API Android开发 iOS开发
深入探索Android与iOS的多线程编程差异
在移动应用开发领域,多线程编程是提高应用性能和响应性的关键。本文将对比分析Android和iOS两大平台在多线程处理上的不同实现机制,探讨它们各自的优势与局限性,并通过实例展示如何在这两个平台上进行有效的多线程编程。通过深入了解这些差异,开发者可以更好地选择适合自己项目需求的技术和策略,从而优化应用的性能和用户体验。
|
18天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
27天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
24天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
27天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
28天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
47 1
|
1月前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
1月前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
44 4