Java多线程总结2

同步装置

        JDK5.0后提供了一个新的多线程并发控制的装置/工具,它允许一组线程互相进行协调运行。

先引用IBM网站的一个表格:

Semaphore

一个经典的并发工具(计数信号量)。通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。

CountDownLatch

同步辅助类,在完成一组正在其他线程中执行的操作之前,允许一个或多个线程一直等待。用于在保持给定数目的信号、事件或条件前阻塞执行。

CyclicBarrier

同步辅助类,是一个可重置的多路同步点,在某些并行编程风格中很有用。

Exchanger

允许两个线程在 collection 点交换对象,它在多流水线设计中是有用的。

 

1.信号量

         Semaphore 信号量通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。Semaphore可以看成是个通行证,线程访问资源池中的资源时必须先取得通行证。如果线程没有取得通行证就会被阻塞进入等待状态。

        通常在取得通行证前会调用acquire()阻塞其他的申请者,然后再获取该许可。最后调用 release() 释放一个通行证,从而也可能释放了一个正在阻塞的申请者。

        有两个主要的方法:

         * public void acquire();

         * public void release();

        Semaphore提供的通行证数量和资源池的大小一致。

        注意的是:信号量仅仅是对池资源进行监控,但不保证线程的安全,因此,在使用时候,应该自己控制线程的安全访问池资源。

网上拷贝的一个示例:

public class Pool {

    ArrayList pool = null;

    Semaphore pass = null;

    public Pool(int size){

        //初始化资源池

        pool = new ArrayList();

        for(int i=0; i

            pool.add("Resource "+i);

        }

        //Semaphore的大小和资源池的大小一致

        pass = new Semaphore(size);

    }

    public String get() throws InterruptedException{

        //获取通行证,只有得到通行证后才能得到资源

        pass.acquire();

        return getResource();

    }

    public void put(String resource){

        //归还通行证,并归还资源

        pass.release();

        releaseResource(resource);

    }

    private synchronized String getResource() {

        String result = pool.get(0);

        pool.remove(0);

        System.out.println("Give out "+result);

        return result;

    }

    private synchronized void releaseResource(String resource) {

        System.out.println("return "+resource);

        pool.add(resource);

    }

}

 

2.同步计数器

         同步计数器CountDownLatch的实现是通过一个计数器来控制线程对资源的访问,当计数器不为零时处于等待中。它可用于一个简单的开/关锁存器或入口,也可用于分解计算一个复杂问题。它主要有两个方法:

public void countDown()

        递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。如果当前计数等于零,则不发生任何操作。

public boolean await(long timeout, TimeUnit unit) throws InterruptedException

        使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回 true 值。

 

引用一个例子:

class Driver2 {

    void main() throws InterruptedException {

        int N = 10;

        CountDownLatch doneSignal = new CountDownLatch(N);

        Executor e = Executors.newSingleThreadExecutor();

 

        for (int i = 0; i < N; ++i) // create and start threads

            e.execute(new WorkerRunnable(doneSignal, i));

 

        doneSignal.await();           // 等待所有任务结束(即计数器为0)

       ………………………..     

    }

 

    class WorkerRunnable implements Runnable {

        private final CountDownLatch doneSignal;

        private final int i;

 

        WorkerRunnable(CountDownLatch doneSignal, int i) {

            this.doneSignal = doneSignal;

            this.i = i;

        }

 

        public void run() {

            try {

                doWork(i);

                doneSignal.countDown();  //计数器递减

            } catch (Exception ex) {

            } // return;

        }

 

        void doWork(int num) {

            System.out.println(num);

        }

    }

}

 

3.障碍器

        障碍器CyclicBarrier允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。

        CyclicBarrier与CountDownLatchu有些相似,但也存在一些区别:

        CountDownLatchu是等待一组线程执行完以后,再执行等待的线程,等待线程可以是一个或几个线程;通过计数器来通信。CyclicBarrier是等待多个子线程都执行到某一点后,再继续执行CyclicBarrier中的任务

      有一个主要方法:

        public int await();

引用一个网上的例子:两个线程分别在一个数组里放一个数,当这两个线程都结束后,主线程算出数组里的数的和

public class Test {

