前言 :
假设有一个很大的任务。这个任务可以分成 a 、 b 、 c三个步骤同时进行,
在同时进行的情况下,如果有一个任务被取消掉了或者遇到错误了,然后所有的都应该结束,要求高效完成,主要是高效。
严格来讲这是一个分布式事务的问题,也就是说有一个事务,此事务的某一段放到a级上去执行,其余两段分别放到b和c,其中有一个半截单子的子事务没有完成,那整个事务取消掉。
面试题:
关于线程同步的面试题,凡是从时间角度或者是优先级角度考虑解决思路的,基本全不对!凡是从join sleep考虑的,99.99%的不对,线程优雅的结束,一般不用interrupt stop resume,不得已再使用interrupt。
经典面试题:synchronized和ReentrantLock的区别是什么?
答案:ReentrantLock可以多个队列,synchronized只有一个队列,ReentrantLock可以做公平锁,synchronized只有非公平(没有公平可言),ReentrantLock还可以tryLock尝试上锁,上不了锁可以去做别的(做出一定的处理),但是synchronized只能上来就死等傻傻等待,等不到就死在那,第四个区别:锁可以被打断,就是我在锁的过程中我可以让别人打断我,打折我的腿我就醒了这个叫lock.incorruptibly可以做这个操作就是entry.lock,但是synchronized不可以,因为synchronized不能打断,除非把整个线程给砍掉。
面试题:【面试过程中现场实现】
提示:以下是本篇文章正文内容,下面案例可供参考
举例:张三李四一行人现在要吃火锅要有很多步骤
- 张三要去用锅烧开水
- 李四要准备买菜洗菜
- 路人王要去请王二来一起吃
假设现在张三去起锅烧水,李四去买菜了,路人王要去请王二来一起吃。
然后其中路人王在去请王二的路上遇到了pk无法去请王二来吃火锅,这个时候应该考虑什么,整个任务全取消,即便是张三在烧水,李四在买菜也取消,停掉。
示例方案一(存在问题):
可以翻看下面的几个案例有一个循序渐进的过程。
package com.aaa.blbl_Test2;
import java.io.IOException;
public class T0_F1 {
public static void main(String[] args) {
/*假设我们有三个任务,t1、t2、t3,然后假设每一个任务正常执行完结束之后,分别应该是3秒钟、
1秒钟、五秒钟,当然为了模拟这个任务t1、t2的两个任务都能正常执行完,所以后面传true的意思是说
让它正常执行完,穿false的意思是说,让它执行到一秒的时候报个错
因为是模拟任务,所以在这里直接给了线程的执行时间,1000毫秒=1秒*/
Thread t1 = new MyTask("t1",3000,true);
Thread t2 = new MyTask("t2",1000,false);
Thread t3 = new MyTask("t3",5000,true);
//演示回滚过程
t1.start();
t2.start();
t3.start();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
private static class MyTask extends Thread
{
private String name;//名字
private int timeInSeconds;//执行时间
private boolean success;//执行成功还是失败
public MyTask(String name, int timeInSeconds, boolean success) {
this.name = name;
this.timeInSeconds = timeInSeconds;
this.success = success;
}
@Override
public void run()
{
//模拟业务执行时间
//实际中时间不固定,可能在处理计算任务,或者是等待Io任务
try {
sleep(timeInSeconds);
System.out.println(name+"任务结束!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
存在问题:因为程序正常的结束取决于你最长的这个时间段,比如t1执行时间是十秒sleep(10000)我们整个程序要想结束,别的线程就要等它十秒才能结束。
但是按照题目的要求一秒就应该结束,因为一秒钟有一个任务已经报错了,所以一秒就应该结束。
示例方案二:(小白玩法)
我们给每一个任务,给它指定一个执行的结果,注意看第二个版本的玩法,这里是分成一个一个版本来实现的,如果不分版本的话对于基础稍弱的很可能写不到最后的那个版本。
package com.aaa.blbl_Test2;
import java.util.ArrayList;
import java.util.List;
public class T0_F2 {
public static void main(String[] args) {
/*版本二玩法:
需要有某一个东西来告知这个线程是正常成功了
还是没成功他的状态还是说没结束呢?
所以在下面做了一个枚举类型,这个枚举类型主要,指带的每一个线程它的状态,
一共有三个状态。
第一个状态是NOTEND也就是说现在正在运行之中。
第二个状态:SUCCESSED代表正常运行结束。
第三个状态:FAILED代表运行失败。
这样做的需求,因为需要知道这个线程他执行失败了,需要知道他的状态。
如果执行失败了通知所有的线程结束。
实现步骤:
private static enum Result{
NOTEND,SUCCESSED,FAILED
}
*/
Thread t1 = new T0_F2.MyTask("t1",3000,true);
Thread t2 = new T0_F2.MyTask("t2",1000,false);
Thread t3 = new T0_F2.MyTask("t3",5000,true);
List<MyTask> tasks = new ArrayList<>();
tasks.add((MyTask) t1);
tasks.add((MyTask) t2);
tasks.add((MyTask) t3);
//启动线程
tasks.stream().forEach((t -> t.start()));
/*
接上面:实现步骤:首先把所有的线程全装到一个list里,然后让所有的线程启动,
启动完成之后,就启动一个监视程序也就是下面的for循环,一直不停的检测它。
*/
//启动监视
for (; ; ) {
for (MyTask task : tasks)
{
// 如果有某一个线程getResult()它的Result是FAILED的了整个线程失败、System.exit(0)整个程序结束。
if (task.getResult() == Result.FAILED)
{
System.exit(0);
}
}
}
}
private static enum Result{
NOTEND,SUCCESSED,FAILED
}
private static class MyTask extends Thread
{
private Result result = Result.NOTEND;
private String name;
private int timeInSeconds;
private boolean success;
public MyTask(String name, int timeInSeconds, boolean success) {
this.name = name;
this.timeInSeconds = timeInSeconds;
this.success = success;
}
public Result getResult() {
return result;
}
@Override
public void run()
{
//模拟业务执行时间
//实际中时间不固定,可能在处理计算任务,或者是等待Io任务
try {
sleep(timeInSeconds);
System.out.println(name+"任务结束!");
result = success ? Result.SUCCESSED : Result.FAILED;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
这个方法也是绝对不能够被允许的:
接上图它里面的for循环是一个盲等待,盲等待(就是一直不停循环代表不停的消耗cpu,
是效率上的降低,不能够被允许,第二点:System.exit(0):粗暴的干掉一切线程,
假设路人王去请王二来吃火锅,一秒钟之后失败了,李四在买菜的路上遇到了修路,张三在烧水把锅踢飞,完全有可能是不一致的状态,打开了半截文件直接就退出了,这个文件还没有close,建立了一个链接对方还在等着我说话,这边还没说直接就close了,所以该方案也是存在问题的不能被允许)
示例方案三:(循序渐进)
示例:每一个线程在运行的时候,加一个最终的处理方法叫boss.end
package com.aaa.blbl_Test2;
/**
* 最原始的方法 Thread run()重写
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class T0_F3 {
private static class Boss extends Thread{
private List<Worker> tasks = new ArrayList<>();
public void addTask(Worker t)
{
tasks.add(t);
}
@Override
public void run()
{
tasks.stream().forEach((t) -> t.start());
}
public void end(Worker worker)
{
if (worker.getResult()==Result.FAILED)
{
System.exit(0);
}
}
}
public static void main(String[] args) {
/*
Boss boss = new Boss();
boos是最大管理员,每一个Worker都拥有一个boos的引用,
如果某一个线程结束了,在boss.end(this)处理方法如果该线程Result.FAILED
那么就整个退出 System.exit(0),这样解决了盲等待的问题。
*/
Boss boss = new Boss();
Worker t1 = new Worker(boss,"t1",3000,true);
Worker t2 = new Worker(boss,"t2",1000,false);
Worker t3 = new Worker(boss,"t3",5000,true);
boss.addTask(t1);
boss.addTask(t2);
boss.addTask(t3);
//启动线程
boss.start();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
private static enum Result{
NOTEND,SUCCESSED,FAILED
}
private static class Worker extends Thread
{
private Result result = Result.NOTEND;
private Boss boss;
private String name;
private int timeInSeconds;
private boolean success;
public Worker(Boss boss, String name, int timeInSeconds, boolean success) {
this.boss = boss;
this.name = name;
this.timeInSeconds = timeInSeconds;
this.success = success;
}
public Result getResult() {
return result;
}
@Override
public void run()
{
//模拟业务执行时间
//实际中时间不固定,可能在处理计算任务,或者是Io任务
try {
sleep(timeInSeconds);
System.out.println(name+"任务结束!");
result = success ? Result.SUCCESSED : Result.FAILED;
boss.end(this);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
最终方案:
书写回滚,回滚这个事件就是每种是每种不同的方式,就是每一个线程有每一个线程不同的回滚方式。
package com.aaa.blbl_Test2;
import java.util.ArrayList;
import java.util.List;
/**
* 最原始的方法 Thread run()重写
*/
public class F0_F4 {
private static class Boss extends Thread{
private List<Worker> tasks = new ArrayList<>();
public void addTask(Worker t)
{
tasks.add(t);
}
@Override
public void run()
{
tasks.stream().forEach((t) -> t.start());
}
public void end(Worker worker)
{
//当某一个线程结束了,并不是把其他线程的腿打断
//而是要让它取消cancel
if (worker.getResult() == Result.FAILED)
{
cancel(worker);
}
}
private void cancel(Worker worker)
{
for (Worker w : tasks)
{
if (w != worker) w.cancel();
}
}
}
public static void main(String[] args) {
Boss boss = new Boss();
Worker t1 = new Worker(boss,"A===",3,true);
Worker t2 = new Worker(boss,"B===",1,false);
Worker t3 = new Worker(boss,"C===",5,true);
boss.addTask(t1);
boss.addTask(t2);
boss.addTask(t3);
//启动线程
boss.start();
try {
System.in.read();
} catch (Exception e) {
e.printStackTrace();
}
}
private static enum Result{
NOTSET,SUCCESSED,FAILED,CANCELLED
}
private static class Worker extends Thread
{
private Result result = Result.NOTSET;
private Boss boss;
private String name;
private int timeInSeconds;
private boolean success;
private volatile boolean cancelling = false;
public Worker(Boss boss, String name, int timeInSeconds, boolean success) {
this.boss = boss;
this.name = name;
this.timeInSeconds = timeInSeconds;
this.success = success;
}
public Result getResult() {
return result;
}
@Override
public void run()
{
int interval = 100;
int total = 0;
for ( ; ; )
{
try {
sleep(interval);//cpu密集型
total += interval;
if (total / 1000 >= timeInSeconds)
{
// System.out.println(total+"$$$$");
// System.out.println(timeInSeconds);
System.out.println(name+"任务结束!"+result);//正常结束
result = success ? Result.SUCCESSED : Result.FAILED;
System.out.println("的撒大");
break;
}
if (cancelling)
{
rollback();
result = Result.CANCELLED;
cancelling = false;
System.out.println(name="执行完毕!"+result);
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boss.end(this);
}
private void rollback()
{
try {
//书写回滚
System.out.println(name+"rollback start....");
/* rollback事务回滚,不同的事务是不同的回滚
举例:所有事件整体是一个大事务,它要求第一个事务是关于数据库的
那么插入五条数据就要(cancel)删除五条数据、假设第二个事务是关于文件拷贝
文件拷贝完之后就要进行删除、第三个是关于MQ里面往里面发消息的
假设发进去几条消息,然后在发几条取消掉的消息这叫rollback
如果在rollback过程中出错,假设插入五条数据删到三条的时候出错了
举例:发出总任务的这个人一定有一个最大等待时间,
假如你在特定的时间内没有完成,那就说明任务失败,那其中有一个子任务,
那这个子任务,如果中间rollback失败了,那就是一定抛出异常了
这个时候一定要记录日志,发告警必须人工处理了,那上面的数据的那个来说删不掉了
说明里面有脏数据了,可能不是删除可能是修改,这时候必须人工处理了,把运维程序员从睡梦中叫醒来处理
*/
sleep(500);
System.out.println(name+"rollback end!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void cancel() {
cancelling = true;//设立标志位
}
}
}
先进处理方案:
package com.aaa.blbl_Test2;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* 问题:可以归分为分布式事务失败回滚的手工实现
* 代码不完善,可扩展
*/
public class F0_F5 {
//任务执行结束的三种状态
private static enum Result
{
SUCCESS,FAIL,CANCELLED
}
static List<MyTask> tasks = new ArrayList<>();
public static void main(String[] args) {
MyTask task1 = new MyTask("task1===",3,Result.SUCCESS);
MyTask task2 = new MyTask("task2===",5,Result.SUCCESS);
MyTask task3 = new MyTask("task3===",1,Result.FAIL);
tasks.add(task1);
tasks.add(task2);
tasks.add(task3);
for (MyTask task : tasks)
{
/*可以使用线程的CompletableFuture可以使用异步任务这个线程池CompletableFuture.supplyAsync
使用纯异步的方式去调用第一个任务,第一个任务如果有了结果之后,
用callback处理结果。
*/
CompletableFuture f = CompletableFuture.supplyAsync(() -> task.runTask())
.thenAccept((result -> callback(result,task)));
}
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static Result callback(Result result,MyTask task)
{
//在处理结果的时候,判断结果如果是FAIL那么让所有的任务全部cancel
if (Result.FAIL == result)
{
for (MyTask ts : tasks )
{
if (ts!=task)
{
ts.cancel();
}
}
}
return result;
}
private static class MyTask
{
private String name;
private int timeInSeconds;
private Result ret;
boolean cancelling = false;
volatile boolean cancelled = false;
public MyTask(String name, int timeInSeconds, Result ret) {
this.name = name;
this.timeInSeconds = timeInSeconds * 1000 ;
this.ret = ret;
}
public Result runTask()
{
int interval = 100;
int total = 0;
try {
for (; ;)
{
Thread.sleep(interval);
if (cancelling) continue;
total += interval;
if (total >= timeInSeconds)break;
if (cancelled) return Result.CANCELLED;
}
}catch (Exception e)
{
e.printStackTrace();
}
System.out.println(name+"end!");
return ret;
}
//任务cancel过程synchronized (this)
public void cancel()
{
cancelling = true;
//不上锁的话关系也不大
synchronized (this)
{
System.out.println(name+"cancelling");
try {
//然后让它去任务结束
Thread.sleep(50);
System.out.println(name+"cancelled");
} catch (InterruptedException e) {
e.printStackTrace();
}
//最后cancel
cancelled = true;
}
}
}
}
总结
以上就是今天要讲的内容,以上代码还有可扩展性,当然“笔者还深度欠缺,如果错误还请指正”。