在实际并发编程中,可以利用synchronized来同步线程对于共享对象的访问,用户需要显示的定义synchronized代码块或者方法。为了加快开发,可以使用Java平台一些并发基础模块来开发。

注:关于容器类中的常见并发容器和同步容器的类图,详见另一篇文章《Java集合框架图》

一 同步容器类

同步容器类主要包括Vector和Hashtable,都是通过Collections.synchronizedXxx工厂方法创建的。这些类都是线程安全的。一些修改删除添加方法都利用了synchronized来进行同步。将这些容器类的状态封装,然后对于其所有的public方法都进行同步,这样就保证了每次只能允许一个线程访问这些容器类。对于每个public方法都保证了原子性操作。

当对于同步容器类进行多个synchronized方法组成的复合操作,为了保证正确性,必须要确保这些复合操作为一个原子操作。

对于这些同步容器里进行迭代处理中,需要注意多个线程访问的问题。当利用for来迭代处理,容器的大小可能会在迭代中修改,则就会抛出ArrayIndexOutOfBoundsException异常。而在利用迭代器foreach运用interator过程中,可能会抛出ConcurrentModificationException。所以在对于这种同步容器类迭代过程中一定要进行加锁处理。

在对同步容器类调用toString,hashCode,equals,containsAll,removeAll,retainAll以及容器作为参数传递都会对容器进行迭代操作。

二 并发容器类

由于同步容器类,只允许同时一个外部线程访问该集合,则降低了它的并发能力。就引出了并发容器类。并发容器类主要就是针对多线程访问设计的。

常见的有ConcurrentHashMap以代替同步且基于散列的Map。CopyOnWriteArrayList用于以迭代为主要操作来代替List。Queue和BlockingQueue,其中BlockingQueue尤其在生产者与消费者模式中作为缓冲得到了很大的运用。

ConcurrentHashMap,这是一种并发性容器类,这里并没有针对每一个方法使用同一个锁进行同步,而是在内部用一种分段锁来实现并发性操作。可以允许同时多个读线程操作,允许同时多个写线程操作,多个读线程与写线程同时操作。迭代过程不需要加锁,但是在迭代过程可能容量大小会发生变化。这种最重要的是用于针对get,put,containsKey和remove等操作频繁的多线程中。

它增加了几个复合型的原子性操作,从而可以直接使用不用加锁。

CopyOnWriteArrayList,这是一种并发性容器类,迭代过程不需要加锁。它是“写入时复制”的容器,当在每次修改时候,都会复制底层数组,创建并发布一个新的容器副本。这种最重要用于需要频繁的进行迭代操作,且迭代操作远远大于修改操作的时候,多个线程可以同时对这个容器进行迭代操作,而不会彼此干扰且与修改容器的线程不相干。

Queue,是一种用来保存临时的数据,包括ConcurrentLinkedQueue,Queue上的操作不会阻塞,如果队列为空,会立即返回。

BlockingQueue,它主要用于并发操作方面。这是一种基于阻塞的队列,当从队列中获取元素时候,如果队列为空则等待,当向队列插入元素时候,如果队列满了则等待。它利用put和take来获取对象,这种操作都是在内部加锁机制实现的。常见的几个插入与移除对象操作:

put,将object放入队列中,如果无空间,则一直等待到有空间,会阻塞调用该方法的线程。

offer,如果可以放入object,则返回true,否则false。一会阻塞调用该方法的线程。

poll,获取首位对象,若立即得不到,可以等待一定时间后再返回值或者null。

take,获取首位对象,若队列为空,则一直等待有元素添加,会阻塞调用该方法的线程。

常见的子类包括ArrayBlockingQueue和LinkedBlockingQueue,分别用于替代LinkedList和ArrayList,提高并发性能。SynchronousQueue,这是一种同步移除与添加的队列,每个插入操作必须等待另一个线程的对应移除操作。同步队列没有任何内部容量,甚至连一个队列的容量都没有。

