学习路径:基础入门-》初步应用-》高级-》源码分析
并发编程学习目录:
1,线程基础、线程之间的共享和协作
2,线程的并发工具类
3,原子操作CAS
4,显示锁和AQS
5,并发容器
6,线程池和Exector框架
7,线程安全
8a,实战项目-并发任务执行框架
8b,实战项目-性能优化实战
9,JMM和底层原理
10,java8新增的并发
什么是分而治之?
十大计算机经典算法:
*快速排序、
堆排序、
*归并排序、
*二分查找、
线性查找、
深度优化、
广度优化、
Dijkstra、
动态规划:
朴素贝叶斯分类,
有几种属于分而治之?(打※号的属于)
同步用法:ForkJoinPool 的 invoke() 方法是同步提交的,就是调用了 invoke() 方法后主线程后面的代码要等待统计完才会执行。
异步用法:ForkJoinPool 的 execute() 方法是异步提交的,就是调用了 execute() 方法后主线程面的代码会继续执行。没有返回值。
异步用法:ForkJoinPool 的 submit() 方法是异步提交的,就是调用了 submit() 方法后主线程面的代码会继续执行。有返回值。
有返回值:RecursiveTask< V >
没有返回值:RecursiveAction
compute() 方法,
java 中 Arrays 中的 parallelSort() 方法中使用到了 Fork-Join
1,Fork-Join 的同步用法,同时演示返回结果值:统计整形数组中所有元素的和
代码示例:
公共类
package com.h.concurrent.ch2.forkjoin.sum;
import java.util.Random;
/**
* @author h
* @Description 公共类
* @createTime 2020年03月16日 22:08
*/
public class MakeArray {
// 数组长度
public static final int ARRAY_LENGTH = 4000;
public static int[] makeArray() {
// new一个随机数发生器
Random random = new Random();
int[] result = new int[ARRAY_LENGTH];
for (int i = 0; i < ARRAY_LENGTH; i++) {
// 用随机数填充数组
result[i] = random.nextInt(ARRAY_LENGTH * 3);
}
return result;
}
}
使用单线程,直接计算:
package com.h.concurrent.ch2.forkjoin.sum;
import java.util.concurrent.TimeUnit;
/**
* @author h
* @Description 用单线程做求和
* 不加休眠时间时:The count is 23759914 spend time: 0
* 添加休眠时间后:The count is 23712059 spend time: 4225
* @createTime 2020年03月16日 22:12
*/
public class SumNormal {
public static void main(String[] args) {
int count = 0;
int[] src = MakeArray.makeArray();
long start = System.currentTimeMillis();
for (int i = 0; i < src.length; i++) {
try { TimeUnit.MILLISECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}
count = count + src[i];
}
System.out.println("The count is " + count
+ " spend time: " + (System.currentTimeMillis() - start));
}
}
使用 Fork-Join(继承 RecursiveTask 的方式):
package com.h.concurrent.ch2.forkjoin.sum;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
/**
* @author h
* @Description 使用fork-join进行求和 RecursiveTask 有返回值的
* 步骤:
* 1,继承 RecursiveTask>/RecursiveAction
* 2,重写 compute() 方法,
* 满足条件时
* 3,进行逻辑处理,
* 不满足条件时继续拆分任务。
* 4,调用父类的 invokeAll 方法,把需要继续拆分的任务交给 ForkJoinTask 继续拆分处理
* 5,返回处理的结果 return left.join() + right.join(); 如果是继承 RecursiveAction 类则不需要返回
* 6,创建出池的实例 ForkJoinPool,ForkJoinPool pool = new ForkJoinPool();
* 7,调用 pool.invoke(sumTask); 方法,该方法是同步方法
* 8,调用 sumTask.join() 获取返回结果,如果是不需要获取返回值时,可以不调用。
* @createTime 2020年03月16日 22:17
*/
public class SumArrayByForkJoin {
private static class SumTask extends RecursiveTask<Integer> {
// 阈值
private final static int THRESHOLD = MakeArray.ARRAY_LENGTH / 10;
private int[] src; // 要进行拆分的任务
private int fromIndex;
private int toIndex;
public SumTask(int[] src, int fromIndex, int toIndex) {
this.src = src;
this.fromIndex = fromIndex;
this.toIndex = toIndex;
}
@Override
protected Integer compute() {
// 满足条件时,进行求和
if (toIndex - fromIndex < THRESHOLD) {
System.out.println(" from Index = "+ fromIndex + " to Index = " + toIndex);
int count = 0;
for (int i = fromIndex; i <= toIndex; i++) {
count = count + src[i];
}
return count;
} else {
// 不满足条件时,继续拆分任务
// fromIndex....mid....toIndex
int mid = (fromIndex + toIndex)/2;
SumTask left = new SumTask(src, fromIndex, mid);
SumTask right = new SumTask(src, mid + 1, toIndex);
// 调用父类的 invokeAll 方法,把需要继续拆分的任务交给 ForkJoinTask 继续拆分处理
invokeAll(left, right);
return left.join() + right.join();
}
}
}
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
int[] src = MakeArray.makeArray();
SumTask sumTask = new SumTask(src, 0, src.length - 1);
long start = System.currentTimeMillis();
pool.invoke(sumTask);
System.out.println(" Task is Running....");
System.out.println(" The count is "+ sumTask.join()
+ " spend time: " + (System.currentTimeMillis() - start) + " ms");
}
}
2,Fork-Join 使用的方式二:
计算 0-100 的和,步骤如下:
package com.h.concurrent.ch2.forkjoin.sum;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
/**
* @author h
* @Description ForkJoin 的使用方式二
* @createTime 2020年03月17日 22:08
*/
public class SumByForkJoin extends RecursiveTask<Integer> {
private static final int THRESHOLD = 10;
private int begin;
private int end;
private int result;
public SumByForkJoin(int begin, int end) {
this.begin = begin;
this.end = end;
}
public int getBegin() {
return begin;
}
public void setBegin(int begin) {
this.begin = begin;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
@Override
protected Integer compute() {
if ((end - begin) <= THRESHOLD) {
for (int i = begin; i <= end; i++) {
result = result + i;
}
} else {
int mid = (begin + end)/2;
SumByForkJoin myTask01 = new SumByForkJoin(begin, mid);
SumByForkJoin myTask02 = new SumByForkJoin(mid + 1, end);
myTask01.fork();
myTask02.fork();
result = myTask01.join() + myTask02.join();
}
return result;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1,创建任务池,实际上就是线程池
ForkJoinPool threadPool = new ForkJoinPool();
// 2,创建任务类
SumByForkJoin myTask = new SumByForkJoin(0, 100);
// 3,通过线程池,执行任务,编写任务拆分规则
ForkJoinTask<Integer> submit = threadPool.submit(myTask);
// 4,获取返回结果
Integer result = submit.get();
System.out.println(result);
// 5,关闭线程池
threadPool.shutdown();
}
}
3,Fork-Join 的异步用法,同时演示不要求返回值:便利指定目录(含子目录)寻找指定类型文件
代码示例如下:
package com.h.concurrent.ch2.forkjoin;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveAction;
/**
* 类说明:遍历指定目录(含子目录)找寻指定类型文件
*/
public class FindDirsFiles extends RecursiveAction {
private File path;
public FindDirsFiles(File path) {
this.path = path;
}
@Override
protected void compute() {
//
List<FindDirsFiles> subTask = new ArrayList<>();
//
File[] files = path.listFiles();
if (files != null) {
for (File file : files) {
if (!file.isDirectory()) {
if (file.getAbsolutePath().endsWith(".txt")) {
System.out.println(file.getAbsolutePath());
}
} else {
FindDirsFiles findDirsFiles = new FindDirsFiles(file);
subTask.add(findDirsFiles);
findDirsFiles.fork(); // 方式一:直接使用任务类的 fork() 方法
// findDirsFiles.join();
}
}
// 方式二:通过调用 ForkJoinPool线程池的 invokeAll(subTask) 方法来调用所有任务;
// 在当前的 ForkJoinPool 上调度所有的任务
// if(!subTask.isEmpty()){
// Collection findDirsFiles = invokeAll(subTask);
// for (FindDirsFiles findDirsFile : findDirsFiles) {
// findDirsFile.join();
// }
// }
}
}
public static void main(String[] args) throws InterruptedException {
// 1,创建任务池
ForkJoinPool pool = new ForkJoinPool();
String path = "F:\\";
// 2,创建任务(可以创建带返回值的和不带返回值的),并编写处理逻辑
FindDirsFiles findDirsFiles = new FindDirsFiles(new File(path));
// 3,通过任务池执行任务,可以同步执行(invoke)和异步执行(execute)
pool.execute(findDirsFiles);
System.out.println("Task is Running......");
Thread.sleep(1);
int otherWork = 0;
for (int i = 0; i < 100; i++) {
otherWork = otherWork + i;
}
System.out.println("Main Thread done sth......,otherWork= " + otherWork);
// 4,获取返回结果
findDirsFiles.join();
System.out.println("Task end....");
}
}
CountDownLatch 也叫 “闭锁”、“发令枪”。应用场景是,当一个或多个工作线程,要等待预备线程或者初始话线程,把相关初始化工作做完了,才让工作线程继续往下执行。
TW1/TW2 是主线程 调用了 await() 方法并且设置了计数为 5。
注意:
package com.h.concurrent.ch2.tools;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* @author h
* @Description 演示 CountDownLatch 的使用
* @createTime 2020年03月17日 23:11
*/
public class UseCountDownLatch {
private static CountDownLatch countDownLatch = new CountDownLatch(5);
private static class InitThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 的 run() 方法开始执行。。。。");
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()+" 线程调用完 countDown()方法后继续执行。。。。");
}
}
private static class Td extends Thread{
@Override
public void run() {
try { TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}
System.out.println(Thread.currentThread().getName()
+ " ready init work step 1st.......");
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()
+ " ready init work step 2st.......");
// countDown() 可以在同一个线程中调用多次
countDownLatch.countDown();
try { TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}
System.out.println(Thread.currentThread().getName()
+ " 计数结束后,Td 继续执行.......");
}
}
private static class TW2 extends Thread{
@Override
public void run() {
System.out.println("TW2 的 run() 方法开始执行。。。。");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("TW2 的 run() 方法执行结束。。。。");
}
}
public static void main(String[] args) throws InterruptedException {
InitThread ta = new InitThread();
ta.setName("Ta");
ta.start();
InitThread tb = new InitThread();
tb.setName("Tb");
tb.start();
InitThread tc = new InitThread();
tc.setName("Tc");
tc.start();
Td td = new Td();
td.setName("Td");
td.start();
TW2 tw2 = new TW2();
tw2.start();
countDownLatch.await();
System.out.println("TW1 do ites work.......");
}
}
控制台打印结果如下:
Ta 的 run() 方法开始执行。。。。
Ta 线程调用完 countDown()方法后继续执行。。。。
TW2 的 run() 方法开始执行。。。。
Tc 的 run() 方法开始执行。。。。
Tc 线程调用完 countDown()方法后继续执行。。。。
Tb 的 run() 方法开始执行。。。。
Tb 线程调用完 countDown()方法后继续执行。。。。
Td ready init work step 1st.......
Td ready init work step 2st.......
TW1 do ites work.......
TW2 的 run() 方法执行结束。。。。
Td 计数结束后,Td 继续执行.......
要等到所有的线程都执行到屏障时,所有线程同时继续往后执行。即 Ta 和 Tb 先达到屏障要等到 Tc 也达到屏障时,三个线程才同时往下执行。
可以通过 BarrierAction 把子线程执行完的结果进行汇总。
注意事项:
package com.h.concurrent.ch2.tools;
import java.util.Map;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;
/**
* @author h
* @Description 演示 CyclicBarrier用法,共3个线程,它们全部完成工作后,
*交出自己的结果,再被统一释放去做自己的事情,而交出的结果被另外的线程拿来拼接字符串
* 注意:
* 只要启动工作线程即可,当有与计数相同数量的工作线程调用了await() 方法,则 BarrierAction 线程会自动启动并执行
* 如果在工作线程多次调用了 cyclicBarrier 的await()方法,则统计线程会执行多次统计。
* @createTime 2020年03月19日 21:40
*/
public class UseCyclicBarrier {
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new BarrierAction());
private static ConcurrentHashMap<String, String> workMap = new ConcurrentHashMap<>();
public static void main(String[] args) {
Thread ta = new Thread(new SubThread());
ta.setName("Ta");
ta.start();
Thread tb = new Thread(new SubThread());
tb.setName("Tb");
tb.start();
Thread tc = new Thread(new SubThread());
tc.setName("Tc");
tc.start();
}
private static class BarrierAction extends Thread{
@Override
public void run() {
System.out.println("BarrierAction 开始执行。。。");
StringBuilder buffer = new StringBuilder("[ ");
for (Map.Entry<String, String> entry : workMap.entrySet()) {
buffer.append(entry.getValue());
buffer.append(" ");
}
buffer.append(" ]");
System.out.println(buffer.toString());
System.out.println("BarrierAction 执行结束。。。");
}
}
private static class SubThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " do sth....");
workMap.put(Thread.currentThread().getName(), Thread.currentThread().getName());
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " do sth.... end");
try {
cyclicBarrier.await();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
控制台打印结果如下:
Ta do sth....
Tc do sth....
Tb do sth....
BarrierAction 开始执行。。。
[ Ta Tb Tc ]
BarrierAction 执行结束。。。
Tb do sth.... end
Ta do sth.... end
Tc do sth.... end
BarrierAction 开始执行。。。
[ Ta Tb Tc ]
BarrierAction 执行结束。。。
如上图所示:
用 Semaphore 实现数据库连接池
Semaphore 注意事项:
Semaphore 如果不调用acquire()方法,只调用了release()方法,那么许可证的个数会不断的上升
公用代码
/**
* @author h
* @Description 数据库连接的平庸实现
* @createTime 2020年03月19日 22:10
*/
public class SqlConnectionImpl implements Connection {
/*获取一个数据库连接*/
public static final SqlConnectionImpl fetchConnection(){
return new SqlConnectionImpl();
}
@Override
public Statement createStatement() throws SQLException {
return null;
}
// 剩余实现方法省略。
}
许可证的个数会不断的上升,没有达到流量控制作用的场景,代码示例
package com.h.concurrent.ch2.tools.semaphore;
import java.util.LinkedList;
import java.util.concurrent.Semaphore;
/**
* @author h
* @Description 演示 Semaphore用法,一个数据库连接池的实现
* Semaphore 如果不调用acquire()方法,只调用了release()方法,那么许可证的个数会不断的上升
* @createTime 2020年03月19日 22:12
*/
public class DBPoolNoUseLess {
public final Semaphore useful;
private static final int POOL_SIZE = 10;
private static LinkedList<SqlConnectionImpl> pool = new LinkedList<>();
/*初始化线程池*/
static {
for (int i = 0; i < POOL_SIZE; i++) {
pool.addLast(SqlConnectionImpl.fetchConnection());
}
}
public DBPoolNoUseLess() {
this.useful = new Semaphore(10);
}
/**
* 获取连接
*/
public SqlConnectionImpl takeConnection() throws InterruptedException {
useful.acquire();
synchronized (pool) {
if (!pool.isEmpty()) {
System.out.println("成功获取连接。");
return pool.removeFirst();
}
}
return null;
}
/**
* 归还连接
*/
public void returnConnection(SqlConnectionImpl connection) {
if (null != connection) {
System.out.println("当前有 " + useful.getQueueLength() + " 个线程在等待数据库连接, 可用连接数:" + useful.availablePermits());
synchronized (pool) {
pool.addLast(connection);
}
}
useful.release();
}
private static DBPoolNoUseLess dbPoolNoUseLess = new DBPoolNoUseLess();
private static class WorkThread extends Thread {
@Override
public void run() {
dbPoolNoUseLess.returnConnection(new SqlConnectionImpl());
/* SqlConnectionImpl sqlConnection = null;
try {
sqlConnection = dbPoolNoUseLess.takeConnection();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(null != sqlConnection) {
dbPoolNoUseLess.returnConnection(sqlConnection);
}
}*/
}
}
public static void main(String[] args) {
for (int i = 0; i < 50; i++) {
new WorkThread().start();
}
}
}
确保可以达到流量控制的代码示例
通过定义两个Semaphore ,防止了只调用 release() 方法的场景,保证了流量控制。
package com.h.concurrent.ch2.tools.semaphore;
import java.sql.Connection;
import java.util.LinkedList;
import java.util.concurrent.Semaphore;
/**
* @author h
* @Description TODO
* @createTime 2020年03月19日 22:58
*/
public class DBPoolSemaphore {
// 两个指示器,分别表示池子的可用连接和已用连接
private final Semaphore useful;
private final Semaphore useless;
private static final int POOL_SIZE = 10;
private static LinkedList<Connection> pool = new LinkedList<Connection>();
static {
for (int i = 0; i < POOL_SIZE; i++) {
pool.addLast(SqlConnectionImpl.fetchConnection());
}
}
public DBPoolSemaphore() {
this.useful = new Semaphore(10);
this.useless = new Semaphore(0);
}
/**
* 获取连接
*/
public Connection takeConnection() throws InterruptedException {
useful.acquire();
Connection connection = null;
synchronized (pool) {
if (!pool.isEmpty()) {
connection = pool.removeFirst();
}
}
useless.release();
return connection;
}
/**
* 归还连接
*/
public void returnConnection(Connection connection) throws InterruptedException {
if (null != connection) {
System.out.println("当前有"+ useful.getQueueLength()+"个线程等待数据库连接!!"+"可用连接数:"+useful.availablePermits() + " " + System.currentTimeMillis());
// 这里先调用可用连接的 acquire(),由于 useless 定义的初始许可证个数为 0,所以如果一上来就先调用 useless.acquire() 则会处于等待状态
useless.acquire();
System.out.println("==== = =========== ========== ==== ========== === == = =");
synchronized (pool) {
pool.addLast(connection);
}
useful.release();
}
}
}
测试类:
package com.h.concurrent.ch2.tools.semaphore;
import java.sql.Connection;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* @author h
* @Description Semaphore 测试类
* @createTime 2020年03月19日 23:13
*/
public class AppTest {
private static DBPoolSemaphore pool = new DBPoolSemaphore();
private static class WorkThread extends Thread{
@Override
public void run() {
try {
Connection connection = pool.takeConnection();
System.out.println(" --- -- --- -- -- -- -- - -- -- " + System.currentTimeMillis());
Random random = new Random();
try { TimeUnit.MILLISECONDS.sleep(random.nextInt(1000));} catch (Exception e) {e.printStackTrace();}
pool.returnConnection(connection);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 500; i++) {
WorkThread workThread = new WorkThread();
workThread.start();
}
}
}
package com.h.concurrent.ch2.tools;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Exchanger;
/**
* @author h
* @Description 演示 Exchanger 用法
* @createTime 2020年03月19日 23:37
*/
public class UseExchange {
private static final Exchanger<Set<String>> exchange = new Exchanger<>();
public static void main(String[] args) {
new Thread(() ->{
// 初始化数据
Set<String> setA = new HashSet<>();
setA.add("a1");
setA.add("a2");
setA.add("a3");
// 交换数据,把 setA交换给BB线程,从BB线程中获取setB
try {
setA = exchange.exchange(setA);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印交换后的结果
setA.forEach(s -> System.out.println(Thread.currentThread().getName() +" "+ s));
}, "AA").start();
new Thread(() ->{
// 初始化数据
Set<String> setB = new HashSet<>();
setB.add("b1");
setB.add("b2");
setB.add("b3");
// 交换数据,把 setB交换给AA线程,从AA线程中获取setA
try {
setB = exchange.exchange(setB);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印交换后的结果
setB.forEach(s -> System.out.println(Thread.currentThread().getName() +" "+ s));
}, "BB").start();
}
}
控制台打印结果如下:
AA b2
AA b3
AA b1
BB a1
BB a2
BB a3
JDK开始设计线程的时候只有 Thread 和 Runnable,而它们的 run() 方法都是没有返回值的。在实际业务场景中可能会有需要带返回值的线程,所以设计了 Callable 接口,但是原有的 Thread 类并没有传入 Callable 接口的构造函数,为了解决这个问题,借助了一个中间类 FutureTask。
FutureTask 类
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
Future 接口:
代码示例如下:
FutureTask task = new FutureTask(new Callable());
Thread t = new Thread(task );
t.start();
task.get();
代码示例如下:
package com.h.concurrent.ch2.future;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
/**
* @author h
* @Description 演示Callable的使用
* @createTime 2020年03月19日 23:57
*/
public class UseFuture {
private static class UseCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("开始计算。。。");
int sum = 0;
for (int i = 0; i < 10000; i++) {
sum = sum + i;
if (Thread.currentThread().isInterrupted()) {
System.out.println("线程中断。。。。退出");
return null;
}
System.out.println("正在计算。。。。。sum = " + sum);
}
System.out.println("计算结束...");
return sum;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
UseCallable useCallable = new UseCallable();
// 包装
FutureTask<Integer> futureTask = new FutureTask<>(useCallable);
new Thread(futureTask).start();
System.out.println("调用 get() 前。");
// System.out.println("计算结果为:" + futureTask.get());
System.out.println("调用 get() 后。");
try { TimeUnit.MILLISECONDS.sleep(5);} catch (Exception e) {e.printStackTrace();}
futureTask.cancel(true);
}
}