1.基础知识
1.1 线程实现方式
1.1.1 Thread
继承Thread实现多线程只需要3步
1. 继承Thread
2. 重写run 方法
3.调用start方法,启动线程
代码如下:
/**
* 继承Thread
* 重写run 方法
* 调用Start 方法 启动线程
*
*/
public class MyThread extends Thread {
private String url;
public MyThread(String url) {
this.url=url;
}
public static void main(String[] args) {
String bd="baidu.com";
MyThread myThread=new MyThread(bd);
myThread.setName("myThread");
myThread.start();
System.out.println(Thread.currentThread().getName()+" start");
}
@Override
public void run() {
System.out.println("down load from "+this.url);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("complete task");
}
}
1.1.2 Runnable
实现Runnable接口,如下几步
1.定义类实现 Runnable 接口
2.实现 run() 方法,编写线程执行逻辑
3.创建线程对象实例,调用 start() 方法启动线程 代码如下:
/**
* 实现接口Runnable
* 重写run 方法
* 创建线程对象,传递Runnable接口实例,调用 start() 方法启动线程
*
*/
public class MyRunnable implements Runnable{
public static void main(String[] args) {
String sohu="sohu.com";
Thread t1=new Thread(new MyRunnable(sohu));
t1.start();
System.out.println(Thread.currentThread().getName()+" start");
}
private String url;
public MyRunnable(String url) {
this.url=url;
}
@Override
public void run() {
System.out.println("down load from "+this.url);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println("complete task");
}
}
1.1.3 Callablle
Callable可以阻塞获取线程执行结果 实现多线程,步骤如下:
1.定义实现类实现Callable接口 定义返回值类型
2.重写call 方法 执行逻辑
3.定义FutureTask 传入Callable接口实现类
4.定义Thread线程,传入FutureTask 实例,调用start方法启动线程
5.调用FutureTask的get方法,阻塞获取线程执行结果
代码如下:
/**
* 1.实现Callable接口 定义返回值类型
* 2.重写call 方法 执行逻辑
* 3.定义FutureTask 传入Callable接口实现类
* 4.定义Thread线程,传入FutureTask 实例,调用start方法启动线程
* 5.调用FutureTask的get方法,阻塞获取线程执行结果
*
*/
public class MyCallable implements Callable<Integer>{
public static void main(String[] args) throws Exception {
FutureTask<Integer> task=new FutureTask<Integer>(new MyCallable(100));
new Thread(task).start();
System.out.println(Thread.currentThread().getName()+" start");
Integer result=task.get();
System.out.println("线程执行结束,获的结果为:"+result);
}
@Override
public Integer call() throws Exception {
System.out.println("开始计算 "+countNum+"累积总和");
int total=0;
for(int i=0;i<countNum;i++) {
total+=i;
}
Thread.sleep(1000);
System.out.println("计算完成,累积总和:"+total);
return total;
}
private Integer countNum;
public MyCallable(Integer countNum) {
this.countNum=countNum;
}
}
1.2 线程状态
NEW 初始状态 Thread t=new Thread()
READY 就绪状态 t.start()等CPU调度 Thread.yield()/Object.notify()/Object.notifyAll()/LockSupport.unpark()
RUNNING 运行状态 CPU正在调度
BLOCKED 阻塞状态 synchronized
WAITING 等待状态 Thread.join/LockSupport.park()/Object.wait()
TIMED_WAITING 超时等待 Thread.join/LockSupport.park()/Object.wait()
TERMINATED 终止状态
1.3 终止线程
终止线程方式有2种,建议使用Interupted
1.3.1 stop
上代码:
/**
* stop 会立即终止线程,不建议使用,会使的后续runTask方法执行未完成
*
*/
public class ThreadStopDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
int k=0;
while(k<10) {
k++;
try {
runTask(k);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1");
t1.start();
Thread.sleep(5000);
System.out.println("t1 stop over");
t1.stop();
}
private static void runTask(int k) throws InterruptedException {
System.out.println("第"+k+"次任务开始");
Thread.sleep(1000);
System.out.println("stepA over");
Thread.sleep(1000);
System.out.println("stepB over");
Thread.sleep(1000);
System.out.println("stepC over");
System.out.println("第"+k+"次任务结束");
}
}
执行结果如图:
眼尖的朋友就会发现,第二次任务stepB,stepC丢失了,这是什么问题?因为
stop 会立即终止线程,不建议使用,会使的后续runTask方法执行未完成 ,有没有一种更好的方式结束线程呢?可不可以在方法执行时候check一个共享变量flag,如果 循环过程中发现flag 变了,可以在方法执行结束时候退出循环,结束当前任务,这似乎可行。万能的java之父不可能没想到这一点,于是有了Interupted
1.3.2 Interupted
Interupted 如何优雅的中断线程呢,话不多说,上代码
/**
* Thread.currentThread().isInterrupted() 获取当前线程的中断标记
* t1.interrupt() 中断当前线程,给当前线程打标记
* 注意 线程里面调用Thread.sleep 一定要在异常里面重新打标Thread.currentThread().interrupt(),否则会出现异常
*/
public class ThreadInteruptedDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
int k=0;
while(!Thread.currentThread().isInterrupted()) {
k++;
runTask(k);
}
},"t1");
t1.start();
TimeUnit.MILLISECONDS.sleep(5000);
System.out.println("t1 stop over");
t1.interrupt();
}
private static void runTask(int k) {
System.out.println("第"+k+"次任务开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("stepA over");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("stepB over");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("stepC over");
System.out.println("第"+k+"次任务结束");
}
}
执行结果如图:
从图中可以看出,t1 stop over 结束指令发出来后,stepB,stepC方法依然执行完成了,这就是我们想要的效果。眼尖的网友会发现 在Thread.sleep 里面我们多次捕获异常并且执行了Thread.currentThread().interrupt(); 这是因为 在t1 执行了t1.interrupt()之后,此时t1线程里面runTask方法里面如果调用Thread.sleep 会抛出InterruptedException异常,这个异常会使得t1.interrupt()打标功能失效。所以需要在捕获异常里面重新打标,调用interrupt方法
* Thread.currentThread().isInterrupted() 获取当前线程的中断标记
* t1.interrupt() 中断当前线程,给当前线程打标记
* 注意 线程里面调用Thread.sleep 一定要在异常里面重新打标Thread.currentThread().interrupt(),否则会出现异常
1.4 多线程安全问题
如下代码
public class ThreadSafeDemo {
public static int count=0;
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
for(int i=0;i<50000;i++) {
count++;
}
},"t1");
Thread t2=new Thread(()->{
for(int i=0;i<50000;i++) {
count++;
}
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count:"+count);
}
}
返回结果:count:61685
可能每次运行结果都不同,并不是我们期盼的100000,这是为什么呢?
* 产生问题 根本原因在于CPU高速缓存
* count++ 操作可以理解为t1 线程取到缓存count值 此时 未执行++操作,t2也取到count内存值,并且缓存
* t1线程执行++操作 并且更新内存count值,此时t2线程++操作,由于t2线程是取的缓存count值++,并未取到t2线程最新值
* t2线程++ 旧值,更新到内存 ,这就出现了内存覆盖问题。
线程安全问题存在于 多个线程访问修改同一个共享变量产生,由于CPU高速缓存内存变量,使得多个线程改动结果不可见导致,为了解决这个问题,于是有了锁
2.锁
2.1 Synchronized
如何解决上面安全问题呢,可以对多线程执行访问共享资源加同一把锁,保证操作变量时候只有一个线程访问 具体代码如图
具体代码如下
/**
* synchronized 对 t1 t2线程加同一把锁,锁obj实例,确保操作 count++时候 t1,t2只有一个线程在运行
* 注意不能锁不同的对象,否则锁失效比如t2线程锁obj2实例
*/
public class ThreadSynchrDemo {
public static int count=0;
public static void main(String[] args) throws InterruptedException {
Object obj=new Object();
Thread t1=new Thread(()->{
for(int i=0;i<50000;i++) {
synchronized (obj) {
count++;
}
}
},"t1");
Thread t2=new Thread(()->{
for(int i=0;i<50000;i++) {
synchronized (obj) {
count++;
}
}
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count:"+count);
}
}
Synchronized 作为关键字可以修饰在 类上,方法上,代码块上。
修饰在类上
类锁,整个类都是线程安全的
修饰在方法上
锁定整个方法,此方法是线程安全的
修饰在代码块上
锁定的是 { } 之间的代码,{}之间的代码是线程安全的
加锁过程分析
我们来回顾下多线程产生安全问题的根本原因,一张图解释如下
图中我们可以了解到,多线程安全问题的根本原因是ThreadA还没执行结束MethodA 并且setA最新值到堆内存时候,ThreadB就开始执行了,此时计算A=A+5 就会产生不一致的结果 ,如何保证线程安全呢。于是我们找个中介者,告诉ThreadB, ThreadA已经执行完毕,ThreadB就可以继续通行。那谁来做这个中介者呢,我们还是需要一个共享内存来完成这个事,如图
红框部分必须是原子操作(CAS),这样似乎可以实现加锁操作了,于是我们看看Synchronized是怎么实现加锁的。
2.2 Lock
3.AQS前置知识
4.AQS
5.多线程组件
5.1CountDownLatch
5.1.1 功能
一个线程(或者多个), 等待另外N个线程完成得以继续执行
5.1.2 场景
现有3个独立任务,3个独立任务完成获取结果
5.1.3 代码
单线程Demo
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
final Integer[] arr=new Integer[3];
Long startTime=System.currentTimeMillis();
arr[0]=runTaskT1();//2s 100
arr[1]=runTaskT2();//2s 400
arr[2]=runTaskT3();//1s 200
Long endTime=System.currentTimeMillis();
System.out.println("总计花费:"+(endTime-startTime)/1000+"s"); //5s
System.out.println("求得总和:"+getTotal(arr)); //700
}
private static Integer getTotal(Integer[] arr) {
int total=0;
for(Integer num:arr) {
total+=num.intValue();
}
return total;
}
protected static Integer runTaskT3() throws InterruptedException {
Thread.sleep(1000);
return 200;
}
protected static Integer runTaskT2() throws InterruptedException {
Thread.sleep(2000);
return 400;
}
protected static Integer runTaskT1() throws InterruptedException {
Thread.sleep(2000);
return 100;
}
}
执行结果如图
结果反馈 总计执行了5S 返回700、虽然结果是对的,这显然不是我们想要的结果,我们想要的是runTaskT1, runTaskT2, runTaskT3 能够并发执行,执行完成最后统计结果
CountDownLatch Demo
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//t1 线程
final CountDownLatch latch = new CountDownLatch(3);
final Integer[] arr=new Integer[3];
Long startTime=System.currentTimeMillis();
//t1 线程9
new Thread(new Runnable() {
@Override
public void run() {
try {
arr[0]=runTaskT1();
}catch(Exception e) {
e.printStackTrace();
}
finally {
latch.countDown();
}
}
},"t1").start();
//t1 线程
new Thread(new Runnable() {
@Override
public void run() {
try {
arr[1]=runTaskT2();
}catch(Exception e) {
e.printStackTrace();
}
finally {
latch.countDown();
}
}
},"t2").start();
//t3 线程
new Thread(new Runnable() {
@Override
public void run() {
try {
arr[2]=runTaskT3();
}catch(Exception e) {
e.printStackTrace();
}
finally {
latch.countDown();
}
}
},"t3").start();
latch.await();
Long endTime=System.currentTimeMillis();
System.out.println("总计花费:"+(endTime-startTime)/1000+"s");
System.out.println("求得总和:"+getTotal(arr));
}
private static Integer getTotal(Integer[] arr) {
int total=0;
for(Integer num:arr) {
total+=num.intValue();
}
return total;
}
protected static Integer runTaskT3() throws InterruptedException {
Thread.sleep(1000);
return 200;
}
protected static Integer runTaskT2() throws InterruptedException {
Thread.sleep(2000);
return 400;
}
protected static Integer runTaskT1() throws InterruptedException {
Thread.sleep(2000);
return 100;
}
}
执行结果如图
可以得知 该程序总计运行了2s 总结果700,对比单线程 CountDownLatch执行时长取决于最耗时的那个线程执行时长,而单线程执行时长取决于总耗时,CountDownLatch无疑效率更高
5.1.4 应用场景
a. 多个互不通信的任务并发执行
b.大任务拆分,子任务并发处理,比如 大数据统计,可以将大数据按照一定维度拆分,启动多线程计算拆分的子任务,最后归并处理,forkjoin也可以
5.1.5 注意事项
latch.countDown();一定要写在finally里面 防止程序发生异常,计数的那个线程latch.countDown()并没有正确的执行,计数器没有复位,导致await方法一直处于阻塞状态,这是个致命的问题
5.1.6 原理分析
5.2 Semaphore
5.2.1 功能
同一时间段只能有N个线程访问,其他线程等待挂起,等到线程释放,等待线程恢复访问(限流)
5.2.2 场景
现有1000个任务需要多线程运行,为防止CPU过载,允许同时运行线程为4个
5.2.3 代码
/**
* 初始化1000个任务 多线程运行
* 防止CPU过载,允许执行任务线程最大为4个,执行完当前任务 其他线程方可启动执行
* @author wuliangfeng
*
*/
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore sp=new Semaphore(4);
String[] taskIds=getTaskId(100);
for(int i=0;i<taskIds.length;i++) {
String taskId=taskIds[i];
new Thread(()->{
try {
sp.acquire();
runTask(taskId);
}catch(Exception e) {
e.printStackTrace();
}finally {
sp.release();
}
},"thread"+i).start();
}
}
private static String[] getTaskId(int num) {
String[] taskIds=new String[num];
for(int i=0;i<num;i++) {
taskIds[i]="Task"+i;
}
return taskIds;
}
private static void runTask(String taskId) throws InterruptedException {
System.out.println("当前线程:"+Thread.currentThread().getName()+",获取并处理任务:"+taskId);
Thread.sleep(100);
System.out.println(taskId+" 任务处理结束");
}
}
5.2.4 原理分析
5.3 CyclicBarrier
5.4 ForkJoin
5.5 CompletableFuture