JAVA多线程代码题
JAVA多线程代码题
学习资料
1.写一个双线程轮流打印1-100
思路1:对象锁lock:wait/notifyAll + 等待标记mark
class ThreadDemo1_01 {
static AtomicInteger count = new AtomicInteger(1); // 定义计数器
static Object lock = new Object(); // 定义对象锁
static int mark = 1; // 定义全局等待标记(1-线程A执行;2-线程B执行)
public static void main(String[] args) {
// 线程A定义
Thread ta = new Thread(() -> {
while (count.get() < 100) {
synchronized (lock) {
while (mark != 1) {
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("线程A执行" + count.getAndIncrement()); // 打印并执行自增操作
lock.notifyAll(); // 唤醒
mark = 2; // 切换等待标记
}
}
});
// 线程B定义
Thread tb = new Thread(() -> {
while (count.get() <= 100) {
synchronized (lock) {
while (mark != 2) {
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("线程B执行" + count.getAndIncrement()); // 线程B打印信息
lock.notifyAll(); // 唤醒
mark = 1; // 切换等待标记
}
}
});
// 启动线程
ta.start();
tb.start();
}
}
思路2:信号量Semaphore(acquire获取锁、release释放锁)
class ThreadDemo1_02 {
// 思路:信号量思路(基于信号量实现互斥锁)
static AtomicInteger counter = new AtomicInteger(1); // 计数器
static Semaphore s1 = new Semaphore(1); // 初始化许可证为1
static Semaphore s2 = new Semaphore(0); // 初始化许可证为0
public static void main(String[] args) {
// 线程A定义
Thread threadA = new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
while (counter.get() < 100) {
s1.acquire(); // s1获取锁(许可证-1)
System.out.println("线程A:" + counter.getAndIncrement());
s2.release(); // s2释放锁(许可证+1)
}
}
});
// 线程B定义
Thread threadB = new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
while (counter.get() < 100) {
s2.acquire(); // s2获取锁(许可证-1)
System.out.println("线程B:" + counter.getAndIncrement());
s1.release(); // s1释放锁(许可证+1)
}
}
});
// 启动线程进行测试
threadA.start();
threadB.start();
}
}
思路3:LockSupport
class ThreadDemo1_03 {
static AtomicInteger count = new AtomicInteger(1); // 定义计数器
static Thread[] threads = new Thread[2];
public static void main(String[] args) {
threads[0] = new Thread(()->{
while(count.get() < 100) {
System.out.println("线程A执行" + count.getAndIncrement());
LockSupport.unpark(threads[1]);
LockSupport.park();
}
});
threads[1] = new Thread(()->{
while(count.get() < 100) {
LockSupport.park();
System.out.println("线程B执行" + count.getAndIncrement());
LockSupport.unpark(threads[0]);
}
});
threads[0].start();
threads[1].start();
}
}
同题目:【2个线程,交替打印1-100的奇偶数】
其本质上是一样的概念,但此处需做额外的奇数、偶数判断(此处以思路2进行参考),对比思路2代码,其在内存嵌套中多加了个while判断,用于明确限定奇数、偶数
class ThreadDemo1_04 {
static AtomicInteger count = new AtomicInteger(1); // 定义计数器
static Semaphore s1 = new Semaphore(1);
static Semaphore s2 = new Semaphore(0);
public static void main(String[] args) {
// 线程A打印奇数
Thread ta = new Thread(() -> {
while (count.get() < 100) {
while (count.get() % 2 == 1) {
try {
s1.acquire();
System.out.println("[奇数]线程A执行" + count.getAndIncrement());
s2.release();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
// 线程B打印偶数
Thread tb = new Thread(() -> {
while (count.get() <= 100) {
while (count.get() % 2 == 0) {
try {
s2.acquire();
System.out.println("[偶数]线程B执行" + count.getAndIncrement());
s1.release();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
ta.start();
tb.start();
}
}
2.三个线程顺序打出1-100
和前面的双线程打印思路类似,需要引入一个计数器、锁(Lock)或者信号量、mark(打印标记,用于控制哪个线程打印什么范围的内容)
类似的,参考【题目1】中的思路1,三个线程轮流打印就是通过控制mark标记进而控制顺序打印(如果当前不是自己的轮次则等待,是则执行),打印后确认打印的范围(如果超出范围,例如多打印了101则通过if语句过滤掉,跳出当次循环)
/**
* 三线程轮流打印1-100
* 解决思路1:对象锁+全局标识
*/
public class Solution1 {
// 锁思路:对象锁+全局标识
static Object lock = new Object();
static AtomicInteger count = new AtomicInteger(1);
static int mark = 1; // 定义等待标记(为1线程A打印,为2线程B打印;为3线程C打印)
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
public void run() {
// 打印数字
while (count.get() < 100) {
synchronized (lock) {
while(mark !=1) {
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 排除101数字的打印
if(count.get() != 101) {
System.out.println("线程1执行" + count.getAndIncrement()); // 打印并执行自增操作
}else{
break; // 跳出当次循环
}
lock.notifyAll(); // 唤醒线程
mark = 2; // 切换等待标记
}
}
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
// 打印数字
while (count.get() < 100) {
synchronized (lock) {
while(mark !=2) {
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 排除101数字的打印
if(count.get() != 101) {
System.out.println("线程2执行" + count.getAndIncrement()); // 打印并执行自增操作
}else{
break; // 跳出当次循环
}
lock.notifyAll(); // 唤醒线程
mark = 3; // 切换等待标记
}
}
}
});
Thread t3 = new Thread(new Runnable() {
public void run() {
// 打印数字
while (count.get() < 100) {
synchronized (lock) {
while(mark !=3) {
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 排除101数字的打印
if(count.get() != 101) {
System.out.println("线程3执行" + count.getAndIncrement()); // 打印并执行自增操作
}else{
break; // 跳出当次循环
}
lock.notifyAll(); // 唤醒线程
mark = 1; // 切换等待标记
}
}
}
});
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
信号量方案:参考【问题1】中的信号量方案思路,此处引入3个信号量进行处理,只有获取到许可的线程才能执行(可以理解为此处的new Semphore(1)
相当于一把锁,满足条件则获取许可执行,随后释放下一个信号量的许可让其他线程执行,以此类推)
/**
* 三线程轮流打印1-100
* 解决思路2:信号量方案
*/
public class Solution2 {
static AtomicInteger count = new AtomicInteger(1); // 定义计数器
// 信号量(分别定义三个信号量)
static Semaphore firstToSecond = new Semaphore(1);// 初始化1个许可
static Semaphore secondToThird = new Semaphore(0);
static Semaphore ThirdToFirst = new Semaphore(0);
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
public void run() {
// 打印数字
while (count.get() < 100) {
try {
// 获取许可
firstToSecond.acquire();
System.out.println("线程t1执行" + count.getAndIncrement());
// 释放许可
secondToThird.release();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
// 打印数字
while (count.get() < 100) {
try {
// 值为1,获取许可,开始执行(如果值为0则进入阻塞状态)
secondToThird.acquire();
System.out.println("线程t2执行" + count.getAndIncrement());
// 释放许可
ThirdToFirst.release();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
Thread t3 = new Thread(new Runnable() {
public void run() {
// 打印数字
while (count.get() < 100) {
try {
// 值为1,获取许可,开始执行(如果值为0则进入阻塞状态)
ThirdToFirst.acquire();
System.out.println("线程t3执行" + count.getAndIncrement());
// 释放许可
firstToSecond.release();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
3.多线程问题:线程A,B、C,分别打印1、2、3,顺序执行10次
类似的,也是参考上述思路。此处设定一个方法,可以指定线程打印指定轮次的数字。例如以count%线程数得到的余数作为轮次,线程A打印轮次0、线程B打印轮次1、线程C打印轮次2等
/**
* 多线程问题:线程A,B、C,分别打印1、2、3,顺序执行10次
* 思路1:Lock对象锁,打印指定轮次
*/
public class Solution1 {
static Object lock = new Object(); // 定义对象锁
static AtomicInteger count = new AtomicInteger(1);// 定义计数器
public static void printNum(int turn){
// 判断是否为当前轮次
while(count.get()<10){
synchronized (lock){
// 判断是否为当前轮次,如果不是则等待
while(count.get()%3!=turn){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 是当前轮次,则打印数字并执行自增
System.out.println( Thread.currentThread().getName() + "线程执行:" + count.getAndIncrement());
// 唤醒其他线程
lock.notifyAll();
}
}
}
public static void main(String[] args) {
// 定义线程A 打印轮次0
Thread t1 = new Thread(new Runnable() {
public void run() {
printNum(0);
}
},"A");
// 定义线程B 打印轮次1
Thread t2 = new Thread(new Runnable() {
public void run() {
printNum(1);
}
},"B");
// 定义线程C 打印轮次2
Thread t3 = new Thread(new Runnable() {
public void run() {
printNum(2);
}
},"C");
// 线程启动
t1.start();
t2.start();
t3.start();
}
}
4.计数累加怎么线程安全,可以怎么实现,100个线程,每个线程累加100次
核心:多线程计数安全累加,通过线程池+AtomicInteger进行操作
/**
* 计数安全累加问题:100个线程,每个线程累加100次
* 思路:线程池 + AtomicInteger
*/
public class Solution1 {
// 定义计数器(此处final修饰AtomicInteger对象表示对象不可变,其内部的值还是可变的)
private static final AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) {
// 定义线程池(固定大小100个线程)
ExecutorService executors = Executors.newFixedThreadPool(100);
// 提交100个任务,每个任务执行100次累加
for (int i = 0; i < 100; i++) {
executors.execute(new Runnable() {
public void run() {
for (int j = 0; j < 100; j++) {
// 原子操作,线程安全
count.incrementAndGet();
}
}
});
}
// 关闭线程池,不再接收新的任务
executors.shutdown();
// 输出最终统计的结果
System.out.println(count);
}
}
5.线程交叉打印12A34B56C,多种实现方式(一个打印数字,一个打印字母)
核心:回归到双线程打印思路,一个线程打印数字、一个线程打印字母,通过标识进行线程切换,如果没有到自己执行的轮次则等待,执行完成则切换mark并唤醒其他线程
/**
* 线程交叉打印12A34B56C,多种实现方式(一个打印数字,一个打印字母)
*/
public class Solution1 {
// 定义对象锁
static Object lock = new Object();
// 定义打印标识(线程1打印数字、线程2打印字母)
static int printMark = 1; // 1打印数字、2打印字母
static AtomicInteger numCount = new AtomicInteger(1); // 定义数字计数器
static AtomicInteger letterCount = new AtomicInteger(0);// 定义字母计数器
static char[] letters ={'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
public static void main(String[] args) {
// 定义线程1 打印数字(52个数字)
Thread t1 = new Thread(new Runnable() {
public void run() {
while(numCount.get()<=52){
synchronized(lock){
while (printMark!=1){
// 非当前轮次则等待(并非打印数字)
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 轮到打印数字,执行操作,切换标志。唤醒其他线程
System.out.println(numCount.getAndIncrement());
System.out.println(numCount.getAndIncrement());
printMark = 2;
lock.notifyAll();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
while(letterCount.get()<26){
synchronized(lock){
while (printMark!=2){
// 非当前轮次则等待(并非打印字母)
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 轮到打印字母,执行操作,切换标志。唤醒其他线程
System.out.println(letters[letterCount.getAndIncrement()]);
printMark = 1;
lock.notifyAll();
}
}
}
});
// 启动线程
t1.start();
t2.start();
}
}
6.两个线程交替打印ABCD..Z字母,一个大写一个小写
核心:回归双线程打印思路,可以定义一个数组(敲定打印序列),依次顺序打印;也可从A->Z递增,然后根据大小写打印标志进行切换转化
/**
* 两个线程交替打印ABCD..Z字母,一个大写一个小写
* 思路:回归双线程打印思路
*/
public class Solution1 {
// 定义对象锁
static Object lock = new Object();
// 定义打印标识
static int printMark = 1; // 1打印大写、2打印小写
static AtomicInteger letterCount = new AtomicInteger(0);// 定义字母计数器
static char[] letters ={'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
public static void main(String[] args) {
// 定义线程打印大写字母
Thread t1 = new Thread(new Runnable() {
public void run() {
while (letterCount.get()<25) {
synchronized (lock) {
// 判断是否为当前轮次(打印大写字母)
while(printMark != 1){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 打印大写字母,计数+1,切换标志,唤醒线程
System.out.println(letters[letterCount.getAndIncrement()]);
printMark = 2;
lock.notifyAll();
}
}
}
});
// 定义线程打印小写字母
Thread t2 = new Thread(new Runnable() {
public void run() {
while (letterCount.get()<26) {
synchronized (lock) {
// 判断是否为当前轮次
while(printMark != 2){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 打印小写字母,计数+1,切换标志,唤醒线程
System.out.println(Character.toLowerCase(letters[letterCount.getAndIncrement()]));
printMark = 1;
lock.notifyAll();
}
}
}
});
// 启动线程
t1.start();
t2.start();
}
}
7.两个线程交替打印出a1b2c3.....z26
核心:双线程交替打印思路,线程1打印字母,线程2打印数字,分别定义不同的计数器打印相应的序列
(此处可以不需要指定字母序列,定义计数器通过字符操作计算即可(类似'A'+1 得到对应的字符值并转化
))
/**
* 两个线程交替打印出a1b2c3.....z26
*/
public class Solution1 {
// 定义对象锁
static Object lock = new Object();
// 定义打印标识
static int printMark = 1;
// 定义数字计数器
static AtomicInteger numCount = new AtomicInteger(1);
// 定义字母计数器
static AtomicInteger letterCount = new AtomicInteger(0);
public static void main(String[] args) {
// 定义线程1打印数字
Thread t1 = new Thread(new Runnable() {
public void run() {
while (numCount.get() <= 26) {
synchronized (lock) {
// 判断是否为当前轮次(打印数字)
while (printMark != 1) {
// 非打印数字轮次,持续等待
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 打印数字、计数+1、切换标识、唤醒线程
System.out.println(numCount.getAndIncrement());
printMark = 2;
lock.notifyAll();
}
}
}
});
// 定义线程2打印字母
Thread t2 = new Thread(new Runnable() {
public void run() {
while (letterCount.get() < 26) {
synchronized (lock) {
// 判断是否为当前轮次(打印字母)
while (printMark != 2) {
// 非打印字母轮次,持续等待
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 打印字母、计数+1、切换标识、唤醒线程
System.out.println(Character.toChars('A' + letterCount.getAndIncrement()));
printMark = 1;
lock.notifyAll();
}
}
}
});
// 启动线程
t1.start();
t2.start();
}
}
8.两个线程,一个打印abcd,一个打印1234,需求交替打印出a1b2c3d4a1b2c3d4; 打印10轮
核心:回归双线程打印思路,一个线程打印字母,一个线程打印数字,打印的内容是在指定的集合中(可以定义数组存储,也可通过计数计算得到),对应计数器对序列长度求余得到当前要打印的位置
此处需注意打印10个轮次,即10个abcd、10个1234,那么总计数应该是40
/**
* 双线程打印:一个打印abcd,一个打印1234,交替打印a1b2c3d4a1b2c3d4; 打印10轮
*/
public class Solution2 {
// 定义对象锁
static Object lock = new Object();
// 定义打印标识(1打印字母;2打印数字)
static int printMark = 1;
// 定义字母打印计数
static AtomicInteger letterCount = new AtomicInteger(0);
// 定义数字打印计数
static AtomicInteger numCount = new AtomicInteger(1);
public static void main(String[] args) {
// 定义线程1打印字母
Thread t1 = new Thread(new Runnable() {
public void run() {
while(letterCount.get() < 40) {
synchronized (lock) {
while(printMark!=1){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 执行打印、打印自增、切换标识、唤醒线程
System.out.println(Character.toChars('a' + letterCount.getAndIncrement()%4));
printMark=2;
lock.notifyAll();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
while(numCount.get() < 40) {
synchronized (lock) {
while(printMark!=2){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 执行打印、计数自增(处理临界值)、切换标识、唤醒线程
// if(numCount.getAndIncrement()%4==0){
// System.out.println(4);
// }else{
System.out.println(numCount.getAndIncrement()%4);
// }
printMark=1;
lock.notifyAll();
}
}
}
});
// 启动线程
t1.start();
t2.start();
}
}
9.假设有T1、T2、T3三个线程,怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
join()
:"加入",让主线程等待(WAITING
状态),一直到调用join()
方法的线程执行结束为止
// join的等效写法
thread1.join();
thread2.join();
// 等价于
while(thread1.isAlive()||thread2.isAlive()){
// 只要thread1、thread2中有一个线程还活跃,主线程就不会向下执行
}
// join的第二种等价写法
synchronized(thread1)){
thread1.wait();
}
思路1:只使用
join
方法,模拟一个Task任务线程,对于每一个线程启动执行并确保执行完成后再启动下一个线程启动
t1
,调用t1.join()
等待t1
执行完成,随后再启动t2
,调用t2.join()
等待t2
执行完成......以此类推/** * 问题:确保t1、t2、t3线程顺序执行(t1执行完后执行t2,然后执行t3) * 思路1:只使用join方法:让主线程处于等待WAITING状态,只有调用join的线程不再活跃(执行完成)主线程才会继续往下 */ public class Solution1 { public static void main(String[] args) throws InterruptedException { // 创建3个任务(线程) Thread t1 = new Thread(new Task("A")); Thread t2 = new Thread(new Task("B")); Thread t3 = new Thread(new Task("C")); // 启动线程t1,调用join方法等待线程t1执行完成 t1.start(); t1.join(); // 继续启动线程t2、调用join方法等待线程t2执行完成 t2.start(); t2.join(); // 继续启动线程t3、调用join方法等待线程t3执行完成 t3.start(); t3.join(); // 所有线程执行完成,主线程继续往下 System.out.println("所有任务执行完成"); } } /** * 定义Task任务线程 */ class Task implements Runnable { private String taskName; public Task(String taskName) { this.taskName = taskName; } @Override public void run() { // 模拟任务执行沉睡1s try { System.out.println("任务" + taskName + "执行...."); Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
思路2:使用
join
配合CountDownLatch
的简单示例CountDownLatch
是一个同步工具类,允许一个或多个线程一直等待,直到其他线程操作执行完成之后才继续往下执行。其应用场景主要是在一些场合中需要等待某个条件达到要求才能执行后面的操作,或者是线程都完成后触发时间,以便进行后续的操作定义两个
CountDownLatch
实例分别用于控制t1->t2(t1Tot2Latch
)、t2->t3(t2Tot3Latch
)的执行顺序,初始计数都为1,t2的开始执行需要等待t1Tot2Latch
计数减为0,t3的开始执行需要等待t2Tot3Latch
计数减为0,以此确保t2在t1完成之后执行,t3在t2完成之后执行- 此处
CountDownLatch
的引入用于确保t1、t2、t3 的执行顺序,而join
方法的引入则是用于确保所有的子线程(任务)执行完成才继续往下(即所有任务完成,主线程才继续往下)
/** * 问题:确保t1、t2、t3线程顺序执行(t1执行完后执行t2,然后执行t3) * 思路2:join + CountDownLatch 配合使用 * 定义两个CountDownLatch计数器(await\countDown方法),当t1Tot2Latch、t2Tot3Latch分别用于确保t1->t2、t2->t3的执行顺序 * t1线程:执行t1任务,执行完成后计数器t1Tot2Latch减1(表示t1执行完成,可以继续下一步) * t2线程:校验t1是否执行完成(调用t1Tot2Latch.await(),如果还没执行完成则继续等待),执行t2并计数器t2Tot3Latch减1(表示t2执行完成,可以继续下一步) * t3线程:类似地,校验t2是否执行完成,执行t3 * 最后通过join方法确保所有任务都执行完成,然后主线程才可以继续往下 * 此处t1->t2->t3的内部同步执行顺序是由CountDownLatch保证的,而所有线程执行完成才执行主线程则是通过join方法进行控制 */ public class Solution2 { public static void main(String[] args) throws InterruptedException { // 定义两个计数器 CountDownLatch t1Tot2Latch = new CountDownLatch(1);// t1 -> t2 的同步(t1执行完成,计数器减1,才触发t2线程执行) CountDownLatch t2Tot3Latch = new CountDownLatch(1);// t2 -> t3 的同步(t2执行完成,计数器减1,才触发t3线程执行) // 创建3个任务(线程) Thread t1 = new Thread(new Runnable() { @Override public void run() { try { System.out.println("任务" + Thread.currentThread().getName() + "执行"); Thread.sleep(1000); // 模拟执行 } catch (InterruptedException e) { throw new RuntimeException(e); } // t1执行完成,计数器减1 t1Tot2Latch.countDown(); } }, "A"); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { // 等待计数器减为0才执行(此处即等待t1执行完成后计数器减1成功后才执行) t1Tot2Latch.await(); System.out.println("任务" + Thread.currentThread().getName() + "执行"); Thread.sleep(1000); // 模拟执行 // t2执行完成,对应计数器减1 t2Tot3Latch.countDown(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "B"); Thread t3 = new Thread(new Runnable() { @Override public void run() { try { // 等待计数器减为0才执行(此处即等待t2执行完成后计数器减1成功后才执行) t2Tot3Latch.await(); System.out.println("任务" + Thread.currentThread().getName() + "执行"); Thread.sleep(1000); // 模拟执行 } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "C"); // 启动线程t1\t2\t3 t1.start(); t2.start(); t3.start(); // 等待所有线程执行完毕(此处t1\t2\t3的执行顺序由同步器进行控制,此处join的引入是为了确保t1\t2\t3都执行完成才继续主线程往下) t1.join(); t2.join(); t3.join(); // 可以实验下,如果此处将join注释,则可能出现主线程内容输出了但是子线程还没执行完成,因此此处要将主线程阻塞,确保所有的任务执行完成才接着往下 // 所有线程执行完成,主线程继续往下 System.out.println("所有任务执行完成"); } }
- 此处
10.目前有500张票,同时有4(1~4)个购票窗口,模拟购票流程,打印购票结果,比如:从1窗口购买1张票,剩余499张票
定义TicketWindow
模拟窗口购票行为,对外提供购票方法,使用synchronized
块来同步对剩余票数的访问(避免多个线程同时修改票数导致数据不一致问题),当剩余票数为0时窗口线程停止执行
/**
* 模拟4个窗口进行售票
* 1.定义售票窗口模拟售票(一个窗口对应一个任务、线程,执行售票操作)
* 2.使用synchronized同步售票操作的票数减少,避免多线程操作导致数据不一致
* 3.售票是判断余票是否足够,如果不足则停止售票关闭窗口(线程执行结束)
*/
public class Solution1 {
// 定义对象锁
static Object lock = new Object();
// 剩余总票数
static int remainTicket = 20;
/**
* 模拟购票窗口
*/
static class TicketWindow implements Runnable{
private String windowName;
public TicketWindow(String windowName) {
this.windowName = windowName;
}
@Override
public void run() {
while(true){
// 同步块:操作卖票操作
synchronized (lock){
if(remainTicket > 0){
buyTicket();
}else{
System.out.println("票已售完,窗口" + this.windowName + "关闭");
break; // 票已售完,线程结束
}
}
// 模拟购票后的随机操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
/**
* 购票
*/
private void buyTicket(){
System.out.println("窗口" + windowName + "售出一张票,当前余票" + remainTicket);
remainTicket--;
}
}
public static void main(String[] args) {
// 开启4个窗口模拟售票
for (int i = 1; i <= 4; i++) {
new Thread(new TicketWindow("ticketWindow"+i)).start();
}
}
}
11.有一批任务Tasks,现在需要实现按批次执行,并且批次可以动态指定,例如[1,3,5,7]第一批执行,[11,13,15,17]第二批执行,…,最后没有指定的任务就最后一起执行掉。批次之间需要按顺序,前一批执行完了才执行下一批
核心:通过Future的批次执行和等待结果确认来实现任务的批次等待
/**
* 按照指定批次执行任务,可指定任务索引进行批量执行
* 1.定义任务MyTask
* 2.构建一个创建批量任务的方法
* 3.构建一个批量执行任务的方法(入参:要执行的任务列表,要执行的任务批次,根据批次依次进行执行)
* - 根据批次依次执行,记录已执行的任务索引
* - 等待批次任务执行完成(通过Future提供的get方法进行确认),最后执行剩余的任务
*/
public class Solution1 {
// 批量创建任务
public List<MyTask> createTaskList(int num) {
List<MyTask> list = new ArrayList<MyTask>();
for (int i = 0; i < num; i++) {
MyTask task = new MyTask(i);
list.add(task);
}
return list;
}
// 批量执行任务
public void executeTask(List<MyTask> list,List<List<Integer>> batchList) throws ExecutionException, InterruptedException {
// 创建固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
// 定义已执行的索引序列
List<Integer> hasExecuteIndex = new ArrayList<>();
// 根据指定的批次信息执行任务
for (int i = 0; i < batchList.size(); i++) {
// 将要执行的批次准备好,批量执行指定索引的任务
List<Future<Void>> futures = new ArrayList<>();
for(int idx : batchList.get(i)) {
Future<Void> future = (Future<Void>) executor.submit(list.get(idx));
futures.add(future);
hasExecuteIndex.add(idx);
}
// 等待一批任务完成
for (Future<Void> future : futures) {
future.get(); // 等待每个任务执行完成
}
// 清理futures,进入下一批次的执行
futures.clear();
System.out.println("当前批次" + i + "执行完成");
}
// 执行剩下任务
List<Future<Void>> lastFuture = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
// 执行未执行的任务
if(!hasExecuteIndex.contains(i)) {
lastFuture.add((Future<Void>) executor.submit(list.get(i)));
}
}
// 关闭线程池并等待所有任务完成
executor.shutdown();
while(!executor.isTerminated()) {
// 纯等待
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Solution1 s = new Solution1();
// 批量创建任务
List<MyTask> list = s.createTaskList(100);
// 定义执行批次
List<List<Integer>> batchList = new ArrayList<>();
List<Integer> batch1 = Arrays.asList(1,3,5,7);
List<Integer> batch2 = Arrays.asList(11,13,15,17);
batchList.add(batch1);
batchList.add(batch2);
// 调用方法执行批次
s.executeTask(list,batchList);
}
}
class MyTask implements Runnable{
private int taskId;
public MyTask(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程执行" + taskId + "任务");
}
}