文章目录
前言
首先,为啥要使用框架来进行并发编程呢?
1、分离任务的创建和执行者的创建
2、线程的重复利用(new线程代价很大)
一、Executor并发编程框架
从java JDK5之后就开始提供ExecutorFrameWork并发编程框架,在java.util.concurrent.*中。
这个框架提供共享线程池,只要把任务创建好,扔进线程池中,程序员不需要关心线程池怎么执行的,直接拿到执行结果就好了。
主要类:
- Executors.newCachedThreadPool/newFixedThreadPool(创建线程池)
- ExecutorService (线程池服务)
- Callable具体的逻辑对象(线程类)
- Future 返回结果
直接用例子来看怎么使用吧。
运行类:
package executor;
import java.util.concurrent.*;
import java.util.ArrayList;
import java.util.List;
public class SumTest {
public static void main(String[] args) {
//线程池
ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(4);
//返回结果
List<Future<Integer>> resultList = new ArrayList<>();
for(int i=0; i<10; i++) {
SumTask calcultor = new SumTask(i*100+1, (i+1)*100);
Future<Integer> result = executor.submit(calcultor);
resultList.add(result);
}
//轮询查看任务结果
do {
System.out.printf("Main:已经完成多少任务:%d\n", executor.getCompletedTaskCount());
for (int i=0; i<resultList.size(); i++) {
Future<Integer> result = resultList.get(i);
System.out.printf("Main: Task %d: %s\n",i,result.isDone());
}
}while(executor.getCompletedTaskCount()<resultList.size());
//综合计算结果
int total = 0;
for( int i=0; i<resultList.size(); i++) {
Future<Integer> result = resultList.get(i);
Integer sum = null;
try {
sum = result.get();
total = total + sum;
}catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
System.out.printf("1-1000的总和:" + total);
// 关闭线程池
executor.shutdown();
}
}
任务类:
package executor;
import java.util.Random;
import java.util.concurrent.Callable;
public class SumTask implements Callable<Integer>{
//定义线程区间
private int startNumber;
private int endNumber;
public SumTask(int startNumber, int endNumber) {
this.startNumber = startNumber;
this.endNumber = endNumber;
}
public Integer call() throws Exception {
int sum = 0;
for(int i= startNumber; i<=endNumber; i++) {
sum = sum+ i;
}
Thread.sleep(new Random().nextInt(1000));
System.out.printf("%s:%d\n", Thread.currentThread().getName(),sum);
return sum;
}
}
在自己跟着教程敲这段代码的时候呢,发现要注意这几点:
- Callable 注意是这个接口,不再是runnable接口了,实现的方法也是call()方法,但是除了名字不一样,里面的写法什么的与之前的完全一致。
- 初始化线程池可以预先定义大小,一般为CPU核数的2倍或者4倍,如果没有定义大小,线程池会一直增加线程数。
- 不需要再对任务包装为thread类了,不需要调用start方法了,直接submit就可以了,线程池自动执行。
- 线程的执行结果放在了Future类中,如果存在多个结果,通过
Future<Integer> result = resultList.get(i);
这种get可以获取到该结果,再通过result.isDone()
判断该线程是否已经执行完。
线程池这玩意现在用的很多,但说实话自己还不是非常清楚咋用,先种个草。
二、Fork-join并发编程框架
java7提出来的框架,用于分治编程(分解、治理、合并),适用于整体任务量不好确定的场合(最小任务可确定)
主要类:
- ForkJoinPool 任务池
- RecursiveAction
- RecursiveTask 可递归的方法,必须事先compute方法。
贴代码看看好理解
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
//分任务求和
public class SumTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建执行线程池
ForkJoinPool pool = new ForkJoinPool();
//ForkJoinPool pool = new ForkJoinPool(4);
//创建任务
SumTask task = new SumTask(1, 10000000);
//提交任务
ForkJoinTask<Long> result = pool.submit(task);
//等待结果
do {
System.out.printf("Main: Thread Count: %d\n",pool.getActiveThreadCount());
System.out.printf("Main: Paralelism: %d\n",pool.getParallelism());
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (!task.isDone());
//输出结果
System.out.println(result.get().toString());
}
}
任务类,写的有点递归分解的意思。
import java.math.BigInteger;
import java.util.concurrent.RecursiveTask;
//分任务求和
public class SumTask extends RecursiveTask<Long> {
private int start;
private int end;
public SumTask(int start, int end) {
this.start = start;
this.end = end;
}
public static final int threadhold = 5;
@Override
protected Long compute() {
Long sum = 0L;
// 如果任务足够小, 就直接执行
boolean canCompute = (end - start) <= threadhold;
if (canCompute) {
for (int i = start; i <= end; i++) {
sum = sum + i;
}
} else {
// 任务大于阈值, 分裂为2个任务
int middle = (start + end) / 2;
SumTask subTask1 = new SumTask(start, middle);
SumTask subTask2 = new SumTask(middle + 1, end);
invokeAll(subTask1, subTask2);
Long sum1 = subTask1.join();
Long sum2 = subTask2.join();
// 结果合并
sum = sum1 + sum2;
}
return sum;
}
}
需要注意的几点
- 实现的接口又变了,实现RecursiveTask接口,并且需要重写compute方法。
- invoke方法,提交线程。.join()方法,等待调用这个方法的线程完成才能够取结果。
- 注意这种递归分解的思路。
三、java并发数据结构
常用的数据结构是线程不安全的。ArrayList,HashMap,HashSet是非同步的,多个线程对其进行读或者写的时候,就会出现数据错乱。
java5之后提供了一些并发的数据结构。
可以分为:
- 阻塞式:集合为空或者满时,等待。
- 非阻塞式:集合为空或者满时,不等待,返回null或者异常。
直接看代码吧:
List
//线程不安全
List<String> unsafeList = new ArrayList<String>();
//线程安全:基于synchronized,效率差
List<String> safeList1 = Collections.synchronizedList(new ArrayList<String>());
//线程安全:基于复制机制,适用于读多写少,非阻塞
CopyOnWriteArrayList<String> safeList2 = new CopyOnWriteArrayList<String>();
map
//线程不安全
Map<Integer,String> unsafeMap = new HashMap<Integer,String>();
//线程安全:效率差
Map<Integer,String> safeMap1 = Collections.synchronizedMap(new HashMap<Integer,String>());
//线程安全:读多写少,非阻塞
ConcurrentHashMap<Integer,String> safeMap2 = new ConcurrentHashMap<Integer,String>();
queue
//线程不安全
Deque<String> unsafeQueue = new ArrayDeque<String>();
//线程安全:非阻塞
ConcurrentLinkedDeque<String> safeQueue1 = new ConcurrentLinkedDeque<String>();
set
//线程不安全
Set<String> unsafeSet = new HashSet<String>();
//线程安全:基于synchronized,效率差
Set<String> safeSet1 = Collections.synchronizedSet(new HashSet<String>());
//线程安全:读多写少,非阻塞
CopyOnWriteArraySet<String> safeSet2 = new CopyOnWriteArraySet<String>();
四、java并发协作控制
现总结一下之前写了啥?
- Thread/Executor/Fork-Join:这三个东西呢,都可以实现多线程。但是呢,相当于创建任务一次执行到底,中间没啥交流,线程之间缺少协作。但实际编程中呢,比如说访问同一个文件,有人写有人读,各个线程需要约定好一些资源占用的规则,只采用上面的玩意就显然达不到这种功能了。
- 不对呀,synchronized不是能够控制线程只能访问一个关键区吗?volatile不是能够只让一个线程对资源进行修改吗?还有sleep/wait/join啥的不是能够对细粒度的控制吗?确实,但是synchronize太过于简单粗暴,性能损失大,不能够完全实现用户需求哈。本节只针对synchronized这个讲讲有啥改进的。
4.1 Lock
功能:能够实现更复杂的临界区结构,功能类似于synchronized但是实现的功能更加复杂。
主要类:
- ReentrantLock :可重入的互斥锁
- ReentrantReadWriteLock:可重入的读写锁
我的理解是,synchronized相当于一把互斥锁,但是lock能够实现的更多,性能还更好。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LockExample {
private static final ReentrantLock queueLock = new ReentrantLock(); //可重入锁
private static final ReentrantReadWriteLock orderLock = new ReentrantReadWriteLock(); //可重入读写锁
/**
* 有家奶茶店,点单有时需要排队
* 假设想买奶茶的人如果看到需要排队,就决定不买
* 又假设奶茶店有老板和多名员工,记单方式比较原始,只有一个订单本
* 老板负责写新订单,员工不断地查看订单本得到信息来制作奶茶,在老板写新订单时员工不能看订单本
* 多个员工可同时看订单本,在员工看时老板不能写新订单
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
//buyMilkTea();
handleOrder(); //需手动关闭
}
public void tryToBuyMilkTea() throws InterruptedException {
boolean flag = true;
while(flag)
{
if (queueLock.tryLock()) {
//queueLock.lock();
long thinkingTime = (long) (Math.random() * 500);
Thread.sleep(thinkingTime);
System.out.println(Thread.currentThread().getName() + ": 来一杯珍珠奶茶,不要珍珠");
flag = false;
queueLock.unlock();
} else {
//System.out.println(Thread.currentThread().getName() + ":" + queueLock.getQueueLength() + "人在排队");
System.out.println(Thread.currentThread().getName() + ": 再等等");
}
if(flag)
{
Thread.sleep(1000);
}
}
}
public void addOrder() throws InterruptedException {
orderLock.writeLock().lock();
long writingTime = (long) (Math.random() * 1000);
Thread.sleep(writingTime);
System.out.println("老板新加一笔订单");
orderLock.writeLock().unlock();
}
public void viewOrder() throws InterruptedException {
orderLock.readLock().lock();
long readingTime = (long) (Math.random() * 500);
Thread.sleep(readingTime);
System.out.println(Thread.currentThread().getName() + ": 查看订单本");
orderLock.readLock().unlock();
}
public static void buyMilkTea() throws InterruptedException {
LockExample lockExample = new LockExample();
int STUDENTS_CNT = 10;
Thread[] students = new Thread[STUDENTS_CNT];
for (int i = 0; i < STUDENTS_CNT; i++) {
students[i] = new Thread(new Runnable() {
@Override
public void run() {
try {
long walkingTime = (long) (Math.random() * 1000);
Thread.sleep(walkingTime);
lockExample.tryToBuyMilkTea();
} catch(InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
);
students[i].start();
}
for (int i = 0; i < STUDENTS_CNT; i++)
students[i].join();
}
public static void handleOrder() throws InterruptedException {
LockExample lockExample = new LockExample();
Thread boss = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
lockExample.addOrder();
long waitingTime = (long) (Math.random() * 1000);
Thread.sleep(waitingTime);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
});
boss.start();
int workerCnt = 3;
Thread[] workers = new Thread[workerCnt];
for (int i = 0; i < workerCnt; i++)
{
workers[i] = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
lockExample.viewOrder();
long workingTime = (long) (Math.random() * 5000);
Thread.sleep(workingTime);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
});
workers[i].start();
}
}
}
需要注意的是:readLock表示读锁,可以多个线程共享,writeLock表示写锁,排他,只能有一个线程拥有。
4.2 Semaphore
信号量:本质是一个计数器,计数器大于0就可以使用,等于零就不能使用,可以设置多个并发量,可以限制为n个访问。
相当于是Lock更近一步,可以控制多个同时访问关键区。
- acquire,获取,信号量减1
- release,释放,信号量加1
其实与lock超像。
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private final Semaphore placeSemaphore = new Semaphore(5);
public boolean parking() throws InterruptedException {
if (placeSemaphore.tryAcquire()) {
System.out.println(Thread.currentThread().getName() + ": 停车成功");
return true;
} else {
System.out.println(Thread.currentThread().getName() + ": 没有空位");
return false;
}
}
public void leaving() throws InterruptedException {
placeSemaphore.release();
System.out.println(Thread.currentThread().getName() + ": 开走");
}
/**
* 现有一地下车库,共有车位5个,由10辆车需要停放,每次停放时,去申请信号量
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
int tryToParkCnt = 10;
SemaphoreExample semaphoreExample = new SemaphoreExample();
Thread[] parkers = new Thread[tryToParkCnt];
for (int i = 0; i < tryToParkCnt; i++) {
parkers[i] = new Thread(new Runnable() {
@Override
public void run() {
try {
long randomTime = (long) (Math.random() * 1000);
Thread.sleep(randomTime);
if (semaphoreExample.parking()) {
long parkingTime = (long) (Math.random() * 1200);
Thread.sleep(parkingTime);
semaphoreExample.leaving();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
parkers[i].start();
}
for (int i = 0; i < tryToParkCnt; i++) {
parkers[i].join();
}
}
}
4.3 latch
同步辅助类,用来同步执行任务的一个或者多个线程,协调各个线程到了某个阶段的时候大家都等等,大家都到了,才一起接着往下走。
主要类和方法
CountDownLatch类
- countDown()计数器减1
- await() 等待latch变为0,如果没有变为0,继续等待。latch变为0之后,将环形所有在此latch上await的线程,解锁他们的await等待。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
/**
* 设想百米赛跑比赛 发令枪发出信号后选手开始跑,全部选手跑到终点后比赛结束
*
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
int runnerCnt = 10;
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(runnerCnt);
for (int i = 0; i < runnerCnt; ++i) // create and start threads
new Thread(new Worker(startSignal, doneSignal)).start();
System.out.println("准备工作...");
System.out.println("准备工作就绪");
startSignal.countDown(); // let all threads proceed
System.out.println("比赛开始");
doneSignal.await(); // wait for all to finish
System.out.println("比赛结束");
}
static class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
public void run() {
try {
startSignal.await();
doWork();
doneSignal.countDown();
} catch (InterruptedException ex) {
} // return;
}
void doWork() {
System.out.println(Thread.currentThread().getName() + ": 跑完全程");
}
}
}
其实这里代码也超好理解,我觉得重要的地方在于怎么构建这个latch任务,注意它是把CountDownLatch对象传到任务中,如果共用的是同一个CountDownLatch对象,他们就会一起等待到该对象的latch变为0.
4.4 barrier
集合点,功能与latch一样,允许多个线程在某一点上进行同步。
他的原理是:通过构造函数设置同步线程的数量,await等待其他线程,到达数量后就放行。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
/**
* 假定有三行数,用三个线程分别计算每一行的和,最终计算总和
* @param args
*/
public static void main(String[] args) {
final int[][] numbers = new int[3][5];
final int[] results = new int[3];
int[] row1 = new int[]{1, 2, 3, 4, 5};
int[] row2 = new int[]{6, 7, 8, 9, 10};
int[] row3 = new int[]{11, 12, 13, 14, 15};
numbers[0] = row1;
numbers[1] = row2;
numbers[2] = row3;
CalculateFinalResult finalResultCalculator = new CalculateFinalResult(results);
CyclicBarrier barrier = new CyclicBarrier(3, finalResultCalculator);
//当有3个线程在barrier上await,就执行finalResultCalculator
for(int i = 0; i < 3; i++) {
CalculateEachRow rowCalculator = new CalculateEachRow(barrier, numbers, i, results);
new Thread(rowCalculator).start();
}
}
}
class CalculateEachRow implements Runnable {
final int[][] numbers;
final int rowNumber;
final int[] res;
final CyclicBarrier barrier;
CalculateEachRow(CyclicBarrier barrier, int[][] numbers, int rowNumber, int[] res) {
this.barrier = barrier;
this.numbers = numbers;
this.rowNumber = rowNumber;
this.res = res;
}
@Override
public void run() {
int[] row = numbers[rowNumber];
int sum = 0;
for (int data : row) {
sum += data;
res[rowNumber] = sum;
}
try {
System.out.println(Thread.currentThread().getName() + ": 计算第" + (rowNumber + 1) + "行结束,结果为: " + sum);
barrier.await(); //等待!只要超过3个(Barrier的构造参数),就放行。
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
class CalculateFinalResult implements Runnable {
final int[] eachRowRes;
int finalRes;
public int getFinalResult() {
return finalRes;
}
CalculateFinalResult(int[] eachRowRes) {
this.eachRowRes = eachRowRes;
}
@Override
public void run() {
int sum = 0;
for(int data : eachRowRes) {
sum += data;
}
finalRes = sum;
System.out.println("最终结果为: " + finalRes);
}
}
创建方法还是一样的,把CyclicBarrier对象传到任务里,达到数量后会自动进行回调,注意这里的回调也是采用多线程run方法???这里还不是很清楚。
4.5 phaser
在每一阶段结束的位置对线程进行同步,当所有的线程都到达了这一步,再进行下一步,与上面的barrier不同的是可以多次来进行使用。
Phaser
- arrive()
- arriveAndAwaitAdvance() 所有的线程进行等待
import java.util.concurrent.Phaser;
public class PhaserExample {
/**
* 假设举行考试,总共三道大题,每次下发一道题目,等所有学生完成后再进行下一道
*
* @param args
*/
public static void main(String[] args) {
int studentsCnt = 5;
Phaser phaser = new Phaser(studentsCnt);
for (int i = 0; i < studentsCnt; i++) {
new Thread(new Student(phaser)).start();
}
}
}
class Student implements Runnable {
private final Phaser phaser;
public Student(Phaser phaser) {
this.phaser = phaser;
}
@Override
public void run() {
try {
doTesting(1);
phaser.arriveAndAwaitAdvance(); //等到5个线程都到了,才放行
doTesting(2);
phaser.arriveAndAwaitAdvance();
doTesting(3);
phaser.arriveAndAwaitAdvance();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void doTesting(int i) throws InterruptedException {
String name = Thread.currentThread().getName();
System.out.println(name + "开始答第" + i + "题");
long thinkingTime = (long) (Math.random() * 1000);
Thread.sleep(thinkingTime);
System.out.println(name + "第" + i + "道题答题结束");
}
}
4.6 exchanger
允许在并发线程中相互交换消息,允许在2个线程中定义同步点,当两个线程都达到同步点,他们交换数据结构
Exchanger
- exchange(),线程双方互相交互数据
- 交互数据是双向的
两个线程都执行到同一个Exchanger的exchange()方法时,就交换一下数据。
import java.util.Scanner;
import java.util.concurrent.Exchanger;
public class ExchangerExample {
/**
* 本例通过Exchanger实现学生成绩查询,简单线程间数据的交换
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
Exchanger<String> exchanger = new Exchanger<String>();
BackgroundWorker worker = new BackgroundWorker(exchanger);
new Thread(worker).start();
Scanner scanner = new Scanner(System.in);
while(true) {
System.out.println("输入要查询的属性学生姓名:");
String input = scanner.nextLine().trim();
exchanger.exchange(input); //把用户输入传递给线程
String value = exchanger.exchange(null); //拿到线程反馈结果
if ("exit".equals(value)) {
break;
}
System.out.println("查询结果:" + value);
}
scanner.close();
}
}
class BackgroundWorker implements Runnable {
final Exchanger<String> exchanger;
BackgroundWorker(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
while (true) {
try {
String item = exchanger.exchange(null);
switch (item) {
case "zhangsan":
exchanger.exchange("90");
break;
case "lisi":
exchanger.exchange("80");
break;
case "wangwu":
exchanger.exchange("70");
break;
case "exit":
exchanger.exchange("exit");
return;
default:
exchanger.exchange("查无此人");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
总结
面对敌人的言行逼供,我一句话都没说,只是一五一十地把它写下来。