前言
总结来源:java核心技术卷1和java编程思想。
- 操作系统将CPU上的时间片分配给每一个进程。
- 进程与线程的区别:每个进程拥有自己的一整套变量,而线程共享数据。
使用线程提供任务
过程:
1)将任务代码移到实现了Runnable接口的类的run()方法中,该接口只有一个方法:
public interface Runnable
{
void run();
}
由于Runnable接口是函数式接口,可以用lambda表达式建立一个实例:
Runnable r = () -> {task code};
2)由Runnable创建一个Thread对象:
Thread t = new Thread(r);
3)启动线程:
t.start();
注意:也可以通过构建Thread类的子类来定义一个线程,构造子类的对象并调用start()方法,不过该方式已经不再被推荐。
class MtThread extends Thread
{
public void run(){
task code
}
}
中断线程
- 线程终止:当线程的run方法执行方法体中的最后一条语句,并经由执行return语句返回时或者出现了在方法中没有捕获的异常时,线程将终止。
- 对一个线程调用interrupt方法时,线程的中断状态将被置位,每个线程都具有boolean标志,可以先调用静态方法Thread.currentThread()方法获取当前线程,然后调用isInterrupted()方法检查该标志位,但是如果线程被阻塞,就无法检测中断状态并产生Interrupted Exception,如果在中断状态被置位时调用sleep()方法,它不会休眠,相反会清除之一状态并抛出Interrupted Exception。
- 注意:没有可以强制线程终止的方法,然而,interrupt方法可以用来请求终止线程。
interrupt()、interrupted()以及isInterrupter()方法的区别
java.lang.Thread 1.0 //该类包含的常用方法
void interrupt()
说明: interrupt方法向线程发送中断请求,线程中断状态被设置为true,
如果当前线程被sleep调用阻塞,则抛出Interrupted Exception。
static boolean interrupted()
说明: interrupted方法测试当前线程是否被中断,调用该方法会产生副作用,
即它将当前线程的中断状态重置为true。
boolean isInterrupted()
说明: isInterrupted方法测试线程是否被终止,该调用不会改变线程的中断状态。
static Thread currentThread()
说明:返回代表当前执行线程的Thread对象。
注意:在很多代码中发现Interrupted Exception被抑制在很低的层次上,像这样:
void mySubTask(){
...
try{
sleep(delay);
}catch(InterruptedException e){} //Don't ignore!
}
不应该这样做,如果不知道如何处理该异常,应该采用以下两种方式:
- 在catch子句中调用Thread.currentThread().interrupt()来设置中断状态,于是调用者可以对其进行检测;
- 更好的选择是不进行处理直接抛出,如下:
void mySubTask() throws InterruptedException{
...
sleep(delay);
...
}
线程状态
- 新创建:用new操作符创建一个线程且该线程还没有启动。
- 可运行状态:调用start()方法后,该线程处于runnable状态。
抢占式调度:给每个可运行的线程一个时间片来执行任务,当时间片用完,操作系统剥夺该线程的执行权,根据优先级选择下一个执行的线程。
协作式调度:一个线程只有在调用yield()方法、或者被阻塞或等待时,线程才会失去控制权。 - 被阻塞县城和等待线程:当线程处于被阻塞或等待状态时,它不允许任何代码且消耗最少的资源,直到线程调度器重新激活它。
- 被终止的线程,原因有两个:
1)run()方法正常退出;
2)因为没有捕获的异常终止了run()方法。
线程属性
- 线程优先级:在java程序设计语言中,每个线程都有一个优先级,默认情况下一个线程继承它的父线程的优先级。
java.lang.Thread 1.0
void setPriority(int newPriority)
说明: 设置线程的优先级,优先级必须在Thread.MIN_PRIORITY与
Thread.MAX_PRIORITY之间,默认为Thread.NORM_PRIORITY
static void yield()
说明: 导致当前线程处于让步状态,如果有其他可运行且具有至少与此线程
同样高的优先级,那么该线程会被调用。
-
守护线程:通过设置t.setDaemon(true)将t线程转换为守护线程,该线程唯一的用途是为其他线程提供服务,当只剩下守护线程时,虚拟机就会退出。
注意:守护线程应该永远不访问固有资源如文件、数据库等,因为它会在任何时候甚至在一个操作的中间发生中断。 -
未捕获异常处理器:线程的run()方法不能抛出任何受查异常,但是非受查异常会导致线程终止,run方法不需要任何catch子句来处理可以被传播的异常,相反,在线程死亡之前,异常会被传递到一个用于未捕获异常的处理器,该处理器必须术语一个实现了Thread.UncaughtExceptionHandler接口的类,该接口只有一个方法:
void uncaughtException(Thread t, Throwable e)
java.lang.Thread 1.0
static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler) 5.0
static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() 5.0
说明: 设置或获取未捕获异常的默认处理器
void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler) 5.0
Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() 5.0
说明: 设置或获取未捕获异常的处理器,如果没有安装,则将线程组对象作为处理器。
java.lang.Thread.UncaughtException
void uncaughtException(Thread t, Throwable e)
说明: 当一个线程因未捕获异常而终止,按照规定要将客户报告记录到日志中。
-
同步
1)需要同步的原因:多个线程访问共享数据并可能对其进行修改时会造成数据不可预测的错误。通常这种情况称为存在竞争条件。 -
锁对象
有两种机制可以防止代码快受并发访问的干扰,java语言提供了一个synchronized关键字表达这一目的,并且Java SE 5.0引入了ReentrantLock类。
概述:锁和条件的关键之处
锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码。
锁可以管理试图进入被保护代码的线程。
锁可以拥有一个或多个相关的条件对象。
每个条件对象管理那些已经进入被保护的代码段但是还不能运行的线程。
1)用ReentrantLock保护代码快的基本结构
puvlic class MyClass{
//创建一个锁对象
private Lock myLock = new ReentrantLock();
//该方法包含需要加锁的共享代码段
public void myMethod(){
myLock.lock(); //a ReentrantLock object
try{
critical section
}finally{
//把释放锁放在finally内保证所可以正常释放
myLock.unlock();//确保释放锁
}
}
}
//注意:使用锁就不能使用带资源的try语句。
注意:
1)使用锁就不能使用带资源的try语句。
2)锁是可以重入的,即一个线程可以再次获取已经获得的锁,没获取一此锁就
要调用一次释放锁的方法。
3)使用new ReentrantLock(boolean fair)可以创建公平策略的锁,不过会大
大降低性能,最好不要使用。
- 条件对象:
1)通常线程进入临界区却发现需要满足某写条件才能运行,要使用 一个条件对象来管理那些已经获得锁缺不能工作的线程。 2)一个锁对象可以有一个或多个相关的条件对象,可以通过newCondition() 方法获得一个条件对象。 3)一个线程调用await()方法,它进入该条件的等待集,当锁可用时, 该线程不能马上接触阻塞,相反它处于阻塞状态,知道另一个线程调用 同一条件上的signalAll()方法时为止。 4)调用signalAll()方法不会立即接触一个等待线程,而是解除了该条件下 的线程阻塞,使这些线程可以通过竞争实现对象的访问。 5)最好不要使用signal()方法,该方法之会随机唤醒一个线程,如果该 线程无法执行,且没有其他线程来进行唤醒操作,那么会导致死锁发生。 6)当等待条件的线程获得锁时应该再次测试该条件,由于无法确保条 件是否满足,signalAll()方法只是通知正在等待的线程,此时条件可能 已经满足需要再次进行检测。
通常使用如下方法进行检测:
while(!(ok to proceed))
condition.await();
//示例
puvlic class MyClass{
//创建一个锁对象
private Lock myLock = new ReentrantLock();
//创建一个条件对象
private Condition condition = myLock.newCondition();
//该方法包含需要加锁的共享代码段
public void myMethod(){
myLock.lock(); //a ReentrantLock object
try{
//判断条件是否满足,不满足则进行等待
while(!条件)
conditon.await();
...
}finally{
//把释放锁放在finally内保证所可以正常释放
myLock.unlock();//确保释放锁
}
}
}
java.util.concurrent.locks.Lock 5.0
Condition newCondition()
说明: 返回一个与该锁相关的条件对象
java.util.concurrent.locks.Condition 5.0
void await()
说明: 将该线程放到条件的等待集中。
voidsignalAll()
说明: 解除该条件的等待集中的所有线程的阻塞状态。
void signal()
说明: 从该条件的等待集中随机选择一个线程,解除其阻塞状态。
- synchronized关键字
1)Lock和Condition接口为程序设计人员提供了高度的锁定控制,但大多数情况下不需要这样的控制,java语言提供了一种机制,即从1.0版本开始,java中的每个对象都有一个内部锁(this.intrinsicLock),如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。
2)内部对象锁只有一个相关条件,wait()方法添加一个线程到等待集中,notifyAll()notify()方法解除等待线程的阻塞状态。
3)wait()、notifyAll()、notify()方法是Object类的final方法,Condition方法必须被命名为await()、signalAll()、signal()以便它们不会与那些方法产生冲突。
示例
class MyClass{
public sychcronized void myMethod(){
while(!条件)
wait();
... //方法主体
nofityAll();//释放锁
}
}
//上述代码相当于
class MyClass{
public void myMethod(){
this.intrinsicLock.lock(); //Java的内部锁
while(!条件)
await();
... //方法主题
this.intrinsicLock.signalAll();
}
注意1):内部锁和条件存在的一些局限:
- 不能中断一个正在试图获得锁的线程;
- 试图获得锁时不能设定超时;
- 每一个锁仅有单一的条件;
注意2):当时有synchronized关键字进行同步时,我们使用wait、notifyAll、
notify方法进行控制,当使用Lock锁时,我们使用await、signalAll、signal
进行控制。
注意3):在代码中应该使用哪种锁方式?Lock和Condition对象还是同步方法?
最好不使用这两种,许多情况下可以使用java.util.concurrent包中的机制,
它会自动处理所有的加锁,如使用阻塞队列来同步完成一个共同任务的线程,
以及并行流。。
如果synchronized关键字时候,应尽量使用,这样可以减少代码数量降低
出错的几率。
- 同步阻塞:
每个java对象都一个锁,线程除了可以通过同步方法获得自身的内部锁外,还可以进入同步阻塞获得锁Object对象的锁。
public class MyClass{
private Object lock = new Object();
public void myMethod(int a,int b){
synchronized(lock){
...
}
...
}
- Volatile域:
1)如果向一个变量写入值,而改变量接下来可能会被另一个线程读取,或者,
从一个变量读取值,而这个变量可能是之前被另一个线程写入,此时必须使用同步。
2)volatile关键字为实例域的同步提供了一种免锁机制,如果声明一个域为
volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。
注意1):volatile变量不能提供原子性。
注意2):假设对改变量除了赋值以外并不完成其他操作,那么可以将这个共享变量
声明为volatile。
2)的示例
private volatile boolean done;
public boolean isDone(){return done;}
public void setDone(){ done = true;}
注意1)的示例
public voidf flipDone(){done = !done;}
不能确保翻转域的值,该过程需要先读取然后翻转最后保存一步到位,不能
保证读取、翻转和写入过程不被中断。
- 原子性:比加锁效率更快
java.util.concurrent.atomic包中有很多类使用很高级的机器指令(而不是使用锁) 来保证其他操作的原子性。
示例:
AtomicInteger类提供了incrementAndGet()和decrementAndGet()方法来实现自增和自减。
public class MyClass{
public static AtomicLong nextNum = new AtomicLong();
long id = nextNum.incrementAndGet();
}
在Java SE 8之前,如果需要完成更加复杂的更新,就必须使用compareAndGet()方法,
但是现在只需要使用uodateAndGet()并传入一个lambda表达式即可,或者使用
accumulateAndGet()方法,此外还有getAndUpdate()、getAndAccumulate()方法
可以返回原值。
public class MyClass{
public static AtomicLong nextNum = new AtomicLong();
nextNum.updateAndGet(x -> Math.max(x,observed));
//或
nextNum.accumulateAndGet(observed,Math::max);
}
如果有大量的线程要访问相同的原子值,性能会大幅下降,因为乐观更新需要太多次重试,
Java SE 8提供了LongAdder和LongAccumulator类来解决这个问题,LongAdder包含多个
变量(加数),其总和为当前值,可以有多个线程更新不同的加数,线程个数增加时会自
动提供新的加数。通常情况,只有当前工作都完成后才需要总和的值,对于这种情况,这
种方法会很高效。
//LongAdder示例,调用increment()进行自增,调用sun()获取总和
final LongAddder adder = new LongAdder();
for(...)
pool.submit(() ->{
while(...){
...
if(...) adder.increment();
}
});
long total = adder.sum();
/*
LongAccumulator将上述推广到任意的累加操作,在构造器中需要提供操作
以及它的零元素,要加入新的值,可以调用accumulate(int)方法,并传入
要增加的值,调用get()方法来获取当前值,下面代码与上述效果一样。
*/
LongAccumulator adder = new LongAccumulator(Long::sum,0);
//In some thread...
adder.accumulate(value);
/*
在内部,这个累加器包含变量a1,a2,...an。每个变量初始化为零元素(在
这个例子零元素为0),调用accumulate()并提供value时,其中的某个变
量ai就会以原子方式进行更新ai = ai op value,在这里op是中缀形式的
累加操作,当调用get()时,会返回所有变量中缀累加和(a1 op a2 op
... op an)即等于a1+a2+...+an。
另外DoubleAdder和DoubleAccumulator也采用同样的操作。
*/
- 死锁:
1)发生的条件:循环等待且都不释放锁。 - 线程局部变量:
当线程之间共享变量时会存在风险,有时需要避免使用共享变量,可以使用ThreadLocal辅助类为各个线程提供各自的实例。
列如SimpleDateFormat不是线程安全的类,如果多个线程调用format(Date date)方法,内部数据会因为并发访问而产生破坏,可以使用同步,但开销很大,也可以在需要是构造一个局部的SimpleDateFormat对象,不过也太浪费,此时可以使用ThreadLocal类进行构造。
//存在线程安全的方法示例
public class MyClass{
public static SimpleDateFormat dateFormat =
new SimpleDateFormat("yyyy-MM-dd");
//如果多个线程调用format()方法则会导致出错
String dateStamp = dateFormat.format(new Date());
}
//使用ThreadLocal构造局部变量的方法示例
public class MyClass{
public static final ThreadLocal<SimpleDateFormat>
dateFormat = ThreadLocal.withInitial(
() -> new SimpleDateFormat("yyyy-MM-dd");
);
//访问具体的格式化方法可以调用
String dateStamp = dataFormat.get().format(new Date());
}
java.long.ThreadLocal<T> 1.2
T get()
说明: 得到这个线程的当前值,如果是首次调用get,会调用initialize()
来初始化这个值。
protected initialize()
说明: 应覆盖该方法来提供一个初始值,默认情况下,该方法返回null。
void set(T t)
说明: 为合格线程设置一个新值。
void remove()
说明: 删除对应这个线程的值。
static <S> ThreadLocal<S> withInitial(Supperlier<? extends S>)
supplier) 8
说明: 创建一个线程局部变量,其初始值通过调用给定的supplier生成。
java.util.concurrent.ThreadLocalRandom 7
static ThreadLocalRandom current()
说明: 返回特定于当前线程的Random实例。
- 锁测试与超时:
线程在调用lock()方法获得另一个线程所持有的锁的时候,很可能会发生阻塞,tryLock()方法试图申请一个锁,在成功获得锁后返回true,否则毅力返回false,此时线程可以离开去做其他事。
示例:
if(myLock.tryLock()){
try{
...
}finally{
myLock.unLock();
}
}
注意1):lock()方法不能被中断,如果一个线程在等待获得一个锁时被中断,中断线程
在获得锁之前一直处于阻塞状态,如果出现死锁,那么lock()方法就无法终止。
注意2):如果调用带有超时参数的tryLock(),那么如果线程在等待期间被中断,
await()方法将抛出InterruptedException异常,这是一个非常有用的特性,它允
许打破死锁。
注意3:):如果一个线程被另一个线程调用signalAll()激活,或者达到超时时限,又
或者线程被中断,那么await()方法将返回。
java.util.concurrent.locks.Lock 5.0
boolean tryLock()
说明: 尝试获得锁而没有发生阻塞,如果成功返回true否则返回false。
这个方法会抢夺可以资源。
boolean try(long time,TimeUnit unit)
说明: 尝试获得锁,阻塞时间不会超过给定值。
TimeUnit是一个枚举类型,包括: SECONDS、MILLISECONDS、
MICROSECONDS、NANOSECONDS。
void lockInterruptibly()
说明: 获得锁,但是会不确定的发生阻塞,如果线程被中断,会否出中断异常。
java.util.concurrent.locks.Condition 5.0
boolean await(long time, TimeUnit unit)
说明: 进入该条件的等待集,知道线程从等待集中移出或等待了指定时间后才
解除阻塞。如果因为等待时间到了而返回则返回false,否则返回true.
void awaitUninterruptibly()
说明: 进入该条件的等待集,知道线程从等待集中移出才解除阻塞,如果线程
被中断,该方法不会抛出InterruptedException异常。
- 读\写锁:
如果很多线程从一个数据结构中读取数据而很少线程修改其中的数据,使用ReentrantReadWriteLock类很有用。在这种情况下,允许对读取的线程共享访问时合适的,当然,写入的线程依然必须是互斥访问的。
下面是使用读\写锁的必要步骤:
1)构造一个RenntrantReadWriteLock对象:
private ReentrantReadWriteLock rwl
= new ReentrantReadWriteLock();
2)抽取读锁和写锁:
private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();
3)对所有的获取方法加读锁:
public double getTotalBalance(){
readLock.lock();
try{
...
}finally{
readLock.unlock();
}
}
4)对所有的修改方法加写锁:
public void transfer(...){
writeLock.lock();
try{
...
}finally{
writeLock.unlock()l
}
}
java.util.concurrent.locks.ReentrantReadWriteLock 5.0
Lock readLock()
说明: 得到一个可以被多个读操作共用的锁,但会排除所有的写操作。
Lock writeLock()
说明: 得到一个写锁,排除所有其他的读操作和写操作。
- 为什么启用stop和suspend方法:
1)stop方法会终止所有未结束的方法包括run()方法,当线程被终止时立即释放它所持有的锁,这会导致状态不一致,例如,转账问题,当从一个账户转出钱还未到另一个账户时终止了线程,此时钱已转出而另一个账户未到账。
2)suspend方法会导致死锁,suspend会挂起一个持有一个锁的线程,直到另一个线程调用resume()方法进行恢复,被挂起的线程持有的锁在恢复之前是不可用的,如果调用suspend()方法的线程需要获取被挂起线程的锁,那么就会导致死锁,被挂起的线程等待被恢复,而将其挂起的线程等待锁。 - 阻塞队列:
1)对于许多线程问题,可以使用一个或多个队列以优雅且安全的方式将其形式化。生产者线程向队列中插入元素,消费者线程取出他们。使用队列,可以安全的=地从一个线程向另一组线程传递数据。
2)当试图向队列添加元素而队列已满,或者想从队列移除元素而队列为空的时候,阻塞队列会导致线程阻塞。
方法 | 正常动作 | 特殊情况下的动作 |
---|---|---|
add | 添加一个元素 | 如果队列满,则抛出IllegalStateException |
element | 返回队列的头元素 | 如果队列空,抛出NoSuchElementException |
offer | 添加一个元素并返回true | 如果队列满,返回false |
peek | 返回队列的头元素 | 如果队列空,返回null |
pool | 移除并返回队列的头元素 | 如果队列空,返回null |
put | 添加一个元素 | 如果队列满,阻塞 |
remove | 移除并返回头元素 | 如果队列空,抛出NoSuchElementException |
take | 移除并返回头元素 | 如果队列空,阻塞 |
java.util.concurrent.ArrayBlockingQueue<E> 5.0
ArrayBlockingQueue(int catacity)
ArrayBlockingQueue(int catacity, boolean fair)
说明: 构造一个带有指定容量和公平性设置的阻塞队列,该队列用循环数组
实现,如果设置了公平性参数,那么等待了最长时间的线程会优先得到处理,
不过会大大降低性能,不要不得已,不要进行设置。
java.util.concurrent.LinkedBlockingQueue<E> 5.0
java.util.concurrent.LinkedBlockingDeque<E> 6
LinkedBlockingQueue()
LinkedBlockingDeque()
说明: 构造一个无上限的阻塞队列或双向队列,用链表实现。
LinkedBlockingQueue(int capacity)
LinkedBlockingDeque(int capacity)
说明: 构造一个指定容量的阻塞队列或双向队列,用链表实现。
java.util.concurrent.DelayQueue<E extends Delayed> 5.0
DelayQueue()
说明: 构造一个包含Delayed元素的无界的阻塞时间有限的阻塞队列,只
有那些延迟已经超过时间的元素可以从队列中移除。
java.util.concurrent.Delayed 5.0
long getDelay(TimeUnit unit)
说明: 得到该对象的延迟,用给定时间单位进行度量。
java.util.concurrent.PriorityBlockingQueue<E> 5.0
PriorityBlockingQueue()
PriorityBlockingQueue(int initialCapacity)
PriorityBlockingQueue(int initialCapacity, Comparator<? suoer E> comparator)
说明: 构造一个无边界阻塞优先队列,用堆实现。
参数: initialCapacity 优先队列的初始容量,默认值为11.
comparator 用来对元素进行比较的比较器,如果没有指定,
则元素必须实现Comparator接口。
java.util.comcurrent.BlockingQueue<E> 5.0
void put(E element)
说明: 添加元素,必要时进行阻塞。
E take()
说明: 移除并返回头元素,必要时进行阻塞。
boolean offer(E element, long time, TimeUnil unit)
说明: 添加给定的元素,如果成功返回true,必要时阻塞,否则返回false。
E pool(long time, TimeUnit unit)
说明: 移除并返回头元素,必要时阻塞,直至元素可用或超时用完 ,失败时
返回null。
java.util.concurrent.BlockingDeque<E> 6
void putFirst(E element)
void putLast(E element)
说明: 添加元素,必要时阻塞。
E takeFirst()
E takeLast()
说明: 移除并返回头元素或尾元素,必要时阻塞。
boolean offerFirst(E element, long time, TimeUnit unit)
boolean offerLast(E element, long time, TimeUnit unit)
说明: 添加给定的元素,成功时返回true,必要时阻塞直至元素添加或超时。
E pollFirst(long time, TimeUnit unit)
E poolLast(long time, TimeUnit unit)
说明: 移动并返回头元素或尾元素,必要时阻塞,直至元素可用或超时,失败
时返回null。
java.util.concurrent.TransferQueue<E> 7
void transfer(E element)
boolean tryTransfer(E element, long time, TimeUnit unit)
说明: 传输一个值,或者尝试在给定的超时时间内传输这个值,这个调用将阻
塞,直到另一个线程将元素删除。第二个方法会在调用成功时返回true。
- 线程安全的集合:
java.util.concurrent包提供了映射、有序集和队列的高效实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet和ConcurrentLinkedQueue,通过永续并发地访问数据结构的不同部分来使竞争极小化。
java.util.concurrent.ConcurrentLinkedQueue<E> 5.0
ConcurrentLinkedQueue<E>()
说明: 构造一个可以被多线程安全访问的无边界且非阻塞的队列。
ConcurrntSkipListSet<E>() 6
ConcurrntSkipListSet<E>(Comparator<? super E> comp) 6
说明: 构造一个可以被多线程安全访问的有序集。第一个构造要求元素
实现Comparable接口。
java.util.concurent.ConcurrentHashMao<K,V> 5.0
java.util.concurrent.ConcurrentSkipListMap<K,V> 6
ConcurrentHashMap<K,V>()
ConcurrentHashMap<K,V>(int initialCapacity)
ConcurrentHashMap<K,V>(int initialCapacity, float loadFactor, int concurrencyLevel)
说明: 构造一个可以被多线程安全访问的散列映射。
参数: initialCapaciity 集合的初始容量,默认值为16.
loadFactor 负载因子,默认值为0.75.
concurrencyLevel 并发写者(即对映射进行修改的线程)线程的估计数目
映射条目的原子更新:
1)错误示例:构造一个并发映射map
Long oldValue = map.get(key);
Long newValue = oldValue == null ? 1 : oldValue + 1;
map.put(key,newValue);
上述操作虽然get和put是线程安全的不会破坏数据结构,但是操作的执行序列
不是原子的,所有结婚不可预测。
可以使用一个CocurrentHashMap<String,AtomicLong>或者
CocurrentHashMap<String,LongAdder>,示例如下:
map.putIfAbsent(key,new LongAdder().increment());
解释:putIfAbsent方法返回映射的值(可能是原来的值,或者是新设置的值)
,如果该键不存在对应的LongAdder值,则LongAdder构造器会调用。
2)java 8提供了更方便的原子更新方法,调用compute()方法时可以提供一个
键和一个计算新值得函数,这个函数接受相关联的键和值(如果没有值则为null),
然后计算新值。
map.compute(key,(k,v) -> v == null ? 1 : v + 1);
注意:C哦你currentHashMap中不允许值为null。
此外,还有computeIfPresent()和computeIfAbsent()方法,分别在已经
有原值和没有原值的情况下计算新值,该方法与上述putIfAbsent()方法不同
,putIfAbsent()方法在存在原值的情况下调用原值,不存在的情况下进创建。
map.computIffAbsent(key, k -> new LongAdder().increment());
3)首次增加一个键时通常需要进行特殊处理,可以使用merge()方法进行处理,该
方法有一个参数表示键不存在时使用的初始值,否则,就调用提供的函数来结合
原值和新值。
map.merge(key,1L,Long::sum);
此时,键不存在时值为Long类型的1.
对并发映射的匹操作:
java 8为并发映射提供了匹操作,有三种不同的操作,其中每个操作都对应这四个
具体的版本。
操作:
1)搜索(search):为每个键或值提供一个函数,知道生成一个非null的结果,然后搜索终止
,返回函数的结果。
2)归约(reduce):组合所有的键或值,并提供一个累加函数。
3)forEach:为所有的键或值提供一个函数。
每种操作对应的四种具体操作:
1)operationKey:处理键。
2)operationValues:处理值。
3)operation:处理键和值。
4)operationEntries:处理Map.Entry对象。
上述操作均需要指定一个参数化的阈值,如果映射条目多于阈值,就会并行完成匹操
作,如果希望匹操作运行在一个线程中,则与之可以为Long.MAX_VALUES,如果希
望尽可能多的线程运行匹操作,可以使用阈值1。
1. search示例:
U searchKeys(long threshold, BiFunction<? super K, ? extends U> f)
U searchValues(long threshold, BiFunction<? super V, ? extends U> f)
U search(long threshold, BiFunction<? super K, ? super V, ? extends U> f)
U searchEntries(long threshold, BiFunction<Map.Entry<K,V>, ? extends U> f)
//查找第一个出现次数超过1000次的单词
String result = map.search(threshold, (k,v) -> v > 1000 ? k : null);
说明: result匹配第一个找到的单词,如果没有返回null。
2. forEach方法有两种形式,第一个只为各个映射条目提供一个消费者函数,第二种形式
包括一个转换器,其结果会传递给消费者,该转换器可以作为一个过滤器,只要转换器的
返回null,该值就不会传递给消费者。
形式1: 正常输出
map.forEach(threshold,
(k, v) -> System.out.println(k + " -> " + v));
形式2: 转换器
map.forEach(threshold,
(k, v) -> k + " -> " + v, //转换器
System.out::println)); //消费者
形式3: 转换器和过滤器
map.forEach(threshold,
(k, v) -> v > 1000 ? k + " -> " + v : null, //过滤到和转换
System.out::println); //消费者
3. reduce操作一个累加函数(并非是数学意义的加法,而是指其依次会往后执行进行
统计)组合其输入,其操作和forEach类型也可有转换器和过滤器。
1)计算总和
Long sum = map.reduceValues(threshold, Long::sum);
2)转换器
Integer maxLength = map.reduceKeys(threshold,
String::length, //转换器
Integer::max); //累加器,此处是条统计最大长度的键
3)转换器和过滤器
Long count = map.reduceValues(threshold,
v -> v > 1000 ? 1L : null, //过滤只剩下大于1000的值
Long::sum); //统计个数
注意: 如果只有一个元素,则不会应用累加器。
4. 对于int、long和double输出还有相应的特殊化操作,分别有后缀ToInt、ToLong
和ToDouble,需要把输入转换成基本类型,并指定一个默认值和累加函数,映射为空
时返回默认值。
示例:
long sum = map.reduceValuesToLong(threshold,
Long::longValue, //转换成基本类型
0, //映射为空时默认值
Long::sum); //基本类型的累加函数
并发集视图:
如果想要一个大的线程安全的集而不是映射,并没有一个ConcurrentHashSet
类,需要使用静态方法newKeySet()方法生成一个Set<K>。
Set<String> words = ConcurrentHashMap.<String>newKeySet();
如果原来有一个映射也可以使用keySet()方法生成这个映射的键集,该集是可变的,
删除该集合中的元素,这个键以及值就会从映射中删除,但是不能向键集中增加元素。
不过,java 8提供了第二个keySet()方法,包含一个默认值,可以为集增加元素时使用:
Set<String> woeds = map.keySet(1L);
words.add("java");
如果java在words中不存在,则他现在的值为1。
- Callable与Future:
1)Rnnable封装一个异步运行的任务,可以把它想象成一个没有参数和返回这的异步方法。
2)Callable和Runnable类似,但是有返回值,Callable接口是一个参数化的类型,只有一个方法call()。
3)Future保存异步计算的结果,可以启动一个计算,将Future交给一个线程,Future的结果在计算好之后可以获取。
4)FutureTask包装器可将Callable转换为Future和Runnable。
java.util.concurrent.Callable<V> 5.0
V call()
说明: 运行一个将产生结果的任务。
java.util.concurrent.Future<V> 5.0
V get()
V get(long time, TimeUnit unit)
说明: 获取结果,如果没有可用结果,则阻塞知道真正得到结果或超过指定
的时间为止,如果不成功,第二个方法将抛出TimeoutException异常。
boolean cancel(boolean mayInterrupt)
说明: 尝试取消这一任务的运行,如果任务已经开始并且boolean参数为true,
它会被中断,如果成功执行了取消操作,则返回true。
boolean isCancelled()
说明: 如果任务在完成之前取消了,则返回true。
boolean isDone()
说明: 如果任务结束,无论是正常结束、中途取消或发生异常,都返回true。
java.util.concurrent.FutureTask<V> 5.0
FutureTask(Callable<V> task)
FutureTask(Runnable task, V result)
说明: 构造一个即使Future<V>又是Runnable的对象。
示例:
Callable<String> sss = ...;
FutureTask<String> task = new FutureTask<String>(sss);
Thread t = new Thread(task);
t.start();
...
String result = task.get(); //如果没有完成,则该方法阻塞,直至有
//可用的结果
- 执行器:
构建一个新的线程是有一定代价的,因此应该使用线程池。一个线程池中含有准备运行的空闲线程,将Runnable交给线程池,就要有一个线程调用run()方法,如果任务数大于空闲线程数,则会将任务至于队列中等待。
执行器(Executor)类有许多静态工厂方法用来构建线程池。
方法 | 描述 |
---|---|
newCachedThreadPool | 如有没有空闲线程,则立即创建一个新线程,空闲线程会被保留60秒 |
newFixedThreadPool | 该线程池有固定数量的线程,空闲线程会一直保留 |
newSingleThreadExecutor | 只有一个线程的池,该线程顺序执行每一个提交的任务,类似于Swing的事件分配线程 |
newScheduledThreadPool | 用于预订执行而构建的固定线程池,替代java.util.Timer |
newSingleThreadScheduledExecutor | 用于预订执行而构建的单线程池 |
java.util.concurrent.Executors 5.0
ExecutorService newCachedThreadPool()
说明: 返回一个带缓存的线程池,该池在必要的时候创建线程,在线程空闲60
秒后终止。
ExecutorService newFixedThreadPool(int threads)
说明: 返回一个线程池,该池中的线程数有参数确定。
ExecutorService newSingleThreadExecutor()
说明: 返回一个执行器,他在一个单一的线程中执行各个任务。
java.util.concurrent.ExecutorService 5.0
Future<T> submit(Callable<T> task) //get()方法返回T类型
Future<T> submit(Runnable task, T result) //get()方法返回T类型result
Future<T> submit(Runnable task) //执行完后get()方法返回null
提交指定的任务去执行,返回Future对象,可以通过get()等方法查看。
void shutdown()
说明: 关闭服务,会先完成已经提交的任务,而不再接收新任务。
java.util.concurrent.ThreadPoolExecutor 5.0
int getLargestPoolSize()
说明: 返回线程池在该执行器声明周期中的最大尺寸。
预订执行:
SchedyledExecutorService接口具有预订执行或重复执行任务而设计的方法,它是一种允许线程池机制的java.util.Timer的泛化,可以预订Runnable或Callable在初始的延迟之后只运行一次,也可以预订一个Runnable对象周期性的运行。
java.util.concurrent.Executors 5.0
ScheduledExecutorService newScheduledThreadPool(int threads)
说明: 返回一个线程池,他使用给定的线程数来调度任务。
ScheduledExecutorService newSingleThreadScheduledExecutor()
说明: 返回一个执行器,它在单一的线程在调度任务。
java.util.concurrent.ScheduledExecutorService 5.0
ScheduledFuture<V> schedule(Callable<V> task, long time, TimenUnit unit)
ScheduledFuture<?> schedule(Runnable task, long time, TimenUnit unit)
说明: 预订在指定时间之后执行任务。
ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimenUnit unit)
说明: 预订在初始的延迟结束后,周期性的运行给定的任务,周瑜长度为period。
ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long initialDelay, long delay, TimenUnit unit)
说明: 预订在初始的延迟结束后周期性的运行给定的任务,在一次调用完成和下次调用开始之间有长度为delay的延迟。
- 同步器: