看了好多的面經,手撕代碼中除了算法題最常出現多線程的問題了,以下幾個問題是總結了下出現比較多的,做一下記錄,方便多看看
多線程下賣票
synchronized實現
只要有票就可以賣,賣之前第二次檢查,保證多線程下不會賣出負數
class SellTacket implements Runnable {
static int count = 100;
static Object lock = new Object();
public static void main(String[] args) {
SellTacket sellTacket = new SellTacket();
for (int i = 0; i < 20; i++) {
new Thread(sellTacket, "賣票員" + i).start();
}
}
@Override
public void run() {
while (count > 0) {
synchronized (lock) {
if (count > 0) {
count--;
System.out.println(Thread.currentThread().getName() + "賣出一張票,庫存爲:" + count);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
兩個線程交替打印0-100奇偶數
兩個線程啓動之間睡眠1秒,保證偶數線程先啓動
當打印完成一次後喚醒另一個線程,如果還有打印任務就wait當前線程等待被喚醒
class MyPrint implements Runnable {
private static int count = 0;
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(new MyPrint(), "偶數").start();
Thread.sleep(1000);
new Thread(new MyPrint(), "奇數").start();
}
@Override
public void run() {
while (count <= 100) {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
lock.notifyAll();
if (count <= 100) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
三個線程交替打印0-100的數
三個線程就不能簡單的執行文喚醒直接執行了
使用ReentrantLock+自旋+雙重檢查
操作,如果線程執行到打印位置不該他打印,就自旋等待合適了再操作
class MyPrint {
private static Lock lock = new ReentrantLock();//定義一個lock鎖
private static int count = 0;//確定打印的是什麼內容
public static void main(String[] args) {
new ThreadA().start();
new ThreadB().start();
new ThreadC().start();
}
static class ThreadA extends Thread {
@Override
public void run() {
while (count <= 100) {
try {
lock.lock();
while (count % 3 == 0 && count <= 100) {//自旋
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
} finally {
lock.unlock();
}
}
}
}
static class ThreadB extends Thread {
@Override
public void run() {
while (count <= 100) {
try {
lock.lock();
while (count % 3 == 1 && count <= 100) {//自旋
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
} finally {
lock.unlock();
}
}
}
}
static class ThreadC extends Thread {
@Override
public void run() {
while (count <= 100) {
try {
lock.lock();
while (count % 3 == 2 && count <= 100) {//自旋
System.out.println(Thread.currentThread().getName() + ";" + count++);
}
} finally {
lock.unlock();
}
}
}
}
}
三個線程交替打印十次ABC
思路和打印三個數一致,採用了自旋+ReentrantLock
注意i++要在鎖內,只有成功獲得鎖後才能打印一次
class PrintABC {
private static Lock lock = new ReentrantLock();//定義一個lock鎖
private static int state = 0;//確定打印的是什麼內容
public static void main(String[] args) {
new ThreadA().start();
new ThreadB().start();
new ThreadC().start();
}
static class ThreadA extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; ) {
try {
lock.lock();
while (state % 3 == 0) {//自旋
System.out.println("A");
state++;
i++;
}
} finally {
lock.unlock();
}
}
}
}
static class ThreadB extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; ) {
try {
lock.lock();
while (state % 3 == 1) {//自旋
System.out.println("B");
state++;
i++;
}
} finally {
lock.unlock();
}
}
}
}
static class ThreadC extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; ) {
try {
lock.lock();
while (state % 3 == 2) {//自旋
System.out.println("C");
state++;
i++;
}
} finally {
lock.unlock();
}
}
}
}
}
單例模式
使用雙重檢查實現
class MySingleton {
private volatile static MySingleton singleton;
public MySingleton(){}
public static MySingleton getSingleton() {
if (singleton == null){
synchronized (MySingleton.class){
if (singleton == null){
singleton = new MySingleton();
}
}
}
return singleton;
}
}
- 爲什麼需要雙重檢查:
如果只有一次檢查,實例還沒創建時如果多個線程經過了第一次檢查等待鎖,那麼會重複的創建實例,所以需要第二次檢查過濾掉以上等待的線程
- 爲什麼需要volatile 修飾:
因爲對象創建不是原子性的,分爲
- 創建空對象
- 調用構造方法初始化
- 將對象引用賦值
如果不用volatile 修飾可能發生重排序,創建一個對象後先賦值引用再初始化,如果此時有線程進來獲得了這個引用,引用是沒有初始化的。
volatile 修飾還能保證可見性,創建成功後其他線程能立刻可見
必然死鎖
兩個線程以不同順序獲得鎖,獲得一個鎖之後睡眠一秒保證兩個線程都獲得了一個鎖
class MustDeadLock implements Runnable {
private int flag;
private static Object o1 = new Object();
private static Object o2 = new Object();
public static void main(String[] args) {
MustDeadLock r1 = new MustDeadLock();
MustDeadLock r2 = new MustDeadLock();
r1.flag = 1;
r2.flag = 0;
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
@Override
public void run() {
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread1獲得鎖1");
synchronized (o2) {
System.out.println("thread1獲得兩個鎖");
}
}
} else if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread2獲得鎖2");
synchronized (o1) {
System.out.println("thread2獲得兩個鎖");
}
}
}
}
}
生產者消費者模式
public class Solution {
/**
* producer 循環調用 put
* consumer 循環調用 take
* storage.put:庫存滿了就等待,然後存入後喚醒消費者
* storage.take:庫存0就等待,然後消費後喚醒生產者
*/
public static void main(String[] args) {
Storage storage = new Storage();
Producer producer = new Producer(storage);
Consumer consumer = new Consumer(storage);
new Thread(producer).start();
new Thread(consumer).start();
}
/**
* 生產者
*/
static class Producer implements Runnable {
private Storage storage;
public Producer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.put();
}
}
}
/**
* 消費者
*/
static class Consumer implements Runnable {
private Storage storage;
public Consumer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.take();
}
}
}
/**
* 阻塞隊列
*/
static class Storage {
private int maxSize;
private LinkedList<Integer> storages;
public Storage() {
this.maxSize = 10;
this.storages = new LinkedList<>();
}
public synchronized void put() {
if (storages.size() == maxSize) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storages.add(new Random().nextInt(1000));
System.out.println("庫存爲:" + storages.size());
notify();
}
public synchronized void take() {
if (storages.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("拿到了:" + storages.poll());
System.out.println("還剩下:" + storages.size());
notify();
}
}
}
銀行轉賬死鎖
兩個賬戶相互轉賬,因爲獲得鎖的順序相反,可能出現相互等待的情況,爲了保證一定會死鎖,讓每個線程獲得第一個鎖後都睡眠1秒,保證雙方都獲得了第一個鎖
class TransferMoney implements Runnable {
private int flag;
static Account a = new Account(500);
static Account b = new Account(500);
public static void main(String[] args) throws InterruptedException {
TransferMoney r1 = new TransferMoney();
TransferMoney r2 = new TransferMoney();
r1.flag = 1;
r2.flag = 0;
Thread t1= new Thread(r1);
Thread t2= new Thread(r2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("轉賬完成,a:"+a.balance+",b:"+b.balance);
}
@Override
public void run() {
if (flag == 1) {
transfer(a, b, 200);
} else if (flag == 0) {
transfer(b, a, 100);
}
}
private void transfer(Account from, Account to, int money) {
synchronized (from) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":獲得from鎖");
synchronized (to) {
if (from.balance < money) {
System.out.println("餘額不足");
} else {
from.balance -= money;
to.balance += money;
System.out.println("轉賬成功,交易了:" + money);
}
}
}
}
static class Account {
private int balance;
public Account(int balance) {
this.balance = balance;
}
}
}
銀行轉賬如何避免死鎖
造成死鎖的因素是鎖執行的順序相反,爲了保證鎖獲取順序一致,獲取鎖的hash值進行比較,hash值大的鎖先上鎖,如果兩個鎖的hash值相同,再加一層鎖
class TransferMoney implements Runnable {
private int flag;
static Account a = new Account(500);
static Account b = new Account(500);
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
TransferMoney r1 = new TransferMoney();
TransferMoney r2 = new TransferMoney();
r1.flag = 1;
r2.flag = 0;
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("轉賬完成,a:" + a.balance + ",b:" + b.balance);
}
@Override
public void run() {
if (flag == 1) {
transfer(a, b, 200);
} else if (flag == 0) {
transfer(b, a, 100);
}
}
private void transfer(Account from, Account to, int money) {
//獲取鎖的哈希值
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
//hash值大的鎖先鎖
if (fromHash > toHash) {
synchronized (from) {
synchronized (to) {
if (from.balance < money) {
System.out.println("餘額不足");
} else {
from.balance -= money;
to.balance += money;
System.out.println("轉賬成功,交易了:" + money);
}
}
}
} else if (fromHash < toHash) {
synchronized (to) {
synchronized (from) {
if (from.balance < money) {
System.out.println("餘額不足");
} else {
from.balance -= money;
to.balance += money;
System.out.println("轉賬成功,交易了:" + money);
}
}
}
} else {//hash值相等,再加一層鎖
synchronized (lock) {
if (fromHash > toHash) {
synchronized (from) {
synchronized (to) {
if (from.balance < money) {
System.out.println("餘額不足");
} else {
from.balance -= money;
to.balance += money;
System.out.println("轉賬成功,交易了:" + money);
}
}
}
}
}
}
}
static class Account {
int balance;
public Account(int balance) {
this.balance = balance;
}
}
}
哲學家就餐問題
class Philosopher implements Runnable {
private Object leftChopstick;
private Object rightChopstick;
public Philosopher(Object leftChopstick, Object rightChopstick) {
this.leftChopstick = leftChopstick;
this.rightChopstick = rightChopstick;
}
public static void main(String[] args) {
//定義5個哲學家
Philosopher[] philosophers = new Philosopher[5];
//定義筷子
Object[] chopticks = new Object[philosophers.length];
//初始化筷子
for (int i = 0; i < chopticks.length; i++) {
chopticks[i] = new Object();
}
for (int i = 0; i < philosophers.length; i++) {
Object leftChopstick = chopticks[i % philosophers.length];
Object rightChopstick = chopticks[(i + 1) % philosophers.length];
philosophers[i] = new Philosopher(leftChopstick, rightChopstick);
new Thread(philosophers[i], "哲學家" + (i + 1)).start();
}
}
@Override
public void run() {
try {
while (true) {
doAction("think");
synchronized (leftChopstick) {
doAction("拿起左手邊筷子");
synchronized (rightChopstick) {
doAction("拿起右手邊筷子");
doAction("放下右手邊筷子");
}
doAction("放下左手邊筷子");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//打印在做的事情,並隨機睡眠一段時間
static void doAction(String action) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ":" + action);
Thread.sleep((long)Math.random()*1000);
}
}
解決策略
- 服務員檢查(避免策略)
由服務員進行判斷分配,如果發現可能會發生死鎖,不允許就餐 - 改變一個哲學家拿叉子的順序(避免策略)
改變其中一個拿的順序,破壞環路 - 餐票(避免策略)
喫飯必須拿餐票,餐票一共只有4張,喫完了回收 - 領導調節(檢測與恢復策略)
定時檢查,如果發生死鎖,隨機剝奪一個的筷子
改變一個哲學家拿的順序最方便實現:
修改哲學家初始化循環
for (int i = 0; i < philosophers.length; i++) {
Object leftChopstick = chopticks[i % philosophers.length];
Object rightChopstick = chopticks[(i + 1) % philosophers.length];
if (i == philosophers.length - 1) {
philosophers[i] = new Philosopher(rightChopstick, leftChopstick);
} else {
philosophers[i] = new Philosopher(leftChopstick, rightChopstick);
}
new Thread(philosophers[i], "哲學家" + (i + 1)).start();
}