这种最重要用于常见的生产者与消费者模式中,作为缓冲用。在我的一篇Java多线程设计模式(2)生产者与消费者模式》利用自定义数组大小来实现生产者与消费者模式,在ArrayBlockingQueue中其内部大体的实现机制就是和那一样的。

利用ArrayBlockingQueue可以很容易实现生产者与消费者模式,不用进行额外的同步处理,因为它内部都已经实现了同步处理,并且进行了并发性能的提高。代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package whut.producer;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
//利用BlockingQueue实现消费者与生产者
class Producer implements Runnable
{
    private final BlockingQueue
queue;
    public Producer(BlockingQueue
q) {
        queue
= q;
    }
    public void run()
{
        try {
            int i=0;
            while (i<10)
            {
                queue.put(i);
                i++;
            }
        catch (InterruptedException
ex) {
        }
    }
    private Object
produce()
    {
        Random
rd=
new Random();
        int res=rd.nextInt(10);
        return res;
    }
}
class Consumer extends Thread 
{
    private final BlockingQueue
queue;
    public Consumer(String
name,BlockingQueue q) {
        super(name);
        queue
= q;
    }
    public void run()
{
        try {
            while (true)
{
                consume(queue.take());
            }
        catch (InterruptedException
ex) {
        }
    }
    private void consume(Object
x) {
        System.out.println(Thread.currentThread().getName()+"
= "
+x);
    }
}
class BlockingQueueDemo
{
    public static void main(String[]
args) {
        BlockingQueue<Integer>
q = 
new ArrayBlockingQueue<Integer>(10);
        Producer
p = 
new Producer(q);
        Consumer
c1 = 
new Consumer("Apple",q);
        Consumer
c2 = 
new Consumer("Hawk",q);
        new Thread(p).start();
        c1.start();
        c2.start();
    }
}

三 同步工具类

常见使用的同步工具类有信号量(Semaphore),栅栏(Barrier),闭锁(Latch).

闭锁(Latch),可以延迟线程进度直到闭锁到达最终状态。闭锁主要用来确保某些活动直到其他活动都执行完毕后才能继续执行,这里执行的形如递减操作,初始化等待活动的数目,当递减到0,则该活动才得以继续执行。

CountDownLatch是一种灵活的闭锁实现,有个状态为计数器,利用构造器设置。通过countDown方法递减计数器,表示一个活动已经执行完毕,而await方法等待计数器为0,如果不为0则阻塞等待,或者线程被中断,或者等待超时。这两个方法必须成对使用。

CountDownLatch的简单实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package whut.concurrentmodel;
import java.util.concurrent.CountDownLatch;
//利用闭锁来实现,闭锁可以用于线程之间的协作,
//即一个线程必须等待其余所有活动完后执行
public class CountDownLatchClient
{
    public void timeTasks(int nThreads, final Runnable
task)
            throws InterruptedException
{
        //
工作线程等待其他活动执行完毕的{闭锁}
        final CountDownLatch
startGate = 
new CountDownLatch(1);
        //
主线程等待所有工作线程执行完毕的{闭锁}
        final CountDownLatch
endGate = 
new CountDownLatch(nThreads);
        for (int i
0;
i < nThreads; i++) {
            Thread
t = 
new Thread()
{
                public void run()
{
                    try {
                        //
工作线程先等待其他活动执行完毕
                        startGate.await();
                        try {
                            task.run();
                        finally {
                            System.out.println(Thread.currentThread().getName()
                                    "
work finished..."
);
                            //
工作线程执行完毕后,递减闭锁值
                            endGate.countDown();
                        }
                    catch (InterruptedException
ie) {
                    }
                }
            };
            t.start();
        }
        //
这里具体是任务,不过直接模拟了活动执行完毕了
        startGate.countDown();
        //
主线程先等待工作线程执行到0
        endGate.await();
        System.out.println("All
workthread have finished..."
);
    }
    //
主线程
    public static void main(String[]
args) {
        //
TODO Auto-generated method stub
        Runnable
task = 
new Runnable()
{
            public void run()
{
                int i
0;
                while (i
100)
{
                    i++;
                }
            }
        };
        CountDownLatchClient
cdl = 
new CountDownLatchClient();
        try {
            cdl.timeTasks(10,
task);
        catch (InterruptedException
e) {
        }
        System.out.println("do
another thing ...."
);
    }
}

栅栏(Barrier),可以阻塞一组线程直到某个事件发生。它和闭锁一样。闭锁用于等待其他活动,栅栏用于等待其他线程。这里执行的形如递增操作,初始化等待线程的数目,当递增到目标线程数目时候,则该线程才得以继续执行。

CyclicBarrier,是一种常用的栅栏类,可以使得一定数量的参与者反复在栅栏处汇集。通常用于并行迭代计算中。这种将一个复杂的大问题,分解成多个子问题,为每一个子问题创建线程来处理。当线程到达栅栏位置时候将调用await,这个将一直阻塞,直到所有的线程都到达了栅栏位置。在使用它的时候,可以传递一个Runnable,用于当成功通过栅栏后执行的操作或任务。一般每个线程执行的时候先利用barrier.hasCoverged判断,然后执行任务,最后利用barrier.await(),来阻塞所有都到达栅栏位置。

一般使用代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package whut.concurrentmodel;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
//栅栏实例
public class BarrierClient
{
    public static void main(String[]
args) {
        //
TODO Auto-generated method stub
       BarrierClient
bc=
new BarrierClient();
       //获取可以同时并行处理的数目
       int count=Runtime.getRuntime().availableProcessors();
       CyclicBarrier
barrier=
new CyclicBarrier(count);
       for(int i=0;i<count;i++)
       {
           Worker
work=bc.
new Worker(barrier);
           new Thread(work).start();
       }
    }
                                                                                                                                                                                                                                                                                                      
    private class Worker implements Runnable
    {
        private final CyclicBarrier
bar;
        public Worker(CyclicBarrier
bar)
        {
            this.bar=bar;
        }
        public void run()
        {
            //dosome
work
            //...........
            try{
                bar.await();
            }catch(InterruptedException
e)
            {
            }catch(BrokenBarrierException
e)
            {
                                                                                                                                                                                                                                                                                                                  
            }
        }
    }
}

栅栏与闭锁,可以使得任务线程同时开始同时结束,利用栅栏也可以实现与闭锁一样的效果。在并发测试中很有用

信号量(Semaphore),主要是用来控制同时访问某个特定资源的操作数目,或者执行某个指定操作的数目。可以用来实现资源池,对容器施加边界。Semaphore管理者一组虚拟许可,许可的数目由构造器指定。执行操作必须先获取许可,使用完毕后释放许可。如果没有许可,则acquire一直阻塞直到有许可。release方法将返回一个许可信号量。当初始值为1,则可以用作互斥体。

Semaphore一般用于实现资源池以及设置任何容器为有界阻塞容器。注意这种方式与另一篇Java多线程设计模式(3)读写锁模式》的比较

代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package whut.concurrentmodel;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Semaphore;
//利用Semaphore来实现集合的边界处理
public class SemaphoreTest<T>
{
    private final Set<T>
set;
    private final Semaphore
sem;
                                                                                                                                                                                                                    
    public SemaphoreTest(int bound)
    {
        this.set=Collections.synchronizedSet(new HashSet<T>());//同步处理
        //设置Semaphore的大小,用于设置set的边界,控制同时多少个访问
        sem=new Semaphore(bound);
    }
    //add操作成功则会返回true,否则返回false
    public boolean add(T
o)
throws InterruptedException
    {
        sem.acquire();//获取信号量
        boolean wasAdded=false;
        try{
            wasAdded=set.add(o);//同步访问这些方法
            return wasAdded;
        }finally{
            if(!wasAdded)
                sem.release();//释放信号量,如果没有添加成功
        }
    }
                                                                                                                                                                                                                    
    public boolean remove(Object
o)
    {
        boolean wasRemoved=set.remove(o);//成功移除返回true
        if(wasRemoved)
            sem.release();//释放信号量
        return wasRemoved;
    }
}

FutureTask,这是一种可以获取长时间执行任务的快照,可以执行任何返回该对象,从而继续做其他工作,当需要获取任务的执行结果的时候,再利用Future.get获取任务的处理结果。如果任务已经完成,则get会立即返回,否则则会阻塞直到任务进入完成状态,然后返回结果或者抛出异常。FutureTask将计算结果从执行计算的线程传递到了获取这个结果的线程。

FutureTask可以包装Callable和Runnable作为其构造器参数,同时FutureTask是实现了Runnable接口,故可以作为Executor.execute的参数传递。

使用FutureTask的方式有两种,一种是将其作为Thread的构造器参数或者execute()的参数在新的线程中执行。一种是直接运行其run方法,在主线程中串行运行。

利用FutureTask其实就是和另一篇《Java多线程设计模式(5)Future模式》的机制一样的,只不过在内部已经封装好了。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package whut.future;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//利用FutureTask来实现future设计模式
public class FutureTaskDemo
{
    public static void main(String[]
args) {
        //
TODO Auto-generated method stub
        MyCallale
mc = 
new MyCallale();
        FutureTask<String>
myfuture = 
new FutureTask<String>(mc);
        new Thread(myfuture).start();
        System.out.println("operate
other thing"
);
        try {
            System.out.println("data1=" +
myfuture.get());
        catch (InterruptedException
e) {
        catch (ExecutionException
e) {
        }
    }
}
class MyCallale implements Callable<String>
{
    @Override
    public String
call() 
throws Exception
{
        //
TODO Auto-generated method stub
        int i
0;
        Random
r = 
new Random();
        StringBuilder
sb = 
new StringBuilder();
        int res
0;
        while (i
100000000)
{
            i++;
            res
= r.nextInt(i);
        }
        sb.append(res);
        return sb.toString();
    }
}

四 阻塞对象池的几种方式

在实现阻塞对象池中,可以自定义同步,有数组方式和LinkedList,由于这些都不是线程安全的,所以需要显示的进行synchronized同步处理。但是都是串行化访问的,不利于并行处理。

还可以利用基础构建模块,利用BlockingQueue和Semaphore来实现,这些内部都实现了加锁机制,更便于并发与同步的效率。


Java并发基础构建模块简介的更多相关文章

  1. Java 并发基础

    Java 并发基础 标签 : Java基础 线程简述 线程是进程的执行部分,用来完成一定的任务; 线程拥有自己的堆栈,程序计数器和自己的局部变量,但不拥有系统资源, 他与其他线程共享父进程的共享资源及 ...

  2. java并发基础(二)

    <java并发编程实战>终于读完4-7章了,感触很深,但是有些东西还没有吃透,先把已经理解的整理一下.java并发基础(一)是对前3章的总结.这里总结一下第4.5章的东西. 一.java监 ...

  3. java并发基础(五)--- 线程池的使用

    第8章介绍的是线程池的使用,直接进入正题. 一.线程饥饿死锁和饱和策略 1.线程饥饿死锁 在线程池中,如果任务依赖其他任务,那么可能产生死锁.举个极端的例子,在单线程的Executor中,如果一个任务 ...

  4. Java并发基础概念

    Java并发基础概念 线程和进程 线程和进程都能实现并发,在java编程领域,线程是实现并发的主要方式 每个进程都有独立的运行环境,内存空间.进程的通信需要通过,pipline或者socket 线程共 ...

  5. java并发基础及原理

    java并发基础知识导图   一 java线程用法 1.1 线程使用方式 1.1.1 继承Thread类 继承Thread类的方式,无返回值,且由于java不支持多继承,继承Thread类后,无法再继 ...

  6. 【搞定 Java 并发面试】面试最常问的 Java 并发基础常见面试题总结!

    本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star![Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.欢迎 Sta ...

  7. java并发编程实战学习(3)--基础构建模块

    转自:java并发编程实战 5.3阻塞队列和生产者-消费者模式 BlockingQueue阻塞队列提供可阻塞的put和take方法,以及支持定时的offer和poll方法.如果队列已经满了,那么put ...

  8. 【Java并发.5】基础构建模块

    本章会介绍一些最有用的并发构建模块,有丶东西(最后一小节,纯干货). 5.1 同步容器类 同步容器类包括 Vector 和 Hashtable ,这些类实现线程安全的方式是:将它们的状态封装起来,并对 ...

  9. 【Java并发基础】管程简介

    前言 在Java 1.5之前,Java语言提供的唯一并发语言就是管程,Java 1.5之后提供的SDK并发包也是以管程为基础的.除了Java之外,C/C++.C#等高级语言也都是支持管程的. 那么什么 ...

  10. Java并发基础框架AbstractQueuedSynchronizer初探(ReentrantLock的实现分析)

    AbstractQueuedSynchronizer是实现Java并发类库的一个基础框架,Java中的各种锁(RenentrantLock, ReentrantReadWriteLock)以及同步工具 ...

随机推荐

  1. Redis实战9-全局唯一ID

    发布优惠券的时候,每个店铺都可以发布优惠券,当用户抢购的时候,优惠券表中的id如果使用数据库的自增长ID会存在以下问题: 1:id的规律太明显,容易被刷 2:当数据量很大的时候,会受到单表数据的限制 ...

  2. vue导出word文档

    具体需求 在我的疫情可视化项目中有一个功能需要导出word文档,在页面点击按钮后处理数据生成word文件,然后自动下载文档. 实现步骤 多番查询后发现前端导出word,使用docxtemplater较 ...

  3. 分布式缓存应用场景与redis持久化机制

    redis 参考目录: 生产级Redis 高并发分布式锁实战1:高并发分布式锁如何实现 https://www.cnblogs.com/yizhiamumu/p/16556153.html 生产级Re ...

  4. 原生JavaScript实现一个简单的Promise构造函数示例

    下面demo示例,只支持实例的then和catch,代码如下: function PromiseDiffer(fn){ var self = this; this.status = 'pendding ...

  5. DOM – IntersectionObserver

    介绍 IntersectionObserver 的作用是监听某个元素是否出现在框内 (比如 viewport). 它可以实现 lazy load image, 一开始图片是没有加载的, 当图片出现在 ...

  6. pytorch中LSTM各参数理解

    nn.LSTM(input_dim,hidden_dim,nums_layer,batch_first) 各参数理解: input_dim:输入的张量维度,表示自变量特征数 hidden_dim:输出 ...

  7. MySQL 切换 Oracle 问题整理

    MySQL 通常小写,Oracle 默认大写 ,查询过程中需加双引号,或者直接将MySQL 字段转换成大写 Springboot 配置 oracle连接 spring: datasource: url ...

  8. 【赵渝强老师】Oracle存储过程中的out参数

    一.什么是存储过程 Oracle存储过程可以说是一个记录集吧,它是由一些PL/SQL语句组成的代码块,这些PL/SQL语句代码像一个方法一样实现一些功能(对单表或多表的增删改查),然后再给这个代码块取 ...

  9. SMMU中stage1 和stage2 的意思

    ARM SMMU(System Memory Management Unit)是一种用于ARM架构的内存管理单元,它支持两阶段的地址转换机制,即Stage 1和Stage 2.这种机制允许操作系统和虚 ...

  10. M.2移动硬盘打造Win To Go系统:高效分区存储文件全攻略

    前言 大家好,我是 Frpee内网穿透 开发者 xnkyn, 曾经的我一直在互联网上学习技术,这次我要在博客园这片净土上给中国互联网技术做贡献,这是我在博客园写的第一篇技术文章,后续我会分享更多的技术 ...