一 并发
1 共享内存
每一个线程表示一条单独的执行流,有自己的程序计数器,有自己的栈,但线程之间可以共享内存,它们可以访问和操作相同的对象。
import java.util.ArrayList;
import java.util.List;
public class ShareMemoryDemo {
private static int shared = 0;
private static void incrShared() {
shared++;
}
static class ChildThread extends Thread {
List<String> list;
public ChildThread(List<String> list) {
this.list = list;
}
@Override
public void run() {
incrShared();
list.add(Thread.currentThread().getName());
}
}
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String>();
Thread t1 = new ChildThread(list);
Thread t2 = new ChildThread(list);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(shared);
System.out.println(list);
}
}
2
[Thread-0, Thread-1]
大部分情况下,会输出期望的值。当多条执行流可以操作相同的变量时,可能会出现一些意料之外的结果,包括竞态条件和内存可见性问题。
1.1 竞态条件(race condition)
当多个线程访问和操作同一个对象时,最终执行结果与执行顺序相关,可能正确也可能不正确。
public class CounterThread extends Thread {
private static int counter = 0;
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
counter++;
}
}
public static void main(String[] args) throws InterruptedException {
int num = 1000;
Thread[] threads = new Thread[num];
for (int i = 0; i < num; i++) {
threads[i] = new CounterThread();
threads[i].start();
}
for (int i = 0; i < num; i++) {
threads[i].join();
}
System.out.println(counter);
}
}
共享静态变量变量counter,初始值位0,在main方法中创建了1000个线程,每个线程对counter循环加1000次,main线程等待所有线程结束后输出counter的值。期望结果为100万。但输出:
999601
且每次结果都不一样。因为counter++这个操作不是原子操作,分为三个步骤:
- 取counter的当前值
- 在当前值基础上加1
- 将新值重新赋值给counter
怎样解决问题:
- 使用synchronized关键字
- 使用显式锁
- 使用原子变量
1.2 内存可见性
多个线程可以共享访问和操作相同的变量,但是一个线程对一个共享变量的修改,另一个线程不一定马上就能看到,甚至永远也看不到。
public class VisibilityDemo {
private static boolean shutdown = false;
static class HelloThread extends Thread {
@Override
public void run() {
while (!shutdown) {
// do something
}
System.out.println("exit hello");
}
}
public static void main(String[] args) throws InterruptedException {
new HelloThread().start();
Thread.sleep(1000);
shutdown = true;
System.out.println("exit main");
}
}
期望的结果是两个线程都退出,但实际执行时,很可能会发现HelloThread永远都不会退出,也就是说,在HelloThread执行流来看,shutdown永远为false,即是main线程已经更改为了true。
一个线程对内存的修改,另一个线程看不到,一是修改没有及时同步到内存,二是另一个线程根本就没有从内存读。
解决这个问题:
- 使用volatile
- 使用synchronized关键字或显式锁同步
2 理解synchronized
2.1 实例方法
synchronized实例方法实际保护的是同一个对象的方法调用,确保同时只能有一个线程执行。
public class CounterThread extends Thread {
Counter counter;
public CounterThread(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
counter.incr();
}
}
public static void main(String[] args) throws InterruptedException {
int num = 1000;
Counter counter = new Counter();
Thread[] threads = new Thread[num];
for (int i = 0; i < num; i++) {
threads[i] = new CounterThread(counter);
threads[i].start();
}
for (int i = 0; i < num; i++) {
threads[i].join();
}
System.out.println(counter.getCount());
}
}
再具体说,synchronized实例方法保护的是当前实例对象,即this,this对象有一个锁和一个等待队列,锁只能被一个线程持有,其他试图获得同样锁的线程需要等待。执行synchronized实例方法的过程大致如下:
- 尝试获得锁,如果能够获得锁,继续下一步,否则加入等待队列,阻塞并等待唤醒。
- 执行实例方法体代码。
- 释放锁,如果等待队列上有等待的线程,从中取一个并唤醒,如果有多个等待的线程,唤醒哪一个是不一定的,不保证公平性。
synchronized保护的是对象而非代码,只要访问的是同一个对象的synchronized方法,即使是不同的代码,也会被同步顺序执行。
因此,一般在保护变量时,需要再所有访问该变量的方法上加上synchd。
2.2 静态方法
synchronized同样适用于静态方法。
public class StaticCounter {
private static int count = 0;
public static synchronized void incr() {
count++;
}
public static synchronized int getCount() {
return count;
}
}
对于实例方法,保护的是当前实例对象this;
对于静态方法,保护的是类对象,这里是StaticCounter.class。实际上,每个对象都有一个锁和一个等待队列,类对象也不例外。
2.3 代码块
synchronized还可以用于包装代码块。
public class Counter {
private int count;
public void incr() {
synchronized (this) {
count++;
}
}
public int getCount() {
synchronized (this) {
return count;
}
}
}
synchronized括号里面的就是保护的对象,对于实例方法,就是this,{}里面是同步执行的代码。对于前面的StaticCounter类,等价的代码如下:
public class StaticCounter {
private static int count = 0;
public static void incr() {
synchronized (StaticCounter.class){
count++;
}
}
public static int getCount() {
synchronized (StaticCounter.class) {
return count;
}
}
}
synchronized同步的对象可以是任意对象,任意对象都有一个锁的等待队列,或者说,任何对象都可以作为锁对象。如,Counter类的等价代码也可以如下代码所示:
public class Counter {
private int count;
private Object lock = new Object();
public void incr() {
synchronized (lock) {
count++;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
3 synchronized的三个特性
- 可重入性
- 内存可见性
- 死锁
可重入是通过记录锁的持有线程和持有数量来实现的。
synchronized除了保证原子操作外,还可以保证内存可见性,在释放锁时,所有写入都会写回内存,而获得锁后,都会从内存中读最新数据。
不过如果只是为了保证内存可见性,使用synchronized的成本比较高,有一个轻量级的方式,就是给变量加修饰符volatile。
死锁
public class DeadLockDemo {
private static Object lockA = new Object();
private static Object lockB = new Object();
private static void startThreadA() {
Thread aThread = new Thread() {
@Override
public void run() {
synchronized (lockA) {
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
}
synchronized (lockB) {
}
}
}
};
aThread.start();
}
private static void startThreadB() {
Thread bThread = new Thread() {
@Override
public void run() {
synchronized (lockB) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockA) {
}
}
}
};
bThread.start();
}
public static void main(String[] args) {
startThreadA();
startThreadB();
}
}
运行后aThread和bThread陷入了相互等待。
应该尽量避免在持有一个锁的同时去申请另一个锁,如果确实需要多个锁,所有代码都应该按照相同的顺序去申请锁。如上面代码,均先申请lockA,再申请lockB。
public class DeadLockDemo {
private static Object lockA = new Object();
private static Object lockB = new Object();
private static void startThreadA() {
Thread aThread = new Thread() {
@Override
public void run() {
synchronized (lockA) {
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
}
synchronized (lockB) {
}
}
}
};
aThread.start();
}
private static void startThreadB() {
Thread bThread = new Thread() {
@Override
public void run() {
synchronized (lockA) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
}
}
}
};
bThread.start();
}
public static void main(String[] args) {
startThreadA();
startThreadB();
}
}
迭代异常
对于同步容器,虽然单个操作是安全的,但迭代并不是。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class startModify {
private static void startModifyThread(final List<String> list) {
Thread modifyThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
list.add("item " + i);
try {
Thread.sleep((int) (Math.random()+10));
} catch (InterruptedException e) {
}
}
}
});
modifyThread.start();
}
private static void startIteratorThread(final List<String> list) {
Thread iteratorThread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
for (String str : list) {
}
}
}
});
iteratorThread.start();
}
public static void main(String[] args) {
final List<String> list = Collections.synchronizedList(new ArrayList<String>());
startIteratorThread(list);
startModifyThread(list);
}
}
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at beforeJob.startModify$2.run(startModify.java:29)
at java.lang.Thread.run(Thread.java:748)
如果在遍历的同时容器发生了结构性的变化,就会抛出该异常。如果要避免这个异常,需要在遍历的时候给整个容器对象加锁。
4 并发容器
Java中有许多专为并发设计的容器类。
- CopyOnWriteArrayList
- ConcurrentHashMap
- ConcurrentLinkedQueue
- ConcurrentSkipListSet
5 线程的基本协作机制
协作场景:
- 生产者/消费者协作模式
- 同时开始
- 等待结束
- 异步结果
- 集合点
wait/notify
public class WaitThread extends Thread {
private volatile boolean fire = false;
@Override
public void run() {
try {
synchronized (this) {
while (!fire) {
wait();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void fire() {
this.fire = true;
notify();
}
public static void main(String[] args) throws InterruptedException {
WaitThread waitThread = new WaitThread();
waitThread.start();
Thread.sleep(1000);
System.out.println("fire");
waitThread.fire();
}
}
在设计多线程协作时,需要想清楚协作的共享变量和条件是什么,这是协作的核心。
生产者/消费者模式
生产者/消费者协作队列:
import java.util.ArrayDeque;
import java.util.Queue;
public class MyBlockingQueue<E> {
private Queue<E> queue = null;
private int limit;
public MyBlockingQueue(int limit) {
this.limit = limit;
queue = new ArrayDeque<>(limit);
}
public synchronized void put(E e) throws InterruptedException {
while (queue.size() == limit) {
wait();
}
queue.add(e);
notifyAll();
}
public synchronized E take() throws InterruptedException {
while (queue.isEmpty()) {
wait();
}
E e = queue.poll();
notifyAll();
return e;
}
}
生产者:
public class Producer extends Thread {
MyBlockingQueue<String> queue;
public Producer(MyBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
int num = 0;
try {
while (true) {
String task = String.valueOf(num);
queue.put(task);
System.out.println("producer task " + task);
num++;
Thread.sleep((int) (Math.random() * 100));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
消费者:
public class Consumer extends Thread {
MyBlockingQueue<String> queue;
public Consumer(MyBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while (true) {
String task = queue.take();
System.out.println("handle task " + task);
Thread.sleep((int) (Math.random() * 100));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试代码:
public class Test {
public static void main(String[] args) {
MyBlockingQueue<String> queue = new MyBlockingQueue<>(10);
new Producer(queue).start();
new Consumer(queue).start();
}
}
同时开始
协作对象:
public class FireFlag {
private volatile boolean fired = false;
public synchronized void waitForFire() throws InterruptedException {
while (!fired) {
wait();
}
}
public synchronized void fire() {
this.fired = true;
notifyAll();
}
}
比赛运动员:
public class Racer extends Thread {
FireFlag fireFlag;
public Racer(FireFlag fireFlag) {
this.fireFlag = fireFlag;
}
@Override
public void run() {
try {
this.fireFlag.waitForFire();
System.out.println("start run " + Thread.currentThread().getName());
} catch (InterruptedException e) {
}
}
}
主程序代码:
public class Test {
public static void main(String[] args) throws InterruptedException {
int num = 10;
FireFlag fireFlag = new FireFlag();
Thread[] races = new Thread[num];
for (int i = 0; i < num; i++) {
races[i] = new Racer(fireFlag);
races[i].start();
}
Thread.sleep(1000);
fireFlag.fire();
}
}
等待结束
协作对象:
public class MyLatch {
private int count;
public MyLatch(int count) {
this.count = count;
}
public synchronized void await() throws InterruptedException {
while (count > 0) {
wait();
}
}
public synchronized void countDown() {
count--;
if (count <= 0) {
notifyAll();
}
}
}
使用MyLatch的工作子线程:
public class Worker extends Thread {
MyLatch latch;
public Worker(MyLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
Thread.sleep((int)(Math.random()*1000));
this.latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
主程序:
public class Test {
public static void main(String[] args) throws InterruptedException {
int workedNum = 100;
MyLatch latch = new MyLatch(workedNum);
Worker[] workers = new Worker[workedNum];
for (int i = 0; i < workedNum; i++) {
workers[i] = new Worker(latch);
workers[i].start();
}
latch.await();
System.out.println("collect worked result");
}
}
异步结果
在Java中,表示子任务的接口是Callable,声明为:
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
为表示异步调用的结果,定义一个接口MyFuture,如下:
public interface MyFuture<V> {
V get() throws Exception;
}
集合点
各个线程先是分头行动,各自到达一个集合点,在集合点需要集齐所有线程,交换数据,然后再进行下一步动作。
协作对象:
public class AssemblePoint {
private int n;
public AssemblePoint(int n) {
this.n = n;
}
public synchronized void await() throws InterruptedException {
if (n > 0) {
n--;
if (n == 0) {
notifyAll();
} else {
while (n != 0) {
wait();
}
}
}
}
}
集合点协作:
public class AssemblePointDemo {
static class Tourist extends Thread {
AssemblePoint ap;
public Tourist(AssemblePoint ap) {
this.ap = ap;
}
@Override
public void run() {
try {
Thread.sleep((int) (Math.random()*1000));
ap.await();
System.out.println("arrived");
} catch (InterruptedException e) {
}
}
}
public static void main(String[] args) {
int num = 10;
Tourist[] threads = new Tourist[num];
AssemblePoint ap = new AssemblePoint(num);
for (int i = 0; i < num; i++) {
threads[i] = new Tourist(ap);
threads[i].start();
}
}
}
6 线程的中断
在Java中,停止一个线程的主要机制是中断,中断并不是强迫中止 一个线程,它是一种协作机制,是给线程传递一个取消信号,但是由线程来决定如何以及何时退出。
线程对中断的反应
interrupt()对线程的影响与线程的状态和在进行的IO操作有关。
线程的状态有:
- RUNNABLE:线程正在运行或具备运行条件只是在等待操作系统调度
- WAITING/TIMED_WAITING:线程在等待某个条件或超时
- BLOCKED:线程在等待锁,试图进入同步块
- NEW/TERMINATED:线程还未启动或已结束
RUNNABLE:如果线程正在运行中,且没有执行IO操作,interrupt()只是会设置线程的中断标志位,没有任何其他作用。
WAITING/TIMED_WAITING:线程调用join/wait/sleep方法进入WAITING或TIMED_WAITING状态,在这些状态时,对线程对象调用interrupt()会使得该线程抛出InterruptException。需要注意的是,抛出异常后,中断标志位会被清空,而不是被设置。
BLOCKED:如果线程在等待锁,对线程对象调用interrupt()只是会设置线程的中断标志位,线程依旧会处于BLOCKED状态。即,interrupt()并不能使一个在等待锁的线程真正“中断”。
NEW/TERMINATE:如果线程尚未启动(NEW),或者已经结束(TERMINATED),则调用interrupt()对它没有任何效果,中断标志位也不会被设置。
二 并发包的基础
- 原子变量
- 显式锁
- 显式条件
1 原子变量和CAS
Java并法包中的基本原子变量有一下几种:
- AtomicBoolean:原子Boolean类型,常用来在程序中表示一个标志位
- AtomicInteger:原子Integer类型
- AtomicLong:原子Long类型,常用来在程序中生成唯一序列号
- AtomicReference:原子引用类型,用来以原子方式更新复杂类型
1.1 AtomicInteger
之所以称为原子变量,是因为它包含一些以原子方式实现组合操作的方法。
- public final int getAndSet(int newValue)
- public final int getAndIncrement()
- ......
这些方法的实现都依赖另一个public方法:
public final boolean compareAndSet(int expect,int update)
compareAndSet是一个非常重要的方法,比较并设置,简称为CAS。两个参数expect和update,以原子方式实现如下功能:如果当前值等于expect,则更新为update,否则不更新,如果更新成功,返回true,否则返回false。
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerDemo {
private static AtomicInteger counter = new AtomicInteger(0);
static class Visitor extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
counter.incrementAndGet();
}
}
}
public static void main(String[] args) throws InterruptedException {
int num = 1000;
Thread[] threads = new Thread[num];
for (int i = 0; i < num; i++) {
threads[i] = new Visitor();
threads[i].start();
}
for (int i = 0; i < num; i++) {
threads[i].join();
}
System.out.println(counter.get());
}
}
1000000
1.2 基本原理和思维
AtomicInteger的主要内部成员是:
private volatile int value;
synchronized代表一种阻塞式算法。
原子变量的更新是非阻塞式的。
1.3 实现锁
基于CAS,除了可以实现乐观非阻塞算法之外,还可以实现悲观阻塞式算法,比如锁。
Java并发包中的所有阻塞式工具、容器、算法也都是基于CAS的。用AtomicInteger实现一个锁MyLock。
import java.util.concurrent.atomic.AtomicInteger;
public class MyLock {
private AtomicInteger status = new AtomicInteger(0);
public void lock() {
while (!status.compareAndSet(0,1)) {
Thread.yield();
}
}
public void unlock() {
status.compareAndSet(1,0);
}
}
2 显式锁
Java并发包中的显式锁接口和类位于包java.util.concurrent.locks下,主要接口和类有:
- 锁接口Lock,主要实现类ReentrantLock
- 读写锁接口ReadWriteLock,主要实现类是ReentrantReadWriteLock
2.1 接口Lock
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
2.2 可重入锁ReentrantLock
Lock接口的主要实现类是ReentrantLock,它的基本用法lock/unlock实现了与synchronized一样的语义,包括:
- 可重入,一个线程在持有一个锁的前提下,可以继续获得该锁
- 可以解决竞态条件问题
- 可以保证内存可见性
ReentrantLock有两个构造函数:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
使用ReentrantLock实现Counter,代码为:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private final Lock lock = new ReentrantLock();
private volatile int count;
public void incr() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
使用tryLock(),可以避免死锁。
ReentrantLock内部使用AQS,有三个内部类:
- abstract static class Sync extends AbstractQueuedSynchronizer
- static final class NonfairSync extends Sync
- static final class FairSync extends Sync
Sync是抽象类,NonfairSync是fair为false时使用的类,FairSync是fire为true是使用的类。
对比ReentrantLock和synchronized
- 相比synchronized,ReentrantLock可以实现与synchronized相同的语义,而且支持以非阻塞方式获取锁,可以相应中断,可以限时,更为灵活。
- 不过,synchronized的使用更为简单,写的代码更少,也更不容易出错。
synchronized代表一种声明式编程思维,程序员更多的是表达一种同步声明,有Java系统负责具体实现,程序员不知道其实现细节;
显式锁代表一种命令式编程思维,程序员实现所有细节。
3 显式条件
显式锁与synchronized相对应,而显式条件与wait/notify相对应。
wait/notify与synchronized配合使用,显式条件与显式锁配合使用。条件与锁相关联,创建条件变量需要通过显式锁。Condition表示条件变量,是一个接口。
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
使用显式条件进行协作:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class WaitThread extends Thread {
private volatile boolean fire = false;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
@Override
public void run() {
try {
lock.lock();
try {
while (!fire) {
condition.await();
}
} finally {
lock.unlock();
}
System.out.println("fired");
} catch (InterruptedException e) {
Thread.interrupted();
}
}
public void fire() {
lock.lock();
try {
this.fire = true;
condition.signal();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
WaitThread waitThread = new WaitThread();
waitThread.start();
Thread.sleep(1000);
System.out.println("fire");
waitThread.fire();
}
}
生产者/消费者模式
使用显式锁/条件实现的阻塞队列
三 并发容器
- 写时复制的List和Set
- ConcurrentHashMap
- 基于SkipList的Map和Set
- 各种并发队列
1 写时复制的List和Set
- CopyOnWriteArrayList
- CopyOnWriteArraySet
CopyOnWriteArraySet内部是通过CopyOnWriteList实现的,其声明为:
private final CopyOnWriteArrayList<E> al;
两者适用于读远多于写、集合不太大的场合,它们采用了写时复制,这是计算机程序中一种重要的思维和技术。
2 ConcurrentHashMap
是一个常用的并发容器,是HashMap的并发版本,与HashMap相比,有如下特点:
- 并发安全
- 直接支持一些原子复合操作
- 支持高并发,读操作完全并行,写操作支持一定的并行
- 与同步容器Collection.synchronizedMap相比,迭代不用加锁,不会抛出ConcurrentModificationException
- 弱一致性
高并发的基本机制
在Java7中,主要有两点:
- 分段锁
- 读不需要锁
同步容器使用synchronized,所有方法竞争同一个锁;而ConcurrentHashMap采用分段锁技术,将数据分为多个段,而每个段有一个独立的锁,而每个段有一个独立的锁,每一个段相当于独立的哈希表,分段的依据也是哈希表值,无论是保存键值对还是根据键查找,都会先根据键的哈希值映射到段,再在段对应的哈希表上进行操作。
3 基于跳表的Map和Set
Java并发包中与TreeMap和TreeSet对应的并发版本是ConcurrentSkipListMap和ConcurrentSkipListSet。
4 并发队列
Java并发包中提供了丰富的队列类。
- 无锁非阻塞并发队列:ConcurrentLinkedQueue和ConcurrentLinkedDeque
- 普通阻塞队列:基于数组的ArrayBlockingQueue,基于链表的LinkedBlockingQueue和LinkedBlockingDeque
- 优先级阻塞队列:PriorityBlockingQueue
- 延时阻塞队列:DelayQueue
- 其他阻塞队列:SynchronousQueue和LinkedTransferQueue
四 异步任务执行服务
在之前的介绍中,线程Thread既表示要执行的任务,又表示执行的机制。Java并发包提供了一套框架,大大简化了执行异步任务所需的开发。这套框架引入了一个“执行任务”的概念,它将“任务的提交”和“任务的执行”相分离,“执行任务”封装了任务执行的细节,对于任务提交者而言,它可以关注于任务本身,如提交任务、获取结果、取消任务,而不需要关注任务执行的细节,如线程创建、任务调度、线程关闭等。
1 基本概念和原理
异步任务执行服务的基本接口、用法和实现原理。
基本接口:
- Runnable和Callable:表示要执行的异步任务
- Executor和ExecutorService:表示要执行服务
- Future:表示异步任务的结果
固定延时:
import java.util.Timer;
import java.util.TimerTask;
public class TimerFixedDelay {
static class LongRunningTask extends TimerTask {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("long running finished");
}
}
static class FixedDelayTask extends TimerTask {
@Override
public void run() {
System.out.println(System.currentTimeMillis());
}
}
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new LongRunningTask(),10);
timer.schedule(new FixedDelayTask(),100,1000);
}
}
五 同步和协作工具类
Java并发包中有一些专门的同步和协作的工具类,包括:
- 读写锁 ReentrantReadWriteLock
- 信号量 Semaphore
- 倒计时门栓 CountDownLatch
- 循环栅栏 CyclicBarrier
信号量类Semaphore限制对资源的并发访问数,有两个构造方法:
- public Semaphore(int permits)
- public Semaphore(int permits, boolean fair)
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
static class Tourist extends Thread {
CyclicBarrier barrier;
public Tourist(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
Thread.sleep((int) (Math.random()*1000));
barrier.await();
System.out.println(this.getName() + " arrived A " + System.currentTimeMillis());
Thread.sleep((int) (Math.random()*1000));
barrier.await();
System.out.println(this.getName() + " arrived B " + System.currentTimeMillis());
} catch (InterruptedException e) {
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
int num = 3;
Tourist[] threads = new Tourist[num];
CyclicBarrier barrier = new CyclicBarrier(num, new Runnable() {
@Override
public void run() {
System.out.println("all arrived " + System.currentTimeMillis() + " executed by " + Thread.currentThread().getName());
}
});
for (int i = 0; i < num; i++) {
threads[i] = new Tourist(barrier);
threads[i].start();
}
}
}
all arrived 1579507780205 executed by Thread-1
Thread-1 arrived A 1579507780205
Thread-2 arrived A 1579507780205
Thread-0 arrived A 1579507780205
all arrived 1579507781163 executed by Thread-2
Thread-2 arrived B 1579507781163
Thread-1 arrived B 1579507781163
Thread-0 arrived B 1579507781163
理解ThreadLocal
public class Test {
static ThreadLocal<Integer> local = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
Thread child = new Thread() {
@Override
public void run() {
System.out.println("child thread initial: " + local.get());
local.set(200);
System.out.println("child thread final: " + local.get());
}
};
local.set(100);
child.start();
child.join();
System.out.println("main thread final: " + local.get());
}
}
child thread initial: null
child thread final: 200
main thread final: 100
说明,main线程对local变量的设置对child线程不起作用,child线程对local变量的改变也不会影响main线程。它们访问的虽然是同一个变量local,但每个线程都有自己的独立的值,这就是线程本地变量的含义。