关于java多线程

来源:互联网 发布:卖衣服的淘宝店 编辑:程序博客网 时间:2024/06/03 19:01

书的话,参考java并发编程实战。
博客可以参考这个Java Concurrency / Multithreading Tutorial

关于什么是thread safe:
参考What is meant by “thread-safe” code?
参考What does threadsafe mean?
参考What is this thing you call “thread safe”?

根据《Java Concurrency in Practice》的定义,一个线程安全的 class 应当满足以下三个条件:• 多个线程同时访问时,其表现出正确的行为。• 无论操作系统如何调度这些线程, 无论这些线程的执行顺序如何交织(interleaving)。• 调用端代码无须额外的同步或其他协调动作。

参考如何证明一个数据结构是线程安全的?

首先我们要定义线程安全,我比较认可的是在《Java concurrency in practice》一书中的定义:一个不论运行时(Runtime)如何调度线程都不需要调用方提供额外的同步和协调机制还能正确地运行的类是线程安全的多线程的场景很多很复杂,难以穷尽地说那些条件下是或者不是线程安全的,但是有一些常用的肯定线程安全的场景:1.无状态的一定是线程安全的。这个很好理解,因为所谓线程不安全也就是一个线程修改了状态,而另一个线程的操作依赖于这个被修改的状态。2.只有一个状态,而且这个状态是由一个线程安全的对象维护的,那这个类也是线程安全的。比如你在数据结构里只用一个AtomicLong来作为计数器,那递增计数的操作都是线程安全的,不会漏掉任何一次计数,而如果你用普通的long做++操作则不一样,因为++操作本身涉及到取数、递增、赋值 三个操作,某个线程可能取到了另外一个线程还没来得及写回的数就会导致上一次写入丢失。3.有多个状态的情况下,维持不变性(invariant)的所有可变(mutable)状态都用同一个锁来守护的类是线程安全的。这一段有些拗口,首先类不变性的意思是指这个类在多线程状态下能正确运行的状态,其次用锁守护的意思是所有对该状态的操作都需要获取这个锁,而用同一个锁守护的作用就是所有对这些状态的修改实际最后都是串行的,不会存在某个操作中间状态被其他操作可见,继而导致线程不安全。所以这里的关键在于如何确定不变性,可能你的类的某些状态对于类的正确运行是无关紧要的,那就不需要用和其他状态一样的锁来守护。因此我们常可以看到有的类里面会创建一个新的对象作为锁来守护某些和原类本身不变性无关的状态。上面这三种只是一种归纳,具体到实际应用时,要看你的类哪些状态是必须用锁来守护的,灵活变通。

参考下面代码是线程不安全的代码,请问为什么很难跑出不安全的样例?

一、关于java.util.concurrent下面的类

1.BlockingQueue

The Java BlockingQueue interface in the java.util.concurrent package represents a queue which is thread safe to put into, and take instances from.

典型场景:
这里写图片描述
A BlockingQueue with one thread putting into it, and another thread taking from it.

该类中定义了四种方式的方法:
这里写图片描述

The 4 different sets of behaviour means this:

1.Throws Exception:If the attempted operation is not possible immediately, an exception is thrown.
2.Special Value:If the attempted operation is not possible immediately, a special value is returned (often true / false).
3.Blocks:If the attempted operation is not possible immedidately, the method call blocks until it is.
4.Times Out:If the attempted operation is not possible immedidately, the method call blocks until it is, but waits no longer than the given timeout. Returns a special value telling whether the operation succeeded or not (typically true / false).

并且不能在BlockingQueue中添加null,不然汇报空指针异常。

BlockingQueue为接口,下面是几种实现:

1.ArrayBlockingQueue
2.DelayQueue
3.LinkedBlockingQueue
4.PriorityBlockingQueue
5.SynchronousQueue

关于BlockingQueue的例子,就是上图的producer和consumer的一个实现:

