并发编程六-并发工具包(JUC)

目录

 

一,概述

1,JUC是什么?

2,JUC有什么?

3,有了synchronized可以同步代码,为什么还有有JUC?

4,synchronized锁与juc提供的Lock锁有什么区别?

二、原子类

三、锁

1,ReentrantLock (可重入锁)

2,ReentrantReadWriteLock (可重入读写锁)

2.1 原理介绍

2.2 使用

四、线程间的通信(辅助工具类)

1,CountDownLatch

2,CyclicBarrier 

2.1 构造函数

2.2 代码示例:

2.3 使用场景:

2.4 与CountDownLatch的区别;

3,Semaphore

3.1 构造函数:

3.2 示例:

3.3 方法介绍:

4,其他

五、线程安全集合介绍

1,Vector

2,HashTable

3,ConcurrentHashMap

4,CopyOnWriteArrayList

5,CopyOnWriteArraySet

6,ConcurrentSkipListMap

7,ConcurrentSkipListSet

8,Collections包装方法

8.1 synchronizedList

8.2 synchronizedSet

8.3 synchronizedMap

六、队列介绍

1,队列

2,队列的使用:

3,队列的方法:

3.1把元素(任务)加入队列的方法:

3.2 从队列获取元素(任务)的方法:

3.3 方法详解:


 

一,概述

1,JUC是什么?

jdk提供了一些多线程同步相关的并发工具类,在java.util.concurrent包下,简称juc。

2,JUC有什么?

juc提供了原子操作类、各种锁、线程安全的集合、线程安全的队列等工具类。

3,有了synchronized可以同步代码,为什么还有有JUC?

来个小示例,模拟业务需求:有三个步骤,每个步骤一个方法,每个方法使用一个线程来负责执行,并且这三个方法是有执行顺序:

方法A由线程A来执行,方法B由线程B来执行,方法C由线程C来执行

同时三个方法的执行顺序为A->B->C

使用synchronized关键字来实现,代码如下:

package com.salulu.juc1;

public class SyncKeyDemo {
    public static void main(String[] args) {
        SyncResourceInfo resourceInfo = new SyncResourceInfo();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                resourceInfo.funA();
            }
        },"Thread-A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                resourceInfo.funB();
            }
        },"Thread-B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                resourceInfo.funC();
            }
        },"Thread-C").start();
    }
}


class SyncResourceInfo {
    private int flag = 1;

    public synchronized void funA() {
        while (flag != 1) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+" -> funA");
        flag = 2;
        this.notifyAll();
    }

    public synchronized void funB() {
        while (flag != 2) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+" -> funB");
        flag = 3;
        this.notifyAll();
    }

    public synchronized void funC() {
        while (flag != 3) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+" -> funC");
        flag = 1;
        this.notifyAll();
    }
}

运行结果:

Thread-A -> funA
Thread-B -> funB
Thread-C -> funC
Thread-A -> funA
Thread-B -> funB
Thread-C -> funC
Thread-A -> funA
Thread-B -> funB
....

功能是实现了,但是有个缺陷,就是在方法中没有办法唤醒指定的线程,只能唤醒所有的线程。

那能不能解决这个问题呢?让线程唤醒指定的线程

下面使用JUC来实现:

package com.salulu.juc1;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockSyncDemo {

    public static void main(String[] args) {
        ResourceInfo resourceInfo = new ResourceInfo();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                resourceInfo.funA();
            }
        },"Thread-A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                resourceInfo.funB();
            }
        },"Thread-B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                resourceInfo.funC();
            }
        },"Thread-C").start();

    }
}

