闭关修炼(四)并发包/类

传统艺能,点到为止



并发包

你接触过哪些线程安全类?

你使用过哪些jdk1.5并发包下的类?

Vector和ArrayList

Vector和ArrayList的区别

Vector和ArrayList原理都是由数组实现的,查询速度块,增加、修改和删除速度慢。

最大的区别在于线程安全问题,Vector是线程安全的,ArrayList是线程不安全的,但ArrayList效率更高。

Vector是线程安全的那么必然是上了锁的类集合。

Vector源码

点进Vector类,看get set add方法的实现,我们发现他们的方法都被synchronized所修饰。多个线程使用Vector类,只要有一个线程在操作,其他线程都读不了数据,还引发锁资源竞争,因此效率很低。

 /**
     * Returns the element at the specified position in this Vector.
     *
     * @param index index of the element to return
     * @return object at the specified index
     * @throws ArrayIndexOutOfBoundsException if the index is out of range
     *            ({@code index < 0 || index >= size()})
     * @since 1.2
     */
    public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        return elementData(index);
    }

    /**
     * Replaces the element at the specified position in this Vector with the
     * specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws ArrayIndexOutOfBoundsException if the index is out of range
     *         ({@code index < 0 || index >= size()})
     * @since 1.2
     */
    public synchronized E set(int index, E element) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

    /**
     * Appends the specified element to the end of this Vector.
     *
     * @param e element to be appended to this Vector
     * @return {@code true} (as specified by {@link Collection#add})
     * @since 1.2
     */
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

ArrayList源码

点进ArrayList一看就知道,是没有做任何的同步