public class BlockingQueueExample {    public static void main(String[] args) throws Exception {        BlockingQueue queue = new ArrayBlockingQueue(1024);        Producer producer = new Producer(queue);        Consumer consumer = new Consumer(queue);    //两个不同的线程启动producer和consumer        new Thread(producer).start();        new Thread(consumer).start();        Thread.sleep(4000);    }}

producer:

public class Producer implements Runnable{    protected BlockingQueue queue = null;    //传入参数queue    public Producer(BlockingQueue queue) {        this.queue = queue;    }    public void run() {    //sleep是为了测试阻塞效果        try {            queue.put("1");            Thread.sleep(1000);            queue.put("2");            Thread.sleep(1000);            queue.put("3");        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

consumer:

public class Consumer implements Runnable{    protected BlockingQueue queue = null;    public Consumer(BlockingQueue queue) {        this.queue = queue;    }    public void run() {        try {            System.out.println(queue.take());            System.out.println(queue.take());            System.out.println(queue.take());        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

那么BlockingQueue内部是怎么实现的呢?

public class BlockingQueue {  private List queue = new LinkedList();  private int  limit = 10;  public BlockingQueue(int limit){    this.limit = limit;  }  public synchronized void enqueue(Object item)  throws InterruptedException  {    while(this.queue.size() == this.limit) {      wait();    }    if(this.queue.size() == 0) {      notifyAll();    }    this.queue.add(item);  }  public synchronized Object dequeue()  throws InterruptedException{    while(this.queue.size() == 0){      wait();    }    if(this.queue.size() == this.limit){      notifyAll();    }    return this.queue.remove(0);  }}

注意在notifyAll();只有在queue空或者满的时候才调用,因为只有这两种情况下才会产生等待。

用synchronized关键字和wait,notify性能会有影响,看看ArrayBlockingQueue中如何来实现的。

1.ArrayBlockingQueue

The ArrayBlockingQueue stores the elements internally in FIFO (First In, First Out) order. The head of the queue is the element which has been in queue the longest time, and the tail of the queue is the element which has been in the queue the shortest time.

2.DelayQueue

The DelayQueue blocks the elements internally until a certain delay has expired. The elements must implement the interface java.util.concurrent.Delayed. Here is how the interface looks: 放进去的元素必须实现Delayed接口

它的内部实现使用了priorityqueue。

不清楚Delayed接口该如何实现?看看:

package com.jcg;import java.util.concurrent.Delayed;import java.util.concurrent.TimeUnit;/** * @author ashraf */public class DelayObject implements Delayed {    private String data;    private long startTime;    public DelayObject(String data, long delay) {        this.data = data;        this.startTime = System.currentTimeMillis() + delay;    }    @Override    public long getDelay(TimeUnit unit) {        long diff = startTime - System.currentTimeMillis();        return unit.convert(diff, TimeUnit.MILLISECONDS);    }    @Override    public int compareTo(Delayed o) {        if (this.startTime < ((DelayObject) o).startTime) {            return -1;        }        if (this.startTime > ((DelayObject) o).startTime) {            return 1;        }        return 0;    }    @Override    public String toString() {        return "{" +                "data='" + data + '\'' +                ", startTime=" + startTime +                '}';    }}

Delayqueue到底是干嘛的?

An unbounded blocking queue of Delayed elements, in which an element can only be taken when its delay has expired. The head of the queue is that Delayed element whose delay expired furthest in the past.只有queue里面的元素过期了,才能够被取走,head中存放的是最先过期的元素。可以用在连接释放中,可以给新加入的连接一个过期时间,然后到达过期时间后,就会被取走。

Expiration occurs when an element’s getDelay(TimeUnit.NANOSECONDS) method returns a value less than or equal to zero.

3.LinkedBlockingQueue

4.PriorityBlockingQueue

All elements inserted into the PriorityBlockingQueue must implement the java.lang.Comparable interface. The elements thus order themselves according to whatever priority you decide in your Comparable implementation.

当然初始化时传入comparator也是可以的。

notice, that in case you obtain an Iterator from a PriorityBlockingQueue, the Iterator does not guarantee to iterate the elements in priority order. 我擦,iterator居然不是按优先级排序的。

5.SynchronousQueue

The SynchronousQueue is a queue that can only contain a single element internally. A thread inseting an element into the queue is blocked until another thread takes that element from the queue. Likewise, if a thread tries to take an element and no element is currently present, that thread is blocked until a thread insert an element into the queue.

简单说,一次只能存在一个元素在queue中。

2.BlockingDeque

它的用处在哪里呢?

deque就是“double ended queue”的简称。

A BlockingDeque could be used if threads are both producing and consuming elements of the same queue. 两边都可以生产或者消费的情况。It could also just be used if the producting thread needs to insert at both ends of the queue, and the consuming thread needs to remove from both ends of the queue.或者生产或者消费需要添加到两头的情况 Here is an illustration of that:
这里写图片描述

A BlockingDeque - threads can put and take from both ends of the deque.

方法如下:
这里写图片描述

BlockingDeque Extends BlockingQueue,原来是继承自queue啊。跟deque继承自queue一模一样。

实现了BlockingDeque的类:
1.LinkedBlockingDeque

选一个PriorityBlockingQueue来看看内部是怎么实现的。

参考这里写链接内容

这里写图片描述

PriorityBlockingQueue和PriorityQueue最大的不同有两点:1.线程安全,这是通过锁实现的,2.支持阻塞操作,特性来自于接口BlockingQueue。

优先级一样的元素出队列顺序是没有保证的。PriorityBlockingQueue不接受null值,线程安全

数据结构

PriorityBlockingQueue的核心数据结构是数组+堆。默认是最小堆,可通过改变比较器实现最大堆。

其内部有两把锁,lock是公用锁,用来保证增删改等操作的线程安全性allocationSpinLock是自旋锁,通过CAS更新,用于扩容

初始化

入队列和出队列

add和put都调用了offer方法,如下:

涉及扩容和从位置k将x元素上浮和下沉。

在最小堆中,添加元素后还要上浮,删除元素是将最小值与末尾值交换,删除末尾值,然后将最上层的值下沉。

//队尾插入元素,如果队列满无法插入,则抛异常public boolean add(E e) {    return offer(e);}public void put(E e) {    offer(e); // never need to block}public boolean offer(E e) {    if (e == null)        throw new NullPointerException();    final ReentrantLock lock = this.lock;    lock.lock();    int n, cap;    Object[] array;    //扩容,因为queue是    while ((n = size) >= (cap = (array = queue).length))        tryGrow(array, cap);    try {        Comparator<? super E> cmp = comparator;        if (cmp == null)            siftUpComparable(n, e, array);        else            siftUpUsingComparator(n, e, array, cmp);        size = n + 1;        //condition释放信号        notEmpty.signal();    } finally {        lock.unlock();    }    return true;}//从位置k将x元素上浮(与PriorityQueue完全一致)private static <T> void siftUpComparable(int k, T x, Object[] array) {    Comparable<? super T> key = (Comparable<? super T>) x;    while (k > 0) {        int parent = (k - 1) >>> 1;        Object e = array[parent];        if (key.compareTo((T) e) >= 0)            break;        array[k] = e;        k = parent;    }    array[k] = key;}
//从队头删除元素,如果队列空,则抛异常public E remove() {    E x = poll();    if (x != null)        return x;    else        throw new NoSuchElementException();}//从队头删除元素,如果队列空,则返回nullpublic E poll() {    final ReentrantLock lock = this.lock;    lock.lock();    try {        return dequeue();    } finally {        lock.unlock();    }}//上述两种删除方法都调用了dequeue方法private E dequeue() {    int n = size - 1;    if (n < 0)        return null;    else {        Object[] array = queue;        E result = (E) array[0];        E x = (E) array[n];        array[n] = null;        Comparator<? super E> cmp = comparator;        if (cmp == null)            siftDownComparable(0, x, array, n);        else            siftDownUsingComparator(0, x, array, n, cmp);        size = n;        return result;    }}//下沉同PriorityQueue完全一致private static <T> void siftDownComparable(int k, T x, Object[] array,                                           int n) {    if (n > 0) {        Comparable<? super T> key = (Comparable<? super T>)x;        int half = n >>> 1;           // loop while a non-leaf        while (k < half) {            int child = (k << 1) + 1; // assume left child is least            Object c = array[child];            int right = child + 1;            if (right < n &&                ((Comparable<? super T>) c).compareTo((T) array[right]) > 0)                c = array[child = right];            if (key.compareTo((T) c) <= 0)                break;            array[k] = c;            k = child;        }        array[k] = key;    }}//队列空会阻塞的删除public E take() throws InterruptedException {    final ReentrantLock lock = this.lock;    lock.lockInterruptibly();    E result;    try {        while ( (result = dequeue()) == null)            notEmpty.await();    } finally {        lock.unlock();    }    return result;}

因为该queue是无界的,意味着增加元素没有阻塞,而删除肯定有啦,size最小值为0嘛。

看Condition.awiat方法的解释:

Causes the current thread to wait until it is signalled orinterrupted

通过put操作的notEmpty.signal()和take操作的notEmpty.await(),控制了线程阻塞,而不用死循环遍历询问。

扩容

private void tryGrow(Object[] array, int oldCap) {    lock.unlock(); // must release and then re-acquire main lock    //我们可以假象,有很多线程都可以执行到这一步,这样既使在扩容的时候    //也可以保证并发。而自旋锁保证了只有一个线程会完成数组扩容。    Object[] newArray = null;    //自旋锁,CAS更新    if (allocationSpinLock == 0 &&        UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,                                 0, 1)) {        try {            int newCap = oldCap + ((oldCap < 64) ?                                   (oldCap + 2) : // grow faster if small                                   (oldCap >> 1));            if (newCap - MAX_ARRAY_SIZE > 0) {    // possible overflow                int minCap = oldCap + 1;                if (minCap < 0 || minCap > MAX_ARRAY_SIZE)                    throw new OutOfMemoryError();                newCap = MAX_ARRAY_SIZE;            }            //要非常注意queue == array的判断,当第一个线程完成扩容后,            //同批次的其他线程再进来,queue的地址已经变化,不会再分配新的            //空间            if (newCap > oldCap && queue == array)                newArray = new Object[newCap];        } finally {            allocationSpinLock = 0;        }    }    if (newArray == null) // back off if another thread is allocating        Thread.yield();    lock.lock();    //除第一个完成扩容的线程,其他线程的条件都是newArray==null.    if (newArray != null && queue == array) {        queue = newArray;        System.arraycopy(array, 0, newArray, 0, oldCap);    }}

说明:当数组空间满,进行扩容。如果容量小于64则两倍扩容,否则1.5倍扩容。这里有数组的复制动作。这里使用了CAS更新allocationSpinLock,确保只有一个线程会执行扩容操作。这里要弄明白为什么要先放弃锁在获取锁?这是因为有更好的并发特性,在扩容的过程中不影响其他线程并发执行take和poll操作,甚至是offer操作,通过自旋锁使线程安全的进行扩容工作。

查看队首

public E element() {    E x = peek();    if (x != null)        return x;    else        throw new NoSuchElementException();}public E peek() {    final ReentrantLock lock = this.lock;    lock.lock();    try {        return (size == 0) ? null : (E) queue[0];    } finally {        lock.unlock();    }}

为什么读要加锁呢?
读加锁是因为防止读到正在插入或删除而导致队列元素还在调整的错误数据。

总结

PriorityBlockingQueue是一种按照优先级顺序线程安全的阻塞的队列实现。遍历的结果也没有顺序,可以通过Arrays.sort(queue.toArray(),comparator)达到有序遍历的目的。可以在多线程环境下替代PriorityQueue。

3.ConcurrentMap

实现有:
1.ConcurrentHashMap

4.ConcurrentNavigableMap

The java.util.concurrent.ConcurrentNavigableMap class is a java.util.NavigableMap with support for concurrent access, and which has concurrent access enabled for its submaps. The “submaps” are the maps returned by various methods like headMap(), subMap() and tailMap().对于他的submap的访问是线程安全的。

几个方法:

1.headMap()

The headMap(T toKey) method returns a view of the map containing the keys which are strictly less than the given key.严格小于

If you make changes to the original map, these changes are reflected in the head map. 重点!如果更改原map,那么submap也会被改变。

ConcurrentNavigableMap map = new ConcurrentSkipListMap();map.put("1", "one");map.put("2", "two");map.put("3", "three");ConcurrentNavigableMap headMap = map.headMap("2");

将会返回“1”对应的结果。

2.tailMap()

The tailMap(T fromKey) method returns a view of the map containing the keys which are greater than or equal to the given fromKey. 大于等于

如果更改原map,那么submap也会被改变。

3.subMap()

The subMap() method returns a view of the original map which contains all keys from (including), to (excluding) two keys given as parameters to the method.

如果更改原map,那么submap也会被改变。

5.CountDownLatch

A java.util.concurrent.CountDownLatch is a concurrency construct that allows one or more threads to wait for a given set of operations to complete.

A CountDownLatch is initialized with a given count. This count is decremented by calls to the countDown() method. Threads waiting for this count to reach zero can call one of the await() methods. Calling await() blocks the thread until the count reaches zero.

内部会有一个count,每次调用countDown方法都会减小,到0时等待的线程就可以工作了。

例子:

CountDownLatch latch = new CountDownLatch(3);Waiter      waiter      = new Waiter(latch);Decrementer decrementer = new Decrementer(latch);new Thread(waiter)     .start();new Thread(decrementer).start();Thread.sleep(4000);

waiter:

public class Waiter implements Runnable{    CountDownLatch latch = null;    public Waiter(CountDownLatch latch) {        this.latch = latch;    }    public void run() {        try {        //阻塞直到count为0            latch.await();        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Waiter Released");    }}

Decrementer:

public class Decrementer implements Runnable {    CountDownLatch latch = null;    public Decrementer(CountDownLatch latch) {        this.latch = latch;    }    public void run() {        try {            Thread.sleep(1000);            this.latch.countDown();            Thread.sleep(1000);            this.latch.countDown();            Thread.sleep(1000);            this.latch.countDown();        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

6.CyclicBarrier

The java.util.concurrent.CyclicBarrier class is a synchronization mechanism that can synchronize threads progressing through some algorithm. In other words, it is a barrier that all threads must wait at, until all threads reach it, before any of the threads can continue. Here is a diagram illustrating that:
这里写图片描述

Two threads waiting for each other at CyclicBarriers.

主要步骤:

1.Creating a CyclicBarrier

When you create a CyclicBarrier you specify how many threads are to wait at it, before releasing them. Here is how you create a CyclicBarrier: CyclicBarrier barrier = new CyclicBarrier(2);

2.Waiting at a CyclicBarrier

3.CyclicBarrier Action

The CyclicBarrier supports a barrier action, which is a Runnable that is executed once the last thread arrives. You pass the Runnable barrier action to the CyclicBarrier in its constructor, like this:

就是大家都到了的情况下,执行什么动作。

Runnable      barrierAction = ... ;CyclicBarrier barrier       = new CyclicBarrier(2, barrierAction);

4.CyclicBarrier Example

例子:

Runnable barrier1Action = new Runnable() {    public void run() {        System.out.println("BarrierAction 1 executed ");    }};Runnable barrier2Action = new Runnable() {    public void run() {        System.out.println("BarrierAction 2 executed ");    }};CyclicBarrier barrier1 = new CyclicBarrier(2, barrier1Action);CyclicBarrier barrier2 = new CyclicBarrier(2, barrier2Action);CyclicBarrierRunnable barrierRunnable1 =        new CyclicBarrierRunnable(barrier1, barrier2);CyclicBarrierRunnable barrierRunnable2 =        new CyclicBarrierRunnable(barrier1, barrier2);new Thread(barrierRunnable1).start();new Thread(barrierRunnable2).start();

CyclicBarrierRunnable:

public class CyclicBarrierRunnable implements Runnable{    CyclicBarrier barrier1 = null;    CyclicBarrier barrier2 = null;    public CyclicBarrierRunnable(            CyclicBarrier barrier1,            CyclicBarrier barrier2) {        this.barrier1 = barrier1;        this.barrier2 = barrier2;    }    public void run() {        try {            Thread.sleep(1000);            System.out.println(Thread.currentThread().getName() +                                " waiting at barrier 1");            this.barrier1.await();            Thread.sleep(1000);            System.out.println(Thread.currentThread().getName() +                                " waiting at barrier 2");            this.barrier2.await();            System.out.println(Thread.currentThread().getName() +                                " done!");        } catch (InterruptedException e) {            e.printStackTrace();        } catch (BrokenBarrierException e) {            e.printStackTrace();        }    }}

输出:

Thread-0 waiting at barrier 1Thread-1 waiting at barrier 1BarrierAction 1 executedThread-1 waiting at barrier 2Thread-0 waiting at barrier 2BarrierAction 2 executedThread-0 done!Thread-1 done!

7.Exchanger

The java.util.concurrent.Exchanger class represents a kind of rendezvous point(约会地点) where two threads can exchange objects. Here is an illustration of this mechanism:
这里写图片描述

Two threads exchanging objects via an Exchanger.
例子:

Exchanger exchanger = new Exchanger();ExchangerRunnable exchangerRunnable1 =        new ExchangerRunnable(exchanger, "A");ExchangerRunnable exchangerRunnable2 =        new ExchangerRunnable(exchanger, "B");new Thread(exchangerRunnable1).start();new Thread(exchangerRunnable2).start();

ExchangerRunnable:

public class ExchangerRunnable implements Runnable{    Exchanger exchanger = null;    Object    object    = null;    public ExchangerRunnable(Exchanger exchanger, Object object) {        this.exchanger = exchanger;        this.object = object;    }    public void run() {        try {            Object previous = this.object;            this.object = this.exchanger.exchange(this.object);            System.out.println(                    Thread.currentThread().getName() +                    " exchanged " + previous + " for " + this.object            );        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

输出:

Thread-0 exchanged A for BThread-1 exchanged B for A

8.Semaphore

先看关于Semaphores的介绍。

Semaphores

A Semaphore is a thread synchronization construct that can be used either to send signals between threads to avoid missed signals, or to guard a critical section like you would with a lock.既可以用来同步,也可以用来锁。

1.Simple Semaphore

public class Semaphore {  private boolean signal = false;  public synchronized void take() {    this.signal = true;    this.notify();  }  public synchronized void release() throws InterruptedException{    while(!this.signal) wait();    this.signal = false;  }}

2.Using Semaphores for Signaling

最常见的还是线程之间的take和release:

Semaphore semaphore = new Semaphore();SendingThread sender = new SendingThread(semaphore);ReceivingThread receiver = new ReceivingThread(semaphore);receiver.start();sender.start();

SendingThread:

public class SendingThread {  Semaphore semaphore = null;  public SendingThread(Semaphore semaphore){    this.semaphore = semaphore;  }  public void run(){    while(true){      //do something, then signal      this.semaphore.take();    }  }}

RecevingThread:

public class RecevingThread {  Semaphore semaphore = null;  public ReceivingThread(Semaphore semaphore){    this.semaphore = semaphore;  }  public void run(){    while(true){      this.semaphore.release();      //receive signal, then do something...    }  }}

3.Counting Semaphore

不再只有true与false两种选择:

public class CountingSemaphore {  private int signals = 0;  public synchronized void take() {    this.signals++;    this.notify();  }  public synchronized void release() throws InterruptedException{    while(this.signals == 0) wait();    this.signals--;  }}

4.Bounded Semaphore

增加了界限:

public class BoundedSemaphore {  private int signals = 0;  private int bound   = 0;  public BoundedSemaphore(int upperBound){    this.bound = upperBound;  }  public synchronized void take() throws InterruptedException{    while(this.signals == bound) wait();    this.signals++;    this.notify();  }  public synchronized void release() throws InterruptedException{    while(this.signals == 0) wait();    this.signals--;    this.notify();  }}

5.Using Semaphores as Locks

作为锁来用

It is possible to use a bounded semaphore as a lock. To do so, 1.set the upper bound to 1, and have the 2.call to take() and release() guard the critical section. Here is an example:

BoundedSemaphore semaphore = new BoundedSemaphore(1);...//获取锁semaphore.take();try{  //critical section} finally {//释放锁  semaphore.release();}

You can also use a bounded semaphore to limit the number of threads allowed into a section of code.

如果将数量设置为5,5 threads would be allowed to enter the critical section at a time.

回到正题,java.util.concurrent.Semaphore是一个counting semaphore。意味着拥有两个主要方法:
1.acquire()
2.release()

Semaphore Usage

鉴于信号量的主要作用是:

1.To guard a critical section against entry by more than N threads at a time.
2.To send signals between two threads.

1.Guarding Critical Sections

Semaphore semaphore = new Semaphore(1);//critical sectionsemaphore.acquire();...semaphore.release();

2.Sending Signals Between Threads

If you use a semaphore to send signals between threads, then you would typically have one thread call the acquire() method, and the other thread to call the release() method.
传递信号的通常写法一般是一个线程acquire,另一个线程release。

Thus it is possible to coordinate threads. For instance, if acquire was called after Thread 1 had inserted an object in a shared list, and Thread 2 had called release() just before taking an object from that list, you had essentially created a blocking queue(仔细理解,实际上潜在地创建了一个blockingqueue). The number of permits available in the semaphore would correspond to the maximum number of elements the blocking queue could hold(permits的个数对应了blockingqueue的最大数量).

3.Fairness

No guarantees are made about fairness of the threads acquiring permits from the Semaphore. That is, there is no guarantee that the first thread to call acquire() is also the first thread to obtain a permit. If the first thread is blocked waiting for a permit, then a second thread checking for a permit just as a permit is released, may actually obtain the permit ahead of thread 1.
不保证先acquire的一定先拿到release后的permit。
当然,可以强行设置,但会牺牲性能。

Semaphore semaphore = new Semaphore(1, true);

9.ExecutorService

An ExecutorService is thus very similar to a thread pool (差不多). In fact, the implementation of ExecutorService present in the java.util.concurrent package is a thread pool implementation.

例子:

ExecutorService executorService = Executors.newFixedThreadPool(10);executorService.execute(new Runnable() {    public void run() {        System.out.println("Asynchronous task");    }});executorService.shutdown();

an anonymous implementation of the Runnable interface is passed to the execute() method. This causes the Runnable to be executed by one of the threads in the ExecutorService.

Task Delegation

这里写图片描述
A thread delegating a task to an ExecutorService for asynchronous execution.

实现该接口的类:
1.ThreadPoolExecutor
2.ScheduledThreadPoolExecutor

3种方式:

public class Test2 {    public static void main(String[] args){        //里面只有一个thread,所以前一个任务未执行完,后一个任务会阻塞        ExecutorService service1 = Executors.newSingleThreadExecutor();        //固定数量,到达最大量后也会阻塞        ExecutorService service2 = Executors.newFixedThreadPool(10);        //动态增加和减少pool中thread的数量        ExecutorService service3 = Executors.newScheduledThreadPool(10);    }}

几种常用方法:

1.execute(Runnable)

ExecutorService executorService = Executors.newSingleThreadExecutor();executorService.execute(new Runnable() {    public void run() {        System.out.println("Asynchronous task");    }});executorService.shutdown();

没有办法拿到执行后的返回结果

2.submit(Runnable)

//能够拿到结果Future future = executorService.submit(new Runnable() {    public void run() {        System.out.println("Asynchronous task");    }});future.get();  //returns null if the task has finished correctly.

3.submit(Callable)

Future future = executorService.submit(new Callable(){    public Object call() throws Exception {        System.out.println("Asynchronous Callable");        return "Callable Result";    }});System.out.println("future.get() = " + future.get());

4.invokeAny()

The invokeAny() method takes a collection of Callable objects, or subinterfaces of Callable. Invoking this method does not return a Future(不反悔Future对象), but returns the result of one of the Callable objects. You have no guarantee about which of the Callable’s results you get. Just one of the ones that finish.拿到的结果是最先完成的结果

If one of the tasks complete (or throws an exception), the rest of the Callable’s are cancelled. 一人完成,全部cancel

ExecutorService executorService = Executors.newSingleThreadExecutor();Set<Callable<String>> callables = new HashSet<Callable<String>>();callables.add(new Callable<String>() {    public String call() throws Exception {        return "Task 1";    }});callables.add(new Callable<String>() {    public String call() throws Exception {        return "Task 2";    }});callables.add(new Callable<String>() {    public String call() throws Exception {        return "Task 3";    }});String result = executorService.invokeAny(callables);System.out.println("result = " + result);executorService.shutdown();

5.invokeAll()

The invokeAll() method invokes all of the Callable objects you pass to it in the collection passed as parameter. The invokeAll() returns a list of Future objects via which you can obtain the results of the executions of each Callable.

ExecutorService executorService = Executors.newSingleThreadExecutor();Set<Callable<String>> callables = new HashSet<Callable<String>>();callables.add(new Callable<String>() {    public String call() throws Exception {        return "Task 1";    }});callables.add(new Callable<String>() {    public String call() throws Exception {        return "Task 2";    }});callables.add(new Callable<String>() {    public String call() throws Exception {        return "Task 3";    }});List<Future<String>> futures = executorService.invokeAll(callables);for(Future<String> future : futures){    System.out.println("future.get = " + future.get());}executorService.shutdown();

ThreadPoolExecutor

上面3种方式中的前两个其实内部就是返回的这个对象。

The number of threads in the pool is determined by these variables:
1.corePoolSize
2.maximumPoolSize

If less than corePoolSize threads are created in the the thread pool when a task is delegated to the thread pool, then a new thread is created, even if idle threads exist in the pool.意思是尽量有corePoolSize数量的线程在运行

If the internal queue of tasks is full, and corePoolSize threads or more are running, but less than maximumPoolSize threads are running, then a new thread is created to execute the task.针对于queue full的情况

10.ScheduledExecutorService

实现的类:
1.ScheduledThreadPoolExecutor(上面3种方式中的第三个其实内部就是返回的这个对象。)

主要方法:

1.schedule (Callable task, long delay, TimeUnit timeunit)
2.schedule (Runnable task, long delay, TimeUnit timeunit)
3.scheduleAtFixedRate (Runnable, long initialDelay, long period, TimeUnit timeunit)
4.scheduleWithFixedDelay (Runnable, long initialDelay, long period, TimeUnit timeunit)

11.Lock

先看看Locks的简介吧。

Locks in Java

A lock is a thread synchronization mechanism like synchronized blocks except locks can be more sophisticated than Java’s synchronized blocks.

1.A Simple Lock

public class Counter{  private int count = 0;  public int inc(){    synchronized(this){      return ++count;    }  }}

当然用Lock可以这样写:

public class Counter{  private Lock lock = new Lock();  private int count = 0;  public int inc(){    lock.lock();    int newCount = ++count;    lock.unlock();    return newCount;  }}

那么Lock内部又是怎么实现的呢?一种简单实现:

public class Lock{  private boolean isLocked = false;  public synchronized void lock()  throws InterruptedException{    while(isLocked){      wait();    }    isLocked = true;  }  public synchronized void unlock(){    isLocked = false;    notify();  }}

Notice the while(isLocked) loop, which is also called a “spin lock”.

2.Lock Reentrance

Synchronized blocks in Java are reentrant(就是能够在Java 同步方法中切换). This means, that if a Java thread enters a synchronized block of code, and thereby take the lock on the monitor object the block is synchronized on, the thread can enter other Java code blocks synchronized on the same monitor object. Here is an example:

public class Reentrant{  public synchronized outer(){    inner();  }  public synchronized inner(){    //do something  }}

The lock implementation shown earlier is not reentrant(但是上面这种关于Lock的实现不是可重入的哦!). If we rewrite the Reentrant class like below, the thread calling outer() will be blocked inside the lock.lock() in the inner() method. 下面将会出错:

public class Reentrant2{  Lock lock = new Lock();  public outer(){    lock.lock();    inner();    lock.unlock();  }  public synchronized inner(){    lock.lock();    //do something    lock.unlock();  }}

A thread calling outer() will first lock the Lock instance. Then it will call inner(). Inside the inner() method the thread will again try to lock the Lock instance. This will fail (meaning the thread will be blocked), since the Lock instance was locked already in the outer() method.两次lock操作将会block。

我们做一点改动,就可以实现可重入了,上述Lock的实现之所以不可重入,是因为lock方法内部不能够辨别调用lock的线程和锁住的线程是不是同一个线程。

public class Lock{  boolean isLocked = false;  Thread  lockedBy = null;  int     lockedCount = 0;  public synchronized void lock()  throws InterruptedException{    Thread callingThread = Thread.currentThread();    while(isLocked && lockedBy != callingThread){      wait();    }    isLocked = true;    lockedCount++;    lockedBy = callingThread;  }  public synchronized void unlock(){    if(Thread.curentThread() == this.lockedBy){      lockedCount--;      if(lockedCount == 0){        isLocked = false;        notify();      }    }  }  ...}

如果不加入lockedCount,那么lock多次,但是unlock一次就可以解开。

现在该Lock类就可以重入了。

3.Lock Fairness

好了,回到正题。
简单例子:

Lock lock = new ReentrantLock();lock.lock();//critical sectionlock.unlock();

实现Lock接口的类:
1.ReentrantLock

lock和synchronized的主要不同

1.A synchronized block makes no guarantees about the sequence in which threads waiting to entering it are granted access. synchronized块对等待的线程进入的顺序没有保证
2.You cannot pass any parameters to the entry of a synchronized block. Thus, having a timeout trying to get access to a synchronized block is not possible. lock可设置等待时间
3.The synchronized block must be fully contained within a single method. A Lock can have it’s calls to lock() and unlock() in separate methods. lock可在一个方法中lock,另一个方法中unlock。

主要方法:

1.lock()
2.lockInterruptibly()
3.tryLock()
4.tryLock(long timeout, TimeUnit timeUnit)
5.unlock()

The lock() method locks the Lock instance if possible. If the Lock instance is already locked, the thread calling lock() is blocked until the Lock is unlocked. 阻塞式的lock

The tryLock() method attempts to lock the Lock instance immediately. It returns true if the locking succeeds, false if Lock is already locked. This method never blocks. 起一个检查的作用,不会阻塞

The unlock() method unlocks the Lock instance. Typically, a Lock implementation will only allow the thread that has locked the Lock to call this method. Other threads calling this method may result in an unchecked exception (RuntimeException). 只允许锁住该lock的线程调用unlock,否则会异常。

12.ReadWriteLock

单独的一个类,没有继承自Lock接口,注意。

A java.util.concurrent.locks.ReadWriteLock is an advanced thread lock mechanism. It allows multiple threads to read a certain resource, but only one to write it, at a time.能够同时让很多线程读,但只能同时让一个线程写。

The idea is, that multiple threads can read from a shared resource without causing concurrency errors. The concurrency errors first occur when 1.reads and writes to a shared resource occur concurrently, or if 2.multiple writes take place concurrently.错误发生情况

我们先来了解Read / Write Locks in Java的并发理论。

它比前面的Locks要难一点。

Read / Write Lock Java Implementation

Read Access If no threads are writing, and no threads have requested write access.读情况
Write Access If no threads are reading or writing.写独占

如果不将写操作的优先级提高,很容易在读频繁的情况下,出现starvation的情况。这样写就要等到它们都完成了才能轮到它。

自己实现之:

public class ReadWriteLock{  private int readers       = 0;  private int writers       = 0;  private int writeRequests = 0;  public synchronized void lockRead() throws InterruptedException{   //writeRequests比readRequests优先级高    while(writers > 0 || writeRequests > 0){      wait();    }    readers++;  }  public synchronized void unlockRead(){    readers--;    notifyAll();  }  public synchronized void lockWrite() throws InterruptedException{    writeRequests++;    while(readers > 0 || writers > 0){      wait();    }    writeRequests--;    writers++;  }  public synchronized void unlockWrite() throws InterruptedException{    writers--;    notifyAll();  }}

注意这里要用notifyAll而不是notify,考虑下列一种情况:

Inside the ReadWriteLock there are threads waiting for read access, and threads waiting for write access(读写的waiting都有). If a thread awakened by notify() was a read access thread, it would be put back to waiting because there are threads waiting for write access(如果用notify,notify读线程,那么它还是会等待,因为有写请求). However, none of the threads awaiting write access are awakened, so nothing more happens. No threads gain neither read nor write access. By calling noftifyAll() all waiting threads are awakened and check if they can get the desired access. 通过notifyAll来让它们来一起竞争。

notifyAll还有一个好处:

If multiple threads are waiting for read access and none for write access, and unlockWrite() is called, all threads waiting for read access are granted read access at once - not one by one.

仔细看上面的代码,写的非常好!

Read / Write Lock Reentrance

上面的代码是不可重入的,考虑下面情况:

1.Thread 1 gets read access.

2.Thread 2 requests write access but is blocked because there is one reader.

3.Thread 1 re-requests read access (re-enters the lock), but is blocked because there is a write request

如何实现可重入?还是像之前一样吗?看下面:

Read Reentrance

一条规则:A thread is granted read reentrance if it can get read access (no writers or write requests), or if it already has read access (regardless of write requests).

改动如下:

public class ReadWriteLock{  private Map<Thread, Integer> readingThreads =      new HashMap<Thread, Integer>();  private int writers        = 0;  private int writeRequests  = 0;  public synchronized void lockRead() throws InterruptedException{    Thread callingThread = Thread.currentThread();    while(! canGrantReadAccess(callingThread)){      wait();                                                                       }    readingThreads.put(callingThread,       (getAccessCount(callingThread) + 1));  }  public synchronized void unlockRead(){    Thread callingThread = Thread.currentThread();    int accessCount = getAccessCount(callingThread);    if(accessCount == 1){ readingThreads.remove(callingThread); }    else { readingThreads.put(callingThread, (accessCount -1)); }    notifyAll();  }  private boolean canGrantReadAccess(Thread callingThread){    if(writers > 0)            return false;    if(isReader(callingThread) return true;    if(writeRequests > 0)      return false;    return true;  }  private int getReadAccessCount(Thread callingThread){    Integer accessCount = readingThreads.get(callingThread);    if(accessCount == null) return 0;    return accessCount.intValue();  }  private boolean isReader(Thread callingThread){    return readingThreads.get(callingThread) != null;  }}

Write Reentrance

规则一:Write reentrance is granted only if the thread has already write access.

改写后:

public class ReadWriteLock{    private Map<Thread, Integer> readingThreads =        new HashMap<Thread, Integer>();    private int writeAccesses    = 0;    private int writeRequests    = 0;    private Thread writingThread = null;  public synchronized void lockWrite() throws InterruptedException{    writeRequests++;    Thread callingThread = Thread.currentThread();    while(! canGrantWriteAccess(callingThread)){      wait();    }    writeRequests--;    writeAccesses++;    writingThread = callingThread;  }  public synchronized void unlockWrite() throws InterruptedException{    writeAccesses--;    if(writeAccesses == 0){      writingThread = null;    }    notifyAll();  }  private boolean canGrantWriteAccess(Thread callingThread){    if(hasReaders())             return false;    if(writingThread == null)    return true;    if(!isWriter(callingThread)) return false;    return true;  }  private boolean hasReaders(){    return readingThreads.size() > 0;  }  private boolean isWriter(Thread callingThread){    return writingThread == callingThread;  }}

Read to Write Reentrance

Sometimes it is necessary for a thread that have read access to also obtain write access.For this to be allowed the thread must be the only reader.

只改写一下writeLock的重入条件即可:

public class ReadWriteLock{    private Map<Thread, Integer> readingThreads =        new HashMap<Thread, Integer>();    private int writeAccesses    = 0;    private int writeRequests    = 0;    private Thread writingThread = null;  public synchronized void lockWrite() throws InterruptedException{    writeRequests++;    Thread callingThread = Thread.currentThread();    while(! canGrantWriteAccess(callingThread)){      wait();    }    writeRequests--;    writeAccesses++;    writingThread = callingThread;  }  public synchronized void unlockWrite() throws InterruptedException{    writeAccesses--;    if(writeAccesses == 0){      writingThread = null;    }    notifyAll();  }  private boolean canGrantWriteAccess(Thread callingThread){    //添加了是否是唯一的reader    if(isOnlyReader(callingThread))    return true;    if(hasReaders())                   return false;    if(writingThread == null)          return true;    if(!isWriter(callingThread))       return false;    return true;  }  private boolean hasReaders(){    return readingThreads.size() > 0;  }  private boolean isWriter(Thread callingThread){    return writingThread == callingThread;  }  private boolean isOnlyReader(Thread thread){      return readers == 1 && readingThreads.get(callingThread) != null;      }}

Write to Read Reentrance

Sometimes a thread that has write access needs read access too. A writer should always be granted read access if requested.

只需要改动 canGrantReadAccess方法:

public class ReadWriteLock{    private boolean canGrantReadAccess(Thread callingThread){      if(isWriter(callingThread)) return true;      if(writingThread != null)   return false;      if(isReader(callingThread)  return true;      if(writeRequests > 0)       return false;      return true;    }}

Fully Reentrant ReadWriteLock

public class ReadWriteLock{  private Map<Thread, Integer> readingThreads =       new HashMap<Thread, Integer>();   private int writeAccesses    = 0;   private int writeRequests    = 0;   private Thread writingThread = null;  public synchronized void lockRead() throws InterruptedException{    Thread callingThread = Thread.currentThread();    while(! canGrantReadAccess(callingThread)){      wait();    }    readingThreads.put(callingThread,     (getReadAccessCount(callingThread) + 1));  }  private boolean canGrantReadAccess(Thread callingThread){    if( isWriter(callingThread) ) return true;    if( hasWriter()             ) return false;    if( isReader(callingThread) ) return true;    if( hasWriteRequests()      ) return false;    return true;  }  public synchronized void unlockRead(){    Thread callingThread = Thread.currentThread();    if(!isReader(callingThread)){      throw new IllegalMonitorStateException("Calling Thread does not" +        " hold a read lock on this ReadWriteLock");    }    int accessCount = getReadAccessCount(callingThread);    if(accessCount == 1){ readingThreads.remove(callingThread); }    else { readingThreads.put(callingThread, (accessCount -1)); }    notifyAll();  }  public synchronized void lockWrite() throws InterruptedException{    writeRequests++;    Thread callingThread = Thread.currentThread();    while(! canGrantWriteAccess(callingThread)){      wait();    }    writeRequests--;    writeAccesses++;    writingThread = callingThread;  }  public synchronized void unlockWrite() throws InterruptedException{    if(!isWriter(Thread.currentThread()){      throw new IllegalMonitorStateException("Calling Thread does not" +        " hold the write lock on this ReadWriteLock");    }    writeAccesses--;    if(writeAccesses == 0){      writingThread = null;    }    notifyAll();  }  private boolean canGrantWriteAccess(Thread callingThread){    if(isOnlyReader(callingThread))    return true;    if(hasReaders())                   return false;    if(writingThread == null)          return true;    if(!isWriter(callingThread))       return false;    return true;  }  private int getReadAccessCount(Thread callingThread){    Integer accessCount = readingThreads.get(callingThread);    if(accessCount == null) return 0;    return accessCount.intValue();  }  private boolean hasReaders(){    return readingThreads.size() > 0;  }  private boolean isReader(Thread callingThread){    return readingThreads.get(callingThread) != null;  }  private boolean isOnlyReader(Thread callingThread){    return readingThreads.size() == 1 &&           readingThreads.get(callingThread) != null;  }  private boolean hasWriter(){    return writingThread != null;  }  private boolean isWriter(Thread callingThread){    return writingThread == callingThread;  }  private boolean hasWriteRequests(){      return this.writeRequests > 0;  }}

好了,我们回归正题java built-in ReadWriteLock:

这个类实现的原则跟上面是一样的:
1.Read Lock:If no threads have locked the ReadWriteLock for writing,and no thread have requested a write lock (but not yet obtained it).Thus, multiple threads can lock the lock for reading.
2.Write Lock:If no threads are reading or writing.
Thus, only one thread at a time can lock the lock for writing.

该接口的实现:
1.ReentrantReadWriteLock

例子:

ReadWriteLock readWriteLock = new ReentrantReadWriteLock();readWriteLock.readLock().lock();    // multiple readers can enter this section    // if not locked for writing, and not writers waiting    // to lock for writing.readWriteLock.readLock().unlock();readWriteLock.writeLock().lock();    // only one writer can enter this section,    // and only if no threads are currently reading.readWriteLock.writeLock().unlock();

13.一些原子类

比如:

1.AtomicBoolean
2.AtomicInteger
3.AtomicLong
4.AtomicReference
5.AtomicStampedReference
6.AtomicIntegerArray
7.AtomicLongArray
8.AtomicReferenceArray

关于这些原子类的具体原理,参考Compare and Swap:

Compare and swap is a technique used when designing concurrent algorithms.

What Situations Compare And Swap is Intended to Support

一般我们写程序会用到”check then act” pattern.比如:

class MyLock {    private boolean locked = false;    public boolean lock() {        if(!locked) {            locked = true;            return true;        }        return false;    }}

但是上述代码在多线程中肯定会出错。为了能在多线程中正确,”check then act” operations必须是原子操作。

Compare And Swap As Atomic Operation

Modern CPUs have built-in support for atomic compare and swap operations.

通过原子类来实现锁:

public static class MyLock {    private AtomicBoolean locked = new AtomicBoolean(false);    public boolean lock() {        return locked.compareAndSet(false, true);    }}

推荐使用这样的原子方法是因为CPU自带这些原子操作,效率高一点

0 0
原创粉丝点击