###前言
最近也面了好多家企業,也總結到很多筆試經驗和麵試經驗。筆試大多數Java題目都是牛客網原題和簡單排序,資料庫,
Java
基礎概念,資料結構,MVC
模式等。面試官問的題目涉及的知識無非是Java
基礎知識,設計模式,網路等。我發現出現頻率很高的知識點有多執行緒,設計模式(單例模式,策略模式,觀察者模式)等。今天就來說一下筆試和麵試中常見的多執行緒題目。
###筆試
- 題目:有
A
,B
,C
三個執行緒,,A
執行緒輸出A
,B
執行緒輸出B
,C
執行緒輸出C
,要求,同時啟動三個執行緒,,按順序輸出ABC
,迴圈10
次。這道題目出現的頻率很高啊。
#####第一種思路
-
建立
3
個執行緒輪流輸出,用lock
物件去同步執行緒的狀態,用count
變數標識出哪個執行緒,MAX
變數用於邊界控制,適時退出輪詢。(沒有用到wait()和notify()執行緒通訊機制) -
手寫程式碼
public class PrintABC {
public static void main(String[] args) {
final Lock lock = new ReentrantLock();
Thread a = new Thread(new PrintfABCThread("A", lock, 0));
Thread b = new Thread(new PrintfABCThread("B", lock, 1));
Thread c = new Thread(new PrintfABCThread("C", lock, 2));
a.start();
b.start();
c.start();
}
}
class PrintfABCThread implements Runnable {
private String name;
private Lock lock;
private Integer flag;
public static int count = 0;
public static final int MAX = 30;
public PrintfABCThread(String name, Lock lock, Integer flag) {
this.name = name;
this.lock = lock;
this.flag = flag;
}
@Override
public void run() {
while (true) {
lock.lock();
if (count >= MAX) {
lock.unlock();
return;
}
if (count % 3 == flag) {
System.out.println(name);
count++;
}
lock.unlock();
}
}
}
複製程式碼
- 輸出結果
#####第二種思路
-
通過
Thread
類的join()
方法讓我們開啟的執行緒加入到主執行緒,只有我們開啟的新執行緒結束後,主執行緒才能繼續執行。(不滿足題意,建立了30個執行緒,而且沒有同時開啟執行緒) -
手寫程式碼
public class PrintfABC {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
Thread a = new Thread(new PrintThread("A"));
a.start();
a.join();
Thread b = new Thread(new PrintThread("B"));
b.start();
b.join();
Thread c = new Thread(new PrintThread("C"));
c.start();
c.join();
}
}
}
class PrintThread implements Runnable {
private String name;
public PrintThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name);
}
}
複製程式碼
- 輸出結果
#####第三種思路
- 定義一個MainLock繼承於ReentrantLock,裡面維護著3個condition,用於執行緒之間的通訊。
public class MainLock extends ReentrantLock {
private static final long serialVersionUID = 7103258623232795241L;
private int count = 0;
private final int max;
private final Condition a;
private final Condition b;
private final Condition c;
public MainLock(int max) {
this.max = max;
this.a = this.newCondition();
this.b = this.newCondition();
this.c = this.newCondition();
}
public boolean isEnd() {
if (count >= max) {
return true;
}
return false;
}
public void increase() {
count++;
}
public int getCount() {
return this.count;
}
public int getMax() {
return this.max;
}
public Condition getA() {
return this.a;
}
public Condition getB() {
return this.b;
}
public Condition getC() {
return this.c;
}
public boolean isA() {
return count % 3 == 0;
}
public boolean isB() {
return count % 3 == 1;
}
public boolean isC() {
return count % 3 == 2;
}
}
複製程式碼
- 建立一個定長執行緒池,開啟3個執行緒,分別去處理輸出A,B,C的請求。
public class Main {
public static void main(String[] args) {
MainLock lock = new MainLock(30);
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.submit(new AThread(lock));
pool.submit(new BThread(lock));
pool.submit(new CThread(lock));
pool.shutdown();
}
}
class AThread implements Runnable {
private final MainLock lock;
public AThread(MainLock lock) {
this.lock = lock;
}
@Override
public void run() {
while (true) {
lock.lock();
try {
if (lock.isA()) {
if (lock.isEnd()) {
System.exit(1);
} else {
print();
}
lock.increase();
lock.getB().signal();
} else {
try {
lock.getA().await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
private void print() {
System.out.println("A ");
}
}
class BThread implements Runnable {
private final MainLock lock;
public BThread(MainLock lock) {
this.lock = lock;
}
@Override
public void run() {
while (true) {
lock.lock();
try {
if (lock.isB()) {
if (lock.isEnd()) {
System.exit(1);
} else {
print();
}
lock.increase();
lock.getC().signal();
} else {
try {
lock.getB().await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
private void print() {
System.out.println("B ");
}
}
class CThread implements Runnable {
private final MainLock lock;
public CThread(MainLock lock) {
this.lock = lock;
}
@Override
public void run() {
while (true) {
lock.lock();
try {
if (lock.isC()) {
if (lock.isEnd()) {
System.exit(1);
} else {
print();
}
lock.increase();
lock.getA().signal();
} else {
try {
lock.getC().await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
private void print() {
System.out.println("C ");
}
}
複製程式碼
- 輸出結果
- 第二個題目: 用多執行緒去處理
"abc"
,"def"
,“ghi”
這個三個字串,讓它們以"adg"
,"beh"
,“cfi
”這種形式輸出。這個題目之前是紅星美凱龍技術部筆試卷的壓軸題,分值是20
分。
#####第一種思路
其實跟第一個題目的解決思路是差不多,唯一變的就是我們要獲取下標訪問字串從而獲取字元。我們可以通過count
變數來標識由哪一個執行緒輸出,通過count / 3
獲取下標。(還是沒有用到wait()和notify()機制)
public class DemoTwo {
public static void main(String[] args) {
final Lock lock = new ReentrantLock();
Thread a = new Thread(new PrintThread("abc", lock, 0));
Thread b = new Thread(new PrintThread("def", lock, 1));
Thread c = new Thread(new PrintThread("ghi", lock, 2));
a.start();
b.start();
c.start();
}
}
class PrintThread implements Runnable {
private String name;
private Lock lock;
private Integer flag;
public static int count = 0;
public static int MAX = 9;
public PrintThread(String name, Lock lock, Integer flag) {
this.name = name;
this.lock = lock;
this.flag = flag;
}
@Override
public void run() {
while (true) {
lock.lock();
if (count >= MAX) {
lock.unlock();
return;
}
if (count % 3 == flag) {
System.out.print(name.charAt(count / 3) + " ");
count++;
}
lock.unlock();
}
}
}
複製程式碼
- 輸出結果。
#####第二種思路
-
和上面的思路是一樣的。(沒有同時開啟3個執行緒)
-
手寫程式碼
public class DemoOne {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
Thread a = new Thread(new MyThread("abc", i));
a.start();
a.join();
Thread b = new Thread(new MyThread("def", i));
b.start();
b.join();
Thread c = new Thread(new MyThread("ghi", i));
c.start();
c.join();
System.out.println("");
}
}
}
class MyThread implements Runnable {
private String str;
private int index;
public MyThread(String str, int index) {
this.str = str;
this.index = index;
}
@Override
public void run() {
System.out.print(String.valueOf(str.charAt(index)) + " ");
}
}
複製程式碼
- 輸出結果。
#####第三種思路
public class Main3 {
public static void main(String args[]) {
MainLock lock = new MainLock(9);
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.submit(new XThread(lock));
pool.submit(new YThread(lock));
pool.submit(new ZThread(lock));
pool.shutdown();
}
}
class XThread implements Runnable {
private final MainLock lock;
public XThread(MainLock lock) {
this.lock = lock;
}
@Override
public void run() {
while (true) {
lock.lock();
try {
if (lock.isA()) {
if (lock.isEnd()) {
System.exit(1);
} else {
print();
}
lock.increase();
lock.getB().signal();
} else {
try {
lock.getA().await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
private void print() {
System.out.print("abc".charAt(lock.getCount() / 3));
}
}
class YThread implements Runnable {
private final MainLock lock;
public YThread(MainLock lock) {
this.lock = lock;
}
@Override
public void run() {
while (true) {
lock.lock();
try {
if (lock.isB()) {
if (lock.isEnd()) {
System.exit(1);
} else {
print();
}
lock.increase();
lock.getC().signal();
} else {
try {
lock.getB().await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
private void print() {
System.out.print("def".charAt(lock.getCount() / 3));
}
}
class ZThread implements Runnable {
private final MainLock lock;
public ZThread(MainLock lock) {
this.lock = lock;
}
@Override
public void run() {
while (true) {
lock.lock();
try {
if (lock.isC()) {
if (lock.isEnd()) {
System.exit(1);
} else {
print();
}
lock.increase();
lock.getA().signal();
} else {
try {
lock.getC().await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
private void print() {
System.out.print("ghi".charAt(lock.getCount() / 3));
}
}
複製程式碼
- 輸出結果
###面試 昨天去掃唄面試,面試官問我多執行緒的實現的二種方式和彼此之間的區別。這個也很簡單,百度也爛大街了。
-
採用
extends Thread
方式-
優點:程式設計簡單,如果要訪問當前執行緒,無需使用
Thread.currentThread()
方法,可以直接用this
,即可獲取當前執行緒。 -
缺點:由於繼承了
Thread
,類無法再繼承其他的父類。 -
使用方式:直接
new
相應的執行緒類即可。
-
-
採用
implements Runnable
方式-
優點:沒有繼承
Thread
類,所以可以繼承其他的父類,在這種形式下,多個執行緒可以共享同一個物件,所以非常合適多個相同的執行緒來處理同一份資源的情況下,把cpu
程式碼和資料分開,形成清晰的模型,較好的體現了物件導向的思想。適用場景,比如賣票。 -
缺點:程式設計稍微複雜,如果要訪問當前執行緒,必須使用
Thread.currentThread()
方法。 -
使用方式:不能直接建立所需類的物件並執行它,而是必須從
Thread
類的一個例項內部啟動它。
-
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
複製程式碼
###尾言
就算失望不能絕望,明天又去面試,美滋滋。