JDK1.5引入的concurrent包
并发是伴随着多核处理器的诞生而产生的,为了充分利用硬件资源,诞生了多线程技术。但是多线程又存在资源竞争的问题,引发了同步和互斥,并带来线程安全的问题。于是,从jdk1.5开始,引入了concurrent包来解决这些问题。
java.util.concurrent 包是专为 Java并发编程而设计的包。
在Java中,当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替进行,在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么称这个类是线程安全的。
一般来说,concurrent包基本上由有3个package组成 :
java.util.concurrent:提供大部分关于并发的接口和类,如BlockingQueue,Callable,ConcurrentHashMap,ExecutorService, Semaphore等 ;
java.util.concurrent.atomic:提供所有原子操作的类, 如AtomicInteger, AtomicLong等;
java.util.concurrent.locks:提供锁相关的类, 如Lock, ReentrantLock, ReadWriteLock, Condition等。
concurrent包下的所有类可以分为如下几大类:
locks部分:显式锁(互斥锁和速写锁)相关,如ReentrantLock,ReentrantReadWriteLock等;
atomic部分:原子变量类相关,是构建非阻塞算法的基础,如AtomicInteger,AtomicBoolean,AtomicLong,AtomicReference等;
executor部分:线程池相关,如ExecutorService,Callable,Future等;
collections部分:并发容器相关,如BlockingQueue,Deque,ConcurrentMap等;
tools部分:同步工具相关,如CountDownLatch,CyclicBarrier,Semaphore,Executors,Exchanger等。
JUC的类图结构如下所示:
concurrent包的优点有:
①功能丰富,诸如线程池(ThreadPoolExecutor),CountDownLatch等并发编程中需要的类已经有现成的实现,不需要自己去实现一套; 相比较而言,jdk1.4对多线程编程的主要支持几乎只有Thread, Runnable,synchronized等。synchronized和JDK5之后的Lock均是悲观锁(悲观锁一般是一个人在使用的时候,另一个人不能用,所以性能极低,所能支持的并发量就不高)。
②concurrent包里面的一些操作是基于硬件级别的CAS(compare and swap,比较再赋值),就是在cpu级别提供了原子操作,简单的说就是可以提供无阻塞、无锁定的算法; 而现代cpu大部分都是支持这种算法的。JUC(java.util.concurrent)是基于乐观锁的,既能保证数据不混乱,又能保证性能。
version-(版本管理)就是基于乐观锁机制-->拿着我们期望的结果,和现有结果进行比对,如果是相同的,就赋值,如果不是相同的,就重试。
CAS:有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
CAS算法内部是通过JNI--native方法来实现: 由java底层的C语言或者C++实现。
一般情况下:同步容器是有使用价值的。有时候,我们的异步容器,比如ArrayList,在并发环境下,会有这些问题:①数据紊乱;②java.util.ConcurrentModificationException。这都是对于集合的读写状态不一致造成的问题。
我们可以这样构建同步容器:
Collections.synchronizedList(new ArrayList<>());
Collections.synchronizedMap()
Collections.synchronizedSet()
上面的方法可以实现同步容器,但是使用了悲观锁,因而效率不高。
使用JUC体系中提供的容器,如:ConcurrentHashMap,则有这样的优势:①不会出现同步问题,数据是正常的;②速度相对较快 ,就算没有hashmap快,也比hashtable快的多。其实,ConcurrentHashMap内部也是有同步的,在这方面面和hashtable没有区别。那么,ConcurrentHashMap快在哪里?主要是因为,其内部划分了很多segment区域,当不同的线程操作不同的segment的时候,其实还是一个异步操作;只有当不同线程操作同一个segment的时候,才会发生同步操作,所以速度很快。一个ConcurrentHashMap内部最多能有16个segment。
我们接下来看一个非常有用的类CountDownLatch, 它是一个可以用来在一个进程中等待多个线程完成任务的类。在此给出一个应用场景:某个主线程接到一个任务,起了n个子线程去完成,但是主线程需要等待这n个子线程都完成任务以后才开始执行某个操作。详见代码:
package com.itszt.test1;
import java.util.concurrent.CountDownLatch;
/**
* 主线程会在启动的子线程完全结束后再继续执行
*/
public class Test {
public static void main(String[] args) {
Test test = new Test();
test.demoCountDown();
} public void demoCountDown() {
int count = 10;
final CountDownLatch l = new CountDownLatch(count);
for (int i = 0; i < count; ++i) {
final int index = i;
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.currentThread().sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread -" + index + "- has finished...");
l.countDown();
}
}).start();
}
try {
l.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("now all threads have finished");
}
}
执行结果如下所示:
thread -9- has finished...
thread -8- has finished...
thread -0- has finished...
thread -4- has finished...
thread -1- has finished...
thread -2- has finished...
thread -5- has finished...
thread -6- has finished...
thread -3- has finished...
thread -7- has finished...
now all threads have finished
接下来,我们再看下Atomic相关的类, 比如AtomicLong, AtomicInteger等。简单来说,这些类都是线程安全的,支持无阻塞无锁定的。
package com.itszt.test1;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
/**
* 测试AtomicLong与long
*/
public class AtomicTest {
public static void main(String[] args) {
AtomicTest test = new AtomicTest();
test.testAtomic();
} public void testAtomic() {
final int loopcount = 10000;
int threadcount = 10;
final NonSafeSeq seq1 = new NonSafeSeq();
final SafeSeq seq2 = new SafeSeq();
final CountDownLatch l = new CountDownLatch(threadcount);
for (int i = 0; i < threadcount; ++i) {
final int index = i;
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < loopcount; ++j) {
seq1.inc();
seq2.inc();
}
System.out.println("finished : " + index);
l.countDown();
}
}).start();
}
try {
l.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("both have finished....");
System.out.println("NonSafeSeq:" + seq1.get());
System.out.println("SafeSeq with atomic: " + seq2.get());
}
class NonSafeSeq {
private long count = 0;
public void inc() {
count++;
}
public long get() {
return count;
}
} class SafeSeq {
private AtomicLong count = new AtomicLong(0);
public void inc() {
count.incrementAndGet();
}
public long get() {
return count.longValue();
}
}
}
上述代码执行如下:
finished : 0
finished : 3
finished : 2
finished : 6
finished : 9
finished : 5
finished : 8
finished : 1
finished : 4
finished : 7
both have finished....
NonSafeSeq:98454
SafeSeq with atomic: 100000
其中,NonSafeSeq是作为对比的类,直接放一个private long count不是线程安全的,而SafeSeq里面放了一个AtomicLong,是线程安全的;可以直接调用incrementAndGet来增加。通过上述执行结果可以看到,10个线程,每个线程运行了10,000次,理论上应该有100,000次增加,使用了普通的long是非线程安全的,而使用了AtomicLong是线程安全的。需要注意的是,这个例子也说明,虽然long本身的单个设置是原子的,要么成功要么不成功,但是诸如count++这样的操作就不是线程安全的,因为这包括了读取和写入两步操作。
在jdk 1.4时代,线程间的同步主要依赖于synchronized关键字,本质上该关键字是一个对象锁,可以加在不同的instance上或者class上。
concurrent包提供了一个可以替代synchronized关键字的ReentrantLock,简单的说,你可以new一个ReentrantLock, 然后通过lock.lock和lock.unlock来获取锁和释放锁;需要注意的是,必须将unlock放在finally块里面。reentrantlock的好处有
:
①更好的性能;
②提供同一个lock对象上不同condition的信号通知;
③还提供lockInterruptibly这样支持响应中断的加锁过程,意思是说你试图去加锁,但是当前锁被其他线程hold住,然后你这个线程可以被中断。
package com.itszt.test1;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
/**
* 测试ReentrantLock
*/
public class ReentrantLockTest {
public static void main(String[] args) {
ReentrantLockTest lockTest = new ReentrantLockTest();
lockTest.demoLock();
} public void demoLock() {
final int loopcount = 10000;
int threadcount = 10;
final SafeSeqWithLock seq = new SafeSeqWithLock();
final CountDownLatch l = new CountDownLatch(threadcount);
for (int i = 0; i < threadcount; ++i) {
final int index = i;
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < loopcount; ++j) {
seq.inc();
}
System.out.println("finished : " + index);
l.countDown();
}
}).start();
}
try {
l.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("both have finished....");
System.out.println("SafeSeqWithLock:" + seq.get());
} class SafeSeqWithLock {
private long count = 0;
private ReentrantLock lock = new ReentrantLock(); public void inc() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
} public long get() {
return count;
}
}
}
上述代码执行如下:
finished : 5
finished : 3
finished : 1
finished : 8
finished : 0
finished : 6
finished : 4
finished : 2
finished : 7
finished : 9
both have finished....
SafeSeqWithLock:100000
上述代码操作中,通过对inc操作加锁,保证了线程安全。
concurrent包里面还提供了一个非常有用的锁,读写锁ReadWriteLock。
A ReadWriteLock maintains a pair of associated locks, one for read-only operations and one for writing.
The read lock may be held simultaneously by multiple reader threads, so long as there are no writers.
The write lock is exclusive.
上述英文意思是说:读锁可以有很多个锁同时上锁,只要当前没有写锁; 写锁是排他的,上了写锁,其他线程既不能上读锁,也不能上写锁;同样,需要上写锁的前提是既没有读锁,也没有写锁。
package com.itszt.test1;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 测试读写锁
*/
public class RWLockTest {
public static void main(String[] args) {
RWLockTest lockTest = new RWLockTest();
lockTest.testRWLock_getw_onr();
} public void testRWLock_getw_onr() {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
final Lock rlock = lock.readLock();
final Lock wlock = lock.writeLock();
final CountDownLatch l = new CountDownLatch(2);
// start r thread,开启读锁
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " now to get rlock--获取读锁");
rlock.lock();
try {
Thread.currentThread().sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " now to unlock rlock--释放读锁");
rlock.unlock();
l.countDown();
}
}).start();
// start w thread,开启写锁
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " now to get wlock--获取写锁");
wlock.lock();
System.out.println(Thread.currentThread().getName() + " now to unlock wlock--释放写锁");
wlock.unlock();
l.countDown();
}
}).start();
try {
l.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " finished");
}
}
上述代码执行如下:
Thread-0 now to get rlock--获取读锁
Thread-1 now to get wlock--获取写锁
Thread-0 now to unlock rlock--释放读锁
Thread-1 now to unlock wlock--释放写锁
main finished
ReadWriteLock的实现是ReentrantReadWriteLock,有趣的是,在一个线程中,读锁不能直接升级为写锁,但是写锁可以降级为读锁;这意思是说,如果你已经有了读锁,再去试图获得写锁,将会无法获得, 一直堵住了;但是如果你有了写锁,再去试图获得读锁,就没问题。
下面是一段写锁降级的代码:
public void testRWLock_downgrade() {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
Lock rlock = lock.readLock();
Lock wlock = lock.writeLock();
System.out.println("now to get wlock");
wlock.lock();
System.out.println("now to get rlock");
rlock.lock();
System.out.println("now to unlock wlock");
wlock.unlock();
System.out.println("now to unlock rlock");
rlock.unlock();
System.out.println("finished");
}
上述代码在main函数中执行后,结果如下:
now to get wlock
now to get rlock
now to unlock wlock
now to unlock rlock
finished
我们再看一段读锁升级的代码:
public void testRWLock_upgrade() {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
Lock rlock = lock.readLock();
Lock wlock = lock.writeLock();
System.out.println("now to get rlock");
rlock.lock();
System.out.println("now to get wlock");
wlock.lock();
System.out.println("now to unlock wlock");
wlock.unlock();
System.out.println("now to unlock rlock");
rlock.unlock();
System.out.println("finished");
}
上述代码执行中,已经有了读锁,再去试图获得写锁,将会无法获得, 程序一直堵塞,进入死锁状态,显示如下:
now to get rlock
now to get wlock
另外,CountDownLatch是一个同步的辅助类,允许一个或多个线程,等待其他一组线程完成操作,再继续执行。
CyclicBarrier也是一个同步的辅助类,允许一组线程相互之间等待,达到一个共同点,再继续执行。
CountDownLatch和CyclicBarrier都是Synchronization aid,即“同步辅助器”,既然都是辅助工具,在使用中有什么区别,各自的使用场景如何?
CountDownLatch场景举例:一年级期末考试要开始了,监考老师发下去试卷,然后坐在讲台旁边玩着手机等待着学生答题,有的学生提前交了试卷,并约起打球了,等到最后一个学生交卷了,老师开始整理试卷,贴封条,下班,陪老婆孩子去了。
启发:CountDownLatch很像一个倒计时锁,倒计时结束,另一个线程才开始执行。就如监考老师要结束监考工作,必须等待所有学生都交了试卷,监考工作才能进入结束环节。 CyclicBarrier场景举例:公司组织户外拓展活动,帮助团队建设,其中最重要的一个项目就是要求全体员工(包括女同事,BOSS,一个都不能少)都能翻越一个高达四米,而且没有任何抓点的高墙,才能继续进行其他项目。
启发:CyclicBarrier可以看成是个障碍,所有的线程必须到齐后才能一起通过这个障碍。
JDK1.5引入的concurrent包的更多相关文章
- Concurrent包常用方法简介
1 Executor接口 留给开发者自己实现的接口,一般情况下不需要再去实现.它只有一个方法 void execute(Runnable command) 2 ExecutorService接口 它继 ...
- 推荐使用concurrent包中的Atomic类
这是一个真实案例,曾经惹出硕大风波,故事的起因却很简单,就是需要实现一个简单的计数器,每次取值然后加1,于是就有了下面这段代码: private int counter = ...
- Java并发(6):concurrent包中的Copy-On-Write容器
一. concurrent包介绍 在JDK1.5之前,Java中要进行业务并发时,通常需要有程序员独立完成代码实现,而当针对高质量Java多线程并发程序设计时,为防止死蹦等现象的出现,比如使用java ...
- 深入理解java:2.3. 并发编程 java.util.concurrent包
JUC java.util.concurrent包, 这个包是从JDK1.5开始引入的,在此之前,这个包独立存在着,它是由Doug Lea开发的,名字叫backport-util-concurrent ...
- java.util.concurrent包API学习笔记
newFixedThreadPool 创建一个固定大小的线程池. shutdown():用于关闭启动线程,如果不调用该语句,jvm不会关闭. awaitTermination():用于等待子线程结束, ...
- java concurrent包的学习(转)
java concurrent包的学习(转) http://my.oschina.net/adwangxiao/blog/110188 我们都知道,在JDK1.5之前,Java中要进行业务并发时,通常 ...
- 【并发编程】【JDK源码】JDK的(J.U.C)java.util.concurrent包结构
本文从JDK源码包中截取出concurrent包的所有类,对该包整体结构进行一个概述. 在JDK1.5之前,Java中要进行并发编程时,通常需要由程序员独立完成代码实现.当然也有一些开源的框架提供了这 ...
- java多线程管理 concurrent包用法详解
我们都知道,在JDK1.5之前,Java中要进行业务并发时,通常需要有程序员独立完成代码实现,当然也有一些开源的框架提供了这些功能,但是这些依然没有JDK自带的功能使用起来方便.而当针对高质量 ...
- 高并发编程基础(java.util.concurrent包常见类基础)
JDK5中添加了新的java.util.concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能.因为同步容器将所有对容器状态的访问都串行化了,这样保证了线程的安全性,所以这种方法 ...
随机推荐
- Codeforces Round #510 (Div. 2)(B)
传送门:Problem B https://www.cnblogs.com/violet-acmer/p/9682082.html 题意: 如果可以通过喝果汁将维生素A,B,C全部摄取,求最小花费,如 ...
- 源码研究:php变量
一:php中的变量类型 1.标量类型:布尔型 bool,整型 int,浮点型 float,字符串型 string2.复杂类型:数组 array,对象 object3.特殊类型:NULL,资源 reso ...
- sudALSA lib dlmisc.c:236:(snd1_dlobj_cache_get) Cannot open shared library /usr/lib/alsa-lib/libasound_module_pcm_pulse.so
我使用的系统是kali,默认的浏览器为iceweasel.默认的浏览器很好用!! 但是,给浏览器安装flash后,播放视频,会报错:有视频,无声音. 下面给出我的解决方案:
- HTTP Status 405 - HTTP method POST is not supported by this URL
出现这个问题, 1.在servlet中没有调用post()方法引起的 2.在serlvet中跳转没有用外跳(response.sendRedirect()) 我的是因为第一种,是没有写dopost() ...
- AE与C#入门笔记
http://www.cnblogs.com/e3e4/articles/3695214.html
- logstash 常用参数
最近在折腾logstash,其处理流程不过于input.filter.output三个处理流程,以下是我翻译的几个常用的处理参数 output流之http output { http { codec ...
- 怎么看SQL表里某列是否是自增列
开启或关闭自增列 SET IDENTITY_INSERT 表名 ON/OFF 1.语句 Select OBJECTPROPERTY(OBJECT_ID('表名'),'TableHasIdentity' ...
- python---RabbitMQ(2)exchange中订阅者模式fanout<广播>,(一对多,发布一条消息,多人同时接收)
fanout:广播:所有bind到此exchange的queue都可以接受到消息 生产者: # coding:utf8 # __author: Administrator # date: // # / ...
- springboot环境下配置过滤器和拦截器
以前我们在配置过滤器和拦截器的时候,都是一个类继承一个接口,然后在xml中配置一下就ok 但是,但是,这是springboot的环境,没有xml的配置.所以我们还要继续学习啊啊啊啊啊~~~~~ 先简单 ...
- Hadoop记录-MRv2(Yarn)运行机制
1.MRv2结构—Yarn模式运行机制 Client---客户端提交任务 ResourceManager---资源管理 ---Scheduler调度器-资源分配Containers ----在Yarn ...