        public static void main(String[] args) {

                //创建障碍器,并设置MainTask为所有定数量的线程都达到障碍点时候所要执行的任务(Runnable)

                CyclicBarrier cb = new CyclicBarrier(7, new MainTask());

                new SubTask("A", cb).start();

                new SubTask("B", cb).start();

                new SubTask("C", cb).start();

                new SubTask("D", cb).start();

                new SubTask("E", cb).start();

                new SubTask("F", cb).start();

                new SubTask("G", cb).start();

        }

}

 

/**

* 主任务

*/

class MainTask implements Runnable {

        public void run() {

                System.out.println(">>>>主任务执行了!<<<<");

        }

}

 

/**

* 子任务

*/

class SubTask extends Thread {

        private String name;

        private CyclicBarrier cb;

 

        SubTask(String name, CyclicBarrier cb) {

                this.name = name;

                this.cb = cb;

        }

 

        public void run() {

                System.out.println("[子任务" + name + "]开始执行了!");

                for (int i = 0; i < 999999; i++) ;    //模拟耗时的任务

                System.out.println("[子任务" + name + "]开始执行完成了,并通知障碍器已经完成!");

                try {

                        //通知障碍器已经完成

                        cb.await();

                } catch (InterruptedException e) {

                        e.printStackTrace();

                } catch (BrokenBarrierException e) {

                        e.printStackTrace();

                }

        }

}

执行结果:

[子任务E]开始执行了!

[子任务E]开始执行完成了,并通知障碍器已经完成!

[子任务F]开始执行了!

[子任务G]开始执行了!

[子任务F]开始执行完成了,并通知障碍器已经完成!

[子任务G]开始执行完成了,并通知障碍器已经完成!

[子任务C]开始执行了!

[子任务B]开始执行了!

[子任务C]开始执行完成了,并通知障碍器已经完成!

[子任务D]开始执行了!

[子任务A]开始执行了!

[子任务D]开始执行完成了,并通知障碍器已经完成!

[子任务B]开始执行完成了,并通知障碍器已经完成!

[子任务A]开始执行完成了,并通知障碍器已经完成!

>>>>主任务执行了!<<<<

 

4.连接器

        连接器Exchanger类,可以用来完成线程间的数据交换。当两个线程通过Exchanger交换了对象,这个交换对于两个线程来说都是安全的。Exchanger仅可以在两个线程之间交换数据。

        Exchanger 可能被视为 SynchronousQueue 的双向形式。Exchanger 可能在应用程序(比如遗传算法和管道设计)中很有用。

引个网上的例子:

class FillAndEmpty {

    //初始化一个Exchanger,并规定可交换的信息类型是DataBuffer

    Exchanger<DataBuffer> exchanger = new Exchanger<DataBuffer>();

    DataBuffer initialEmptyBuffer = new DataBufferByte(10, 10);  //初始化一个empty Buffer

    DataBuffer initialFullBuffer = new DataBufferByte(10, 10); //初始化一个full Buffer

 

    class FillingLoop implements Runnable {

        public void run() {

            DataBuffer currentBuffer = initialEmptyBuffer;

            try {

                while (currentBuffer != null) {

                    addToBuffer(currentBuffer);  //往empty Buffer添加数据

                    if (currentBuffer.isFull())

                        currentBuffer = exchanger.exchange(currentBuffer); //和full Buffer进行交换

                }

            } catch (InterruptedException ex) {

                //...handle...

                ex.printStackTrace();

            }

        }

 

    }

 

    class EmptyingLoop implements Runnable {

        public void run() {

            DataBuffer currentBuffer = initialFullBuffer;

            try {

                while (currentBuffer != null) {

                    takeFromBuffer(currentBuffer);  //从full Buffer中取数据

                    if (currentBuffer.isEmpty())

                        currentBuffer = exchanger.exchange(currentBuffer);  //和empty Buffer进行交换

                }

            } catch (InterruptedException ex) {

                //...handle...

                ex.printStackTrace();

            }

        }

    }

 

    void start() {

        new Thread(new FillingLoop()).start();

        new Thread(new EmptyingLoop()).start();

    }

 

    public void addToBuffer(DataBuffer data) {

       ….

    }

 

