目录
省流总结:
多线程编程题要点
(1)熟悉如何创建线程、重写run()方法;
(2)如果多线程要对相同的资源进行操作访问,则需要加锁;
(3)如果线程间访问顺序有要求,或者有相互制约,则需要用到信号量。
前置知识:常见的几种创建线程写法
1.继承Thread类
继承Thread类,重写run()方法,用start()创线程并执行——面试时如果有多线程编程题,直接用Thread+匿名内部类写法就好,比较简单
2.实现Runnable接口
与继承Thread类写法类似:
3.实现Callable接口
对比前两者可以有返回值Future,常搭配线程池⼯具ExecutorService使用,ExecutorService 可以使⽤ submit ⽅法来让⼀个 Callable 接⼝执⾏。它会返回
⼀个 Future ,我们后续的程序可以通过这个 Future 的 get ⽅法得到结果。
较完整的写法是下面这样,但建议用的内部类的方式写(更简洁):
实现Callable接口的内部类写法案例——解决多线程卡死问题
思路:其实就是利用Future的get()方法去尝试获取线程执行结果,(get()是阻塞的,因此需要用重写的带有超时时间的get()方法,标蓝)如果获取超时则判定线程卡死,将任务取消+将线程关闭。
// 创建一个线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
// 提交一个任务
Future<String> future = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
// 这里是你要执行的代码,比如br.readLine()
return br.readLine();
}
});
// 设置一个超时时间,比如5秒
long timeout = 5;
// 尝试获取任务的结果,如果超时就抛出异常
try {
String s = future.get(timeout, TimeUnit.SECONDS);
// 如果没有超时,就正常处理结果
System.out.println(s);
} catch (TimeoutException e) {
// 如果超时,就取消任务,并且处理异常
future.cancel(true);
System.out.println("Time out has occurred");
}
// 关闭线程池
executor.shutdown();
多线程编程题案例
1.两线程交替打印
思路:信号量+wait、notify机制即可
(1)使用信号量flag,信号量定两种状态——分别执行线程1、2;
(2)比如线程1先判断信号量是否是自己的状态,不是则wait()等待,如果是则执行(此时线程2在wait()),执行完修改信号量状态(仅两种状态,直接取反),然后notifyAll(),就能唤醒线程2,线程2执行…
(3)注意第(2)点的操作都要加synchronized锁。
2.三线程交替打印
跟上题类似,只是信号量flag需要定义三种状态,还需要规定三个线程执行顺序,比如线程1对应信号量指为1,他执行完后到线程2执行,则把flag修改为2,其他类似。
3.多个线程顺序执行
比如要求线程a执行完才开始线程b, 线程b执行完才开始线程。
直接用Join():
Join()原理补充:虽然是线程t1调用的join(),但其实真正执行的是main线程,简单说就是在t1还存活的时候main线程阻塞住(获取锁,lock.wait()),直到线程t1 die之后才notifyAll()
4.三个窗口卖票
因为有了“票”这个对象,担心多线程并发访问他,所以需要加锁,这题没有出售顺序的要求,因此不用信号量,只加锁就够了,当然用信号量+wait、notify应该也是可以实现的。
注意因为这题有“票”这个物品,相对地加锁就可以是给Ticket对象加锁。
5.生产者消费者模型
跟上题类似,因为生产者、消费者会对“资源”这么个对象有操作,因此也需要锁,而且生产者和消费者有相互约束,还需要配合信号量。因此是锁+信号量的方式,对比上一题,没有“票”这样一个物品,所以需要额外定义一个对象锁(这里是用的字符串大写String),没想到String对象也含有wait、notify方法。
至于选用的锁,可以是synchronized,或者reentrantLock(有序),还有阻塞队列。
reentrantLock的写法稍微麻烦点,还可以用到Condition(强化wait、notify),详见参考博客。
这里只记录synchronized的写法:
public class FruitPlateDemo {
private final static String LOCK = "lock";
private int count = 0;
private static final int FULL = 10;
public static void main(String[] args) {
FruitPlateDemo fruitDemo1 = new FruitPlateDemo();
for (int i = 1; i <= 5; i++) {
new Thread(fruitDemo1.new Producer(), "生产者-" + i).start();
new Thread(fruitDemo1.new Consumer(), "消费者-" + i).start();
}
}
class Producer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (LOCK) {
while (count == FULL) {
try {
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产者 " + Thread.currentThread().getName() + " 总共有 " + ++count + " 个资源");
LOCK.notifyAll();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (LOCK) {
while (count == 0) {
try {
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者 " + Thread.currentThread().getName() + " 总共有 " + --count + " 个资源");
LOCK.notifyAll();
}
}
}
}
}
参考
https://blog.csdn.net/shinecjj/article/details/103792151