/**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

    /**
     * Replaces the element at the specified position in this list with
     * the specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

Hashtable和HashMap

Hashtable和HashMap的区别?

HashTable线程安全,HashMap线程不安全

Hashtable和HashMap的底层实现?

//todo 以后在细写吧。

链表+数组,链表做增加删除, HashCode取模得到下标位置,一致性取模算法。

Hashtable put方法源码

很明显可以看到put方法加了synchronized关键字。

	public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }

HashMap put方法源码

没有synchronized关键字

	/**	
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    

SynchronizedMap

什么是SynchronizedMap?

是Collections中的静态类,可以将不安全Map集合转变为安全的集合

//todo Collections再单独拿出来写吧…

SynchronizedMap原理?

原理无非就是用了Object锁的synchronized代码块

	private static class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;

        private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize

        SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }

        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }

        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();}
        }
	//...
}

ConcurrentHashMap

jdk1.5之后产生了许多的java并发包,ConcurrentHashMap就是其中之一,为解决1.2的Hashtable虽然是线程安全的但是效率非常低,造成锁的资源竞争问题。

ConcurrentHashMap设计思路/底层?

分段锁,将一个整体Map拆分成多个小的Hashtable,默认分成16段(上限为16),我们具体的假设,如Map的下标0到4分为一个Hashtable,5-9分为一个Hashtable,10-14分为一个Hashtable…多线程的情况下,线程①查询下标2,线程②查询下标5,线程③查询下标10,那么这三个线程并不使用同一把锁,相比之前的Hashtable,Hashtable三个线程共用同一把锁,ConcurrentHashMap减少了锁的资源竞争,因此效率得到了提高。并且代码中大多共享变量使用volatile关键字声明,目的是第一时间获取修改的内容。volatile不只是起到可见性的作用,还起禁止重排序功能。

CountDownLatch

什么是CountDownLatch?

CountDownLatch类位于concurrent包下,利用它可以实现类似计数器的功能,比如有一个任务A,它要等其他4个任务执行完毕后才能执行,此时就可以使用CountDownLatch来实现这种功能了。相比join可能会更方便一些。

CountDownLatch例子

使用起来很简单,实例化CountDownLatch,给初始值,使用countDown函数计数减一,使用countDownLatch.await()进行阻塞判断,大于0一直阻塞,小于等于0停止阻塞。

代码见下:

import lombok.SneakyThrows;

import java.util.concurrent.CountDownLatch;

public class CountDown {
    @SneakyThrows
    public static void main(String[] args) {
        // 定义计数器
        CountDownLatch countDownLatch = new CountDownLatch(2);
        Thread thread1 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                System.out.println("我是子线程1执行任务");
                Thread.sleep(10);
                System.out.println("我是子线程1执行任务");
                // 计数器减一
                countDownLatch.countDown();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                System.out.println("我是子线程2执行任务");
                Thread.sleep(10);
                System.out.println("我是子线程2执行任务");
                countDownLatch.countDown();
            }
        });

        thread1.start();
        thread2.start();
        // 如果不为0,阻塞
        countDownLatch.await();
        System.out.println("主线程开始执行任务");
        for (int i = 0; i < 3; i++) {
            Thread.sleep(1000);
            System.out.println(i);
        }
        System.out.println("主线程执行任务结束");

    }
}

CyclicBarrier

什么是CyclicBarrier?

jdk1.5并发包中的类,用的不多,了解即可,也是做计数用的,当我们线程到达一定次数,开始并行执行。

CyclicBarrier例子


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;

import java.util.concurrent.CyclicBarrier;

@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
class MyThread extends Thread {
    private CyclicBarrier cyclicBarrier;

    @SneakyThrows
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ",开始写入任务");
        // 模拟任务执行时间
        Thread.sleep(1);
        // await大于0时线程阻塞和计数减一,当为0的时候,所有线程共同并行
        cyclicBarrier.await();
        System.out.println(Thread.currentThread().getName() + ",写入任务结束...");
    }
}

public class CyclicTest {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(50);
        for (int i = 0; i < 50; i++) {
            new MyThread(cyclicBarrier).start();
        }

    }
}

Semaphore

什么是Semaphore

Semaphore属于并发包中的一类,Semaphore可以看作是一种基于计数的信号量,可以设定一个阈值,这个阈值表示最多支持几个线程访问,基于此,多个线程竞争获取许可信号,做自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。

Semaphore可以用来构建一些对象池,资源池,比如数据库连接池,Semaphore计数为1,将变成类似互斥锁的机制。

Semaphore这个概念应该并不陌生。

Semaphore例子

掌握主要方法acquire、availablePermits、release,获取和释放资源
acquire 获取资源,计数-1,阻塞等待
release释放资源计数+1
availablePermits返回此Semaphore对象中当前可用的许可数,许可的数量有可能实时在改变,并不是固定的数量。

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;

import java.util.Random;
import java.util.concurrent.Semaphore;

@Data
@AllArgsConstructor
class Parent implements Runnable {
    private Semaphore semaphore;
    private String name;

    @SneakyThrows
    @Override
    public void run() {
        int i = semaphore.availablePermits();
        if (i > 0) {
            System.out.println(getName() +  " ,i > 0");
        } else {
            System.out.println(getName() +  " ,i < 0,wait..");
        }
        semaphore.acquire();
        System.out.println(getName() +  " ,entering...");
        Thread.sleep(new Random().nextInt(10000));
        System.out.println(getName() +   " ,finish.");
        semaphore.release();
    }
}

public class SemaTest {
    public static void main(String[] args) {
        Semaphore s = new Semaphore(3);
        for (int i = 1; i <= 10; i++) {
            new Thread(new Parent(s, i+"")).start();
        }

    }
}

并发队列

并发队列也是并发包中的,他们都是线程安全的。
生产消费者模型中的缓冲buff就可以看作是一个并发队列
队列遵循规则:先进先出

并发队列有界和无界的区别?

Array数组规定长度,不能超过长度,就是有界的
无界支持无限制存放。

阻塞与非阻塞队列的区别?

生产者写入满的时候,即队列满了,线程进行等待;消费者当队列为空的时候,也进行等待,就是阻塞队列。

非阻塞的,满了或者空了线程不等待直接挂掉。

非阻塞式队列ConcurrentLinkedDeque

非阻塞式无界限安全队列 ConcurrentLinkedDeque和ConcurrentLinkedQueue,不同的是ConcurrentLinkedDueue是双向链表,因此ConcurrentLinkedDueue既可以当做队列也可当做栈来使用。

public class Qu {
    public static void main(String[] args) {
        ConcurrentLinkedDeque<String> concurrentLinkedDeque = new ConcurrentLinkedDeque<>();
        concurrentLinkedDeque.offer("张三");
        concurrentLinkedDeque.offer("李四");
        System.out.println(concurrentLinkedDeque.size());
        System.out.println(concurrentLinkedDeque.poll());
        System.out.println(concurrentLinkedDeque.size());
        System.out.println(concurrentLinkedDeque.poll());
        System.out.println(concurrentLinkedDeque.size());
    }
}

阻塞式队列BlockingQueue

BlockingQueue
常用的四个类是ArrayBlockingQueue、LinkedBlockingQueue,PriorityBlockingQueue和SynchronizedQueue

public class Qu {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(3);
        queue.add("张三");
        queue.add("李四");
        queue.add("王五");
        // 可阻塞的队列,超过界限超时2秒,挂掉
        queue.offer("老六",2,TimeUnit.SECONDS);
        System.out.println("阻塞2秒后结束");
        System.out.println(queue.size());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}

BlockingQueue和ConcurrentLinkedDeque的区别?

BlockingQueue可阻塞,并且时间有界限,ConcurrentLinkedDeque不阻塞

生产者消费者例子

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static java.lang.Thread.sleep;

@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
class ProducerThread extends Thread {
    private BlockingQueue<String> blockingQueue;
    private static AtomicInteger count = new AtomicInteger();
    private volatile Boolean allowProducing = true;

    @SneakyThrows
    @Override
    public void run() {
        System.out.println("生产者线程启动");
        while (allowProducing) {
            System.out.println("正在生产队列");
            String data = count.incrementAndGet() + "";
            boolean offer = blockingQueue.offer(data);
            if (offer) {
                System.out.println("生产者添加队列成功");
            } else {
                System.out.println("生产者添加队列失败");
            }
            sleep(1000);
        }
        System.out.println("生产者线程停止");
    }

    public void stopThread() {
        this.allowProducing = false;
    }
}

@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
class Consumer extends Thread {
    private BlockingQueue<String> blockingQueue;
    private volatile Boolean allowConsume = true;

    @SneakyThrows
    @Override
    public void run() {
        System.out.println("消费者线程启动");
        while (allowConsume) {
            String data = blockingQueue.poll(2, TimeUnit.SECONDS);
            if (data != null) {
                System.out.println("消费者获取数据: " + data);
            } else {
                System.out.println("消费者获取数据失败");
                this.allowConsume = false;
            }
            sleep(1000);
        }
    }

    public void stopThread() {
        this.allowConsume = false;
    }
}

public class TestScz {
    public static void main(String[] args) throws InterruptedException {
        LinkedBlockingDeque<String> blockingDeque = new LinkedBlockingDeque<>(10);
        ProducerThread producerThread1 = new ProducerThread(blockingDeque, true);
        ProducerThread producerThread2 = new ProducerThread(blockingDeque, true);
        Consumer consumer = new Consumer(blockingDeque, true);

        producerThread1.start();
        producerThread2.start();
        consumer.start();

        Thread.sleep(10 * 1000);
        producerThread1.stopThread();
        producerThread2.stopThread();
    }
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值