    public void takeFromBuffer() {

       ….

    }

}

 

        当线程A调用Exchange对象的exchange()方法后,他会陷入阻塞状态,直到线程B也调用了exchange()方法,然后以线程安全的方式交换数据,之后线程A和B继续运行

 

原子变量与无阻塞算法

        关于原子变量,可以参考IBM网站文章:

Java理论与实践:流行的原子  https://www.ibm.com/developerworks/cn/java/j-jtp11234/

        原子变量是基于现代处理器(CPU)的硬件支持把两步操作合为一步,避免了不必要的锁定,提高了程序的运行效率。它们实现了CAS(compare-and-set)和 “check-and-act”动作。

        Java中的常见的原子变量类(automic variable classes)主要有AtomicBoolean, AtomicInteger, AotmicIntegerArray, AtomicLong, AtomicLongArray, AtomicReference ……。

        这些原子量级的变量主要提供两个方法:

    *   compareAndSet(expectedValue, newValue): 比较当前的值是否等于expectedValue,若等于把当前值改成newValue,并返回true。若不等,返回false。

    *   getAndSet(newValue): 把当前值改为newValue,并返回改变前的值。

 

        Java中的原子变量更多地是被用在“非阻塞算法”或“lock-free算法”中。关于非阻塞算法可以参考IBM网站文章:Java 理论与实践: 非阻塞算法简介  http://www.ibm.com/developerworks/cn/java/j-jtp04186/

 

 

 

阻塞队列

        java阻塞队列应用于生产者消费者模式、消息传递、并行任务执行和相关并发设计的大多数常见使用上下文。

        BlockingQueue是一种特殊的Queue,若BlockingQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态直到BlocingkQueue进了新货才会被唤醒。同样,如果BlockingQueue是满的任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有新的空间才会被唤醒继续操作。

ConcurrentLinkedQueue

一个基于链接节点的高效的、可伸缩的、线程安全的非阻塞 FIFO 队列, 此实现采用了有效的“无等待 (wait-free)”算法

LinkedBlockingQueue

一个基于已链接节点的、范围任意的 blocking queue

ArrayBlockingQueue

一个由数组支持的有界阻塞队列。这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。

SynchronousQueue

一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。适合于传递性设计,在这种设计中,在一个线程中运行的对象要将某些信息、事件或任务传递给在另一个线程中运行的对象,它就必须与该对象同步。

PriorityBlockingQueue

一个无界阻塞队列,类似于LinkedBlockingQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数所带的Comparator决定的顺序。

DelayQueue

一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部 是延迟期满后保存时间最长的 Delayed 元素。

LinkedBlockingDeque

一个基于已链接节点的、任选范围的阻塞双端队列。是BlockingDeque接口的一个基本实现,以支持 FIFO 和 LIFO(基于堆栈)操作。

 

 

并发集合

        在Java的聚集框架里可以调用Collections.synchronizeCollection(aCollection)将普通聚集改变成同步聚集,使之可用于多线程的环境下。 但同步聚集在一个时刻只允许一个线程访问它,其它想同时访问它的线程会被阻断,导致程序运行效率不高。Java 5.0里提供了几个共点聚集类,它们把以前需要几步才能完成的操作合成一个原子量级的操作,这样就可让多个线程同时对聚集进行操作,避免了锁定,从而提高了程序的运行效率。JDK 1.5提供下面一些集合实现,它们是被设计为用于多线程环境的:

ConcurrentHashMap

为检索和更新(update)可调整的预期的并发性提供了完整的线程安全的(thread-safe)并发性支持

CopyOnWriteArrayList

一组线程安全的变量集合

CopyOnWriteArraySet

一个线程安全的数组列表(ArrayList)变量

ConcurrentSkipListMap

对应TreeMap

ConcurrentSkipListSet

 

        在修改原始的数组或集合之前,它们中的每一个都会把下层的数组或集合复制一份。其结果是,读取的速度很快,而更新的速度很慢。

        并发集合类为Iterator(迭代子)提供快照式的数据(即使下层数据发生了改变,在Iterator中也不会反映出来)。

 

 

         IBM Java多线程与并发编程专题  http://www.ibm.com/developerworks/cn/java/j-concurrent/

posted @ 2013-09-30 21:35  Jevo  阅读(223)  评论(0)    收藏  举报