class ResourceInfo{
    private int flag = 1;
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    public void funA() {
        try {
            lock.lock();
            while (flag!=1){// 如果标记不等于1则等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+" -> funA");
            // 设置标记位值为2,唤醒指定的线程来执行对应的方法
            flag=2;
            condition2.signal();
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void funB() {
        try {
            lock.lock();
            while (flag!=2){// 如果标记不等于2则等待
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+" -> funB");
            // 设置标记位值为3,唤醒指定的线程来执行对应的方法
            flag=3;
            condition3.signal();
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void funC() {
        try {
            lock.lock();
            while (flag!=3){// 如果标记不等于1则等待
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+" -> funC");
            // 设置标记位值为1,唤醒指定的线程来执行对应的方法
            flag=1;
            condition1.signal();
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

执行结果:

Thread-A -> funA
Thread-B -> funB
Thread-C -> funC
Thread-A -> funA
Thread-B -> funB
....

使用JUC来实现,在实现功能的基础上,还做到了只唤醒指定需要唤醒的线程。

 

4,synchronized锁与juc提供的Lock锁有什么区别?

  • synchronized无法判断获取锁的状态,Lock可以判断是否获取锁
  • synchronized会自动释放锁,Lock需要手动释放
  • synchronized获取不到锁时会一直等待,Lock提供了tryLock()方法来尝试获取锁,可以写获取锁失败的代码逻辑
  • synchronized是非公平锁,但是Lock自己自定义锁的类型为公平锁或非公平锁
  • synchronized是Java关键字,Lock是Java的接口和相关的实现类
  • 共同点:都是可重入锁,都能实现同步功能

二、原子类

关于原子操作类,在之前有写过一遍笔记,并发编程四-原子操作(比较并替换CAS操作)

地址:https://blog.csdn.net/baidu_32689899/article/details/106730593

这里做个补充:带版本号的原子操作类,每次进行CAS操作都可以设置版本号,比如让版本号+1。

实现类:AtomicStampedReference<V>

可以看到是带一个泛型的,下面来看看它的构造函数:

    /**
     * @param initialRef 初始值(初始对象)
     * @param initialStamp 版本号
     */
    public AtomicStampedReference(V initialRef, int initialStamp) {
        pair = Pair.of(initialRef, initialStamp);
    }

使用:

// 这是使用Integer类型,初始值为0,版本号为0
AtomicStampedReference atomicStampedReference = new AtomicStampedReference<Integer>(0,0);
// 每次进行原子更新的时候,就让版本号+1
boolean b1 = atomicStampedReference.compareAndSet(0, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
boolean b2 = atomicStampedReference.compareAndSet(100, 200,atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
// 这里再次把值设置为0,出现ABA的情况,在多个线程同时进行CAS操作的时候,因为版本号不同,所以是会更新数据失败的,避免了ABA问题
boolean b3 = atomicStampedReference.compareAndSet(200, 0,atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);

 

三、锁

1,ReentrantLock (可重入锁)

ReentrantLock内部实现了公平锁和非公平锁,关于公平锁具体的实现在一篇笔记中有写到过:

并发编程五(AQS):https://blog.csdn.net/baidu_32689899/article/details/106746253

这就介绍一下它提供的API:

lock():获得锁。
tryLock():只有在调用时锁没有被其他线程持有时才获取锁。
tryLock(long timeout, TimeUnit unit):只有在调用时锁没有被其他线程持有时才获取锁。可以设置等待超时时间,如果在此时间内还没有获取锁,则抛出异常(InterruptedException)
unlock():释放锁。
newCondition():获取一个Condition对象,该对象提供了线程的等待和通知的方法,但是值得注意的是通知唤醒线程是可以指定线程的,提供了对线程更加丰富的操作。关于Condition的使用,在上面概述中有示例代码。
isLocked():查询此锁是否由任何线程持有。
getQueueLength():返回等待获取此锁的线程数的估计数。这个值只是一个估计值,因为在此方法遍历内部数据时,线程的数量可能会动态变化。
hasQueuedThread():查询给定的线程是否正在等待获取此锁。注意获取锁可能在任何时候发生。
getQueuedThreads():返回包含可能正在等待获取此锁的线程的集合。

2,ReentrantReadWriteLock (可重入读写锁)

2.1 原理介绍

首先看下构造函数:

public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

可以看到,读锁和写锁,内读写锁内部其实是分别new了两个不同的锁对象。

读锁可以理解为多个线程都可以获取到该锁对象

写锁的实现其实与ReentrantLock一样

2.2 使用

代码中有详细的注释,这里就不做重复的说明:

package com.salulu.juc4;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {

    private Map<String,String> dataMap = new HashMap<>();// 线程不安全的集合
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();// 创建一个读写锁
    Lock readLock = readWriteLock.readLock();// 获取读锁
    Lock writeLock = readWriteLock.writeLock();// 获取写锁

    public static void main(String[] args) {
        ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
        // 使用6个线程来写数据
        for (int i = 0; i < 6; i++) {
            int finalI = i;
            new Thread(()->{
                String str = String.valueOf(finalI);
                readWriteLockDemo.writeFun(str,str);
            },""+i).start();
        }
        // 使用6个线程来读数据
        for (int i = 0; i < 6; i++) {
            int finalI = i;
            new Thread(()->{
                String str = String.valueOf(finalI);
                readWriteLockDemo.readFun(str);
            },""+i).start();
        }
    }

    // 读锁:运行多个线程同时获取读锁,也就是说同时可以运行多个线程读取数据
    public void readFun(String key){
        readLock.lock();// 获取读锁
        try{
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName+" read key="+key+" 开始");
            String val = dataMap.get(key);
            System.out.println(threadName+" read key="+key+" 完成");
        }finally {
            readLock.unlock();// 释放读锁
        }
    }

    // 写锁:同时只会有一个线程能获取到锁,也就是说同时只能有一个线程进行写操作
    public void writeFun(String key,String value){
        writeLock.lock();// 获取写锁
        try{
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName+" write key="+key+",value="+value+" 开始");
            dataMap.put(key,value);
            System.out.println(threadName+" write key="+key+",value="+value+" 完成");
        }finally {
            writeLock.unlock();// 使用写锁
        }
    }

}

运行结果:

 

四、线程间的通信(辅助工具类)

1,CountDownLatch

允许一个或多个线程等待,直到在其他线程中执行的操作完成,计数器归零后,等待线程继续运行。

示例:

package com.salulu.juc5;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        // 某个业务需要三个小的业务计算逻辑,都是耗时操作,等三个步骤完成后,才能继续往下执行
        // 这里初始化计数器的值为3
        CountDownLatch countDownLatch = new CountDownLatch(3);
        new Thread(()->{
            try {
                // 模拟业务A
                Thread.sleep(1000);
                System.out.println("业务A完成");
                // 业务A完成后给计数器减一
                countDownLatch.countDown();
                // 模拟业务B
                Thread.sleep(1000);
                System.out.println("业务B完成");
                // 业务B完成后给计数器减一
                countDownLatch.countDown();

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                // 模拟业务C
                Thread.sleep(1000);
                System.out.println("业务C完成");
                // 业务C完成后给计数器减一
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }).start();

        // 等待三个小的业务处理完成后
        countDownLatch.await();
        System.out.println("完美完成。");
    }

}

2,CyclicBarrier 

2.1 构造函数

带两个参数的:public CyclicBarrier(int parties, Runnable barrierAction)

parties:需要满多少个线程,这些线程才继续执行

barrierAction:线程数满parties的值的时候,将执行指定的barrier动作,由最后一个进入barrier的线程执行。

一个参数的:public CyclicBarrier(int parties, Runnable barrierAction)

parties:需要满多少个线程,这些线程才继续执行

2.2 代码示例:

package com.salulu.juc5;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // 某个集字的小游戏,需要同时集齐大威天龙四个字,才能打开门
        CyclicBarrier cyclicBarrier = new CyclicBarrier(4,()->{
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("集齐大威天龙,门被打开");
        });
        new Thread(()->{
            try {
                System.out.println("到达栅栏1-得到'大");
                cyclicBarrier.await();
                System.out.println("冲破栅栏1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                System.out.println("到达栅栏2-得到'威'");
                cyclicBarrier.await();
                System.out.println("冲破栅栏2");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                System.out.println("到达栅栏3-得到'天'");
                cyclicBarrier.await();
                System.out.println("冲破栅栏3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                System.out.println("到达栅栏4-得到'龙'");
                cyclicBarrier.await();
                System.out.println("冲破栅栏4");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

运行结果:

到达栅栏1-得到'大
到达栅栏2-得到'威'
到达栅栏3-得到'天'
到达栅栏4-得到'龙'
集齐大威天龙,门被打开
冲破栅栏4
冲破栅栏1
冲破栅栏3
冲破栅栏2

Process finished with exit code 0

2.3 使用场景:

可以用于多线程分部计算,把一个任务拆分成多个任务,在多个任务执行完成后,合并这些计算结果。

2.4 与CountDownLatch的区别;

CountDownLatch:一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。

CountDownLatch:多个个线程相互等待,在任何一个线程完成之前,所有的线程都必须等待。

CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的

3,Semaphore

控制并发访问的线程个数

3.1 构造函数:

public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

permits:可以获取许可的最初数量,即有多少个线程可以获取许可

fair:等待的线程是否使用公平锁,默认为非公平锁。

3.2 示例:

import java.util.concurrent.Semaphore;

public class SemaphoreDemo {

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();
                    // 执行业务代码
                    String name = Thread.currentThread().getName();
                    System.out.println(name+"正在执行");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            },"Thread-"+i).start();
        }
    }

}

运行结果描述:每次都是只有三个线程在同时执行输出语句

3.3 方法介绍:

acquire()方法:该方法是获取1个许可
acquire(int permits):带参数的,可以设置获取多个许可
release():释放许可
release(int permits):释放多个许可

 

4,其他

还提供了其他的一些辅助类

例如Phaser、StampedLock、Exchange等,暂不做介绍

五、线程安全集合介绍

1,Vector

Vector和ArrayList类似,它是jdk1.0时候提供的一个线程安全的集合

看源码得知它把所有可能产生线程不安全的操作方法都加了synchronized关键字来保证线程安全。

同一时刻只允许一个线程对它进行读写操作。

2,HashTable

HashTable和HashMap类似,它是jdk1.0时候提供的一个线程安全的集合

看源码得知它把所有可能产生线程不安全的操作方法都加了synchronized关键字来保证线程安全。

同一时刻只允许一个线程对它进行读写操作。

3,ConcurrentHashMap

在JDK1.8之前,ConcurrentHashMap加的是分段锁,也就是Segment锁,每个Segment含有整个table的一部分,这样不同分段之间的并发操作就互不影响 
JDK1.8对此做了进一步的改进,它取消了Segment字段,直接在table元素上加锁,实现对每一行进行加锁,进一步减小了并发冲突的概率.

4,CopyOnWriteArrayList

写时复制:查询的时候,不需要加锁,随便访问,只有在写入/删除的时候,才会从原来的数据复制一个副本出来,然后修改这个副本,最后把原数据替换成当前的副本。修改操作的同时,读操作不会被阻塞,而是继续读取旧的数据。这点要跟读写锁区分一下。

add()方法

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

可以从代码中看到:底层维护的是一个数组,新增元素时把数组长度+1,然后把原来的数组复制到新的数组中,把新的元素放到最后一个角标位置。

删除方法也是同样的思想。

5,CopyOnWriteArraySet

与CopyOnWriteArrayList一样的套路。

看构造函数:

public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }

底层其实就是一个CopyOnWriteArrayList集合。

6,ConcurrentSkipListMap

底层基于链表结构,在链表的基础上加了多层索引结构。

借鉴了二分查找的思想。

特点:

可排序;

所有操作都是无阻塞的,所有操作都可以并行;

支持一些原子复合操作;

 

7,ConcurrentSkipListSet

构造函数:

public ConcurrentSkipListSet() {
        m = new ConcurrentSkipListMap<E,Object>();
    }

基于ConcurrentSkipListMap实现。

8,Collections包装方法

8.1 synchronizedList

List<String> synArrayList = Collections.synchronizedList(new ArrayList<String>());

看它是怎么实现的?怎么包装一个就可以保证线程安全呢?

public static <T> List<T> synchronizedList(List<T> list) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list));
    }

从synchronizedList()的源码可以看出,它是根据类型判断之后,创建了一个新的集合,下面来看看这个新的集合的一些方法:

        public E get(int index) {
            synchronized (mutex) {return list.get(index);}
        }
        public E set(int index, E element) {
            synchronized (mutex) {return list.set(index, element);}
        }
        public void add(int index, E element) {
            synchronized (mutex) {list.add(index, element);}
        }
        public E remove(int index) {
            synchronized (mutex) {return list.remove(index);}
        }

        public int indexOf(Object o) {
            synchronized (mutex) {return list.indexOf(o);}
        }
        public int lastIndexOf(Object o) {
            synchronized (mutex) {return list.lastIndexOf(o);}
        }

从上面代码可以看出,这个方法都加了synchronized关键字,来保证线程安全。

8.2 synchronizedSet

Set<E> synHashSet = Collections.synchronizedSet(new HashSet<E>());

看synchronizedSet()方法的代码:

public static <T> Set<T> synchronizedSet(Set<T> s) {
        //创建了一个新的线程安全的集合
        return new SynchronizedSet<>(s);
    }

看SynchronizedSet类,

这这个类中没有看到ad d,remove这些方法,

但是看到了它继承SynchronizedCollection类

static class SynchronizedSet<E>
          extends SynchronizedCollection<E>
          implements Set<E> {
        private static final long serialVersionUID = 487447009682186044L;

        SynchronizedSet(Set<E> s) {
            super(s);
        }
        SynchronizedSet(Set<E> s, Object mutex) {
            super(s, mutex);
        }

        public boolean equals(Object o) {
            if (this == o)
                return true;
            synchronized (mutex) {return c.equals(o);}
        }
        public int hashCode() {
            synchronized (mutex) {return c.hashCode();}
        }
    }

来看SynchronizedCollection类,如下:

可以看到这个操作的方法调用都是加了synchronized关键字,来保证线程安全的。

static class SynchronizedCollection<E> implements Collection<E>, Serializable {
        private static final long serialVersionUID = 3053995032091335093L;

        final Collection<E> c;  // Backing Collection
        final Object mutex;     // Object on which to synchronize

        SynchronizedCollection(Collection<E> c) {
            this.c = Objects.requireNonNull(c);
            mutex = this;
        }

        SynchronizedCollection(Collection<E> c, Object mutex) {
            this.c = Objects.requireNonNull(c);
            this.mutex = Objects.requireNonNull(mutex);
        }

        public int size() {
            synchronized (mutex) {return c.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return c.isEmpty();}
        }
        public boolean contains(Object o) {
            synchronized (mutex) {return c.contains(o);}
        }
        public Object[] toArray() {
            synchronized (mutex) {return c.toArray();}
        }
        public <T> T[] toArray(T[] a) {
            synchronized (mutex) {return c.toArray(a);}
        }

        public Iterator<E> iterator() {
            return c.iterator(); // Must be manually synched by user!
        }

        public boolean add(E e) {
            synchronized (mutex) {return c.add(e);}
        }
        public boolean remove(Object o) {
            synchronized (mutex) {return c.remove(o);}
        }

        public boolean containsAll(Collection<?> coll) {
            synchronized (mutex) {return c.containsAll(coll);}
        }
        public boolean addAll(Collection<? extends E> coll) {
            synchronized (mutex) {return c.addAll(coll);}
        }
        public boolean removeAll(Collection<?> coll) {
            synchronized (mutex) {return c.removeAll(coll);}
        }
        public boolean retainAll(Collection<?> coll) {
            synchronized (mutex) {return c.retainAll(coll);}
        }
        public void clear() {
            synchronized (mutex) {c.clear();}
        }
        public String toString() {
            synchronized (mutex) {return c.toString();}
        }
        // Override default methods in Collection
        @Override
        public void forEach(Consumer<? super E> consumer) {
            synchronized (mutex) {c.forEach(consumer);}
        }
        @Override
        public boolean removeIf(Predicate<? super E> filter) {
            synchronized (mutex) {return c.removeIf(filter);}
        }
        @Override
        public Spliterator<E> spliterator() {
            return c.spliterator(); // Must be manually synched by user!
        }
        @Override
        public Stream<E> stream() {
            return c.stream(); // Must be manually synched by user!
        }
        @Override
        public Stream<E> parallelStream() {
            return c.parallelStream(); // Must be manually synched by user!
        }
        private void writeObject(ObjectOutputStream s) throws IOException {
            synchronized (mutex) {s.defaultWriteObject();}
        }
    }

8.3 synchronizedMap

Map<K,V> synHashMap = Collections.synchronizedMap(new HashMap<K,V>());

一样的套路,看下面的代码得知,同样是新建了一个线程同步的Map集合:

    public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
        return new SynchronizedMap<>(m);
    }

看SynchronizedMap类中的方法:

        public int size() {
            synchronized (mutex) {return m.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
        public boolean containsKey(Object key) {
            synchronized (mutex) {return m.containsKey(key);}
        }
        public boolean containsValue(Object value) {
            synchronized (mutex) {return m.containsValue(value);}
        }
        public V get(Object key) {
            synchronized (mutex) {return m.get(key);}
        }

        public V put(K key, V value) {
            synchronized (mutex) {return m.put(key, value);}
        }
        public V remove(Object key) {
            synchronized (mutex) {return m.remove(key);}
        }
        public void putAll(Map<? extends K, ? extends V> map) {
            synchronized (mutex) {m.putAll(map);}
        }
        public void clear() {
            synchronized (mutex) {m.clear();}
        }

同样看到了synchronized关键字。

 

六、队列介绍

1,队列

队列都遵循FIFO先进先出(first in first out)

队列常用的业务场景:生产者消费者模式。

使用队列可以解偶生产者和消费者。

  • ArrayBlockingQueue: 基于数组的阻塞队列
  • DelayQueue: 可以实现延时任务的队列
  • LinkedBlockingQueue: 基于链表的阻塞队列
  • PriorityBlockingQueue: 可以实现优先级的队列
  • SynchronousQueue: 同步队列,队列中只能有一个任务

2,队列的使用:

小示例:

代码中有相关说明,这里就不重复了。

package com.salulu.juc7;

import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {

    public static void main(String[] args) {
        // 创建一个基于数组的阻塞队列
        BlockingQueue blockingQueue = new ArrayBlockingQueue(10);
        // 创建一个生产者
        ProductData product = new ProductData(blockingQueue);
        // 创建一个消费者
        ConsumteData consumte = new ConsumteData(blockingQueue);
        // 用来存放生产者和消费者的数组
        Thread[] threads = new Thread[20];
        for (int i = 0; i < 10; i++) {
            //前10个为生产者线程
            threads[i] = new Thread(product);
            // 后10个为消费者线程
            threads[i+10] = new Thread(consumte);
        }
        // 启动这20个线程
        for (Thread thread : threads) {
            thread.start();
        }
    }

}

/**
 * 生产者
 */
class ProductData implements Runnable{
    private BlockingQueue<String> queue;
    public ProductData(BlockingQueue queue){
        this.queue = queue;
    }

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        try {
            // 生成数据放入队列中
            String data = UUID.randomUUID().toString();
            // 如果队列已满则阻塞等待
            queue.put(data);
            System.out.println(threadName+"生成数据:"+data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 消费者
 */
class ConsumteData implements Runnable{
    private BlockingQueue<String> queue;
    public ConsumteData(BlockingQueue queue){
        this.queue = queue;
    }

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        try {
            // 从队列中获取一条数据,如果队列为空,则阻塞等待
            String data = queue.take();
            System.out.println(threadName+"消费数据:"+data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3,队列的方法:

3.1把元素(任务)加入队列的方法:

add(E e) : 添加成功返回true,失败则会抛出IllegalStateException异常
offer(E e) : 成功返回 true,如果此队列已满,则返回 false(如果添加了时间参数,且队列已满也会阻塞)
put(E e) :将元素插入此队列的尾部,如果该队列已满,则一直阻塞

3.2 从队列获取元素(任务)的方法:

remove(Object o) :移除指定元素,成功返回true,失败返回false
poll() : 获取并移除此队列的头元素,若队列为空,则返回 null(如果添加了时间参数,且队列中没有数据也会阻塞)
take():获取并移除此队列头元素,若没有元素则一直阻塞。
peek() :获取但不移除此队列的头;若队列为空,则返回 null。

3.3 方法详解:

基于ArrayBlockingQueue实现类来说明:

3.3.1 put方法:

public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // 如果队列满了则阻塞等待
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

put方法使用ReentrantLock锁来保证线程安全。

3.3.2 add方法:

public boolean add(E e) {
        // 调用了父类AbstractQueue的add方法
        return super.add(e);
    }

父类AbstractQueue的add方法:

public boolean add(E e) {
        // 调用offer()方法
        if (offer(e))
            return true;
        else// 如果队列满了,offer()方法则返回false,就会进入else,然后抛出异常
            throw new IllegalStateException("Queue full");
    }

offer(e)方法:

public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 如果队列满了则返回false
            if (count == items.length)
                return false;
            else {
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }

同样是使用ReentrantLock锁来保证线程安全。

3.3.3 put方法:

public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

3.3.4 take()方法

public E take() throws InterruptedException {
        // 使用ReentrantLock锁
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // 如果为空则阻塞等待
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

3.3.5 poll()方法:

public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 如果队列为空,则返回null
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }

3.3.6 peek()方法:

public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 返回队列头部角标的元素
            return itemAt(takeIndex); // null when queue is empty
        } finally {
            lock.unlock();
        }
    }


final E itemAt(int i) {
        // 如果该角标下的元素为null,则返回null
        return (E) items[i];
    }

 

 

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值