多线程四(阻塞队列、信号量、同步集合)

  • 阻塞队列

队列是一种先进先出的数据结构。元素被追加到队列末尾,然后从队列头删除。

在优先队列中,元素被赋予优先级,当访问元素的时候,拥有最高优先级的元素首先被删除。

阻塞队列在试图向一个满队列添加元素或者从空队列中删除元素时会导致线程阻塞。

java中阻塞队列的实现由java.util.concurrent.BlockingQueue接口提供,该接口扩展了java.util.Queue,并且提供同步的put和take方法向队列尾部添加元素,以及从队列头部删除元素。

java中提供了java.util.concurrent.BlockingQueue接口的以下几种实现:

(1)ArrayBlockingQueue:使用数组实现阻塞队列,必须指定一个容量或者可选的公平性来构造。

(2)LinkedBlockingQueue:使用链表实现,可以创建不受限的或受限的队列。

(3)PriorityBlockingQueue:优先队列,可以创建不受限的或受限的优先队列。

注:对于不受限的队列,put方法永远不会阻塞。

阻塞队列和前面说的同步和锁的不同之处在于,阻塞队列中实现了锁和同步,所以不用手动编码。

public void test(){
        BlockingQueue queue = new ArrayBlockingQueue(2);
        try {
            queue.put("a");
            queue.take();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

put方法的内部实现:

多线程四(阻塞队列、信号量、同步集合)_第1张图片

多线程四(阻塞队列、信号量、同步集合)_第2张图片

take方法的内部实现:

多线程四(阻塞队列、信号量、同步集合)_第3张图片

多线程四(阻塞队列、信号量、同步集合)_第4张图片

  • 信号量

信号量可以用来限制访问共享资源的线程数。在访问资源之前,线程必须从信号量获取许可。在访问完资源之后,这个线程必须将许可返回给信号量。

java中信号量的实现是通过java.util.concurrent.Semaphore类实现的。

/**
     * 创建一个不公平策略的信号量    
     */
    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
/**
     * 创建一个公平策略的信号量
     */
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }
/**
     * 获取信号量
     */
    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
/**
     * 释放信号量
     */
    public void release() {
        sync.releaseShared(1);
    }

为了创建信号量,必须使用可选择的公平策略来确定许可的数量。任务通过调用信号量的acquire()方法来获取许可,通过调用信号量的release()方法来释放许可。一旦获得许可,信号量中可用的许可总数减1。一旦许可被释放,信号量中可用的许可总数又加1。

只有一个许可的信号量可以用来模拟一个相互排斥的锁。

  • 同步集合

​​​​​​​java集合框架中的类不是线程安全的,也就是说,如果它们同时被多个线程访问和更新,他们的内容可能被破坏。可以通过锁定集合或者同步集合保护集合中的数据。

java中通过集合工具类Collections中的以下几个方法支持将线程不安全的集合转换成线程安全的集合:

(1)返回同步集合:

public static  Collection synchronizedCollection(Collection c) {
        return new SynchronizedCollection<>(c);
    }

返回的新的Collection对象,它里面所有访问和更新原来的集合的方法都被同步,这些方法使用synchronized关键字来实现。

(2)返回来自指定线性表的同步线性表:

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

(3)返回来自指定图的同步图:

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

(4)从指定规则集返回同步规则集:

public static  Set synchronizedSet(Set s) {
        return new SynchronizedSet<>(s);
    }

(5)从指定有序图返回同步有序图:

public static  SortedMap synchronizedSortedMap(SortedMap m) {
        return new SynchronizedSortedMap<>(m);
    }

(6)返回同步有序规则集:

public static  SortedSet synchronizedSortedSet(SortedSet s) {
        return new SynchronizedSortedSet<>(s);
    }

在java.util.Vector、java.util.Stack和java.util.Hashtable中的方法已经被同步。它们都是java中的旧类。应该使用java.util.ArrayList替换Vector,用java.util.LinkedList替换Stack,用java.util.Map替换java.util.Hashtable。如果需要同步,就使用同步包装类。

这些同步包装类都是线程安全的,但是迭代器具有快速失败的的特性。这就意味着当下层集合被另一个线程修改时,如果在整个集合使用一个迭代器,那么迭代器会通过抛出异常java.util.ConcurrentModificationException而结束,该异常是RuntimeException的一个子类。为了避免这个错误,需要创建一个同步集合对象,并且在遍历它时获得对象上的锁。如下:

public void test(){
        Set hashSet = Collections.synchronizedSet(new HashSet());
        synchronized (hashSet) {
         Iterator iterator = hashSet.iterator();
         while(iterator.hasNext()){
             System.out.println(iterator.next());
         }
        }
    }

 

你可能感兴趣的:(Java)