一、Java基礎知識復盤-多線程
1.1.線程的創(chuàng)建和使用
方式一:繼承Thread類
- 創(chuàng)建一個類繼承于
Thread類 - 重寫
Thread類的run方法 - 創(chuàng)建繼承
Thread類的子類對象 - 通過子類對象調(diào)用
start()方法
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 1000; i++) {
if (i % 2 == 0) {
System.out.println("我是主線程,求偶數(shù):" + i);
}
}
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
if (i % 2 != 0) {
System.out.println("我是子線程,求奇數(shù):" + i);
}
}
}
}
方式二:實現(xiàn)Runnable接口
- 創(chuàng)建一個實現(xiàn)了
Runnable接口的類。 - 實現(xiàn)接口中定義的
run()抽象方法。 - 創(chuàng)建一個實現(xiàn)類的對象。
- 將實現(xiàn)類的對象通過參數(shù)的形式傳遞到
Thread的構造方法中,以此來創(chuàng)建Thread對象。 - 通過
Thread對象來調(diào)用start()方法。
public class RunnableTest {
public static void main(String[] args) {
MyRunnableThread myRunnableThread = new MyRunnableThread();
Thread thread = new Thread(myRunnableThread);
thread.start();
}
}
class MyRunnableThread implements Runnable {
@Override
public void run() {
Thread.currentThread().setName("子線程");
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
方式三:實現(xiàn)Callable接口
- 創(chuàng)建一個實現(xiàn)類
Callable接口的實現(xiàn)類。 - 重寫
call()方法。 - 創(chuàng)建實現(xiàn)類的對象。
- 將實現(xiàn)
callable接口的實現(xiàn)類對象作為參數(shù)傳遞到FutureTask的構造函數(shù)中。 - 創(chuàng)建一個
Thread對象,并將FutureTask對象作為參數(shù)傳遞,并調(diào)用start()方法。 - 如果需要獲取線程的返回值,就使用
get()方法調(diào)用。
public class CallableTest {
public static void main(String[] args) {
CallableThread callableThread = new CallableThread();
FutureTask<Integer> futureTask = new FutureTask<Integer>(callableThread);
new Thread(futureTask).start();
try {
Integer sum = futureTask.get();
System.out.println(sum);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class CallableThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += i;
}
return sum;
}
}
對比方式二,有什么不同
- 相比
run()方法,可以有返回值。 - 這個方法可以拋出異常,實現(xiàn)
Runnable接口的run方法只能try-catch。 - 支持泛型的返回值。
- 可以借助
FutureTask類,獲取返回結果等操作。
方式四:使用線程池
- 提供指定線程數(shù)量的線程池
- 執(zhí)行指定的線程操作,需要提供實現(xiàn)類Runnable接口或者Callable接口的實現(xiàn)類對象
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
Future submit = executorService.submit(new MyThreadPool());
}
}
class MyThreadPool implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
sum += i;
}
return sum;
}
}
說明:
ExecutorService是真正的線程池接口,常見的子類有ThreadPoolExcutor。
Executors是一個工具類,用于創(chuàng)建不同的線程池,如下
-
Executors.newCachedThreadPool();:創(chuàng)建一個可根據(jù)需要創(chuàng)建新線程的線程池。主要問題是線程數(shù)最大數(shù)是Integer.MAX_VALUE,可能會創(chuàng)建數(shù)量非常多的線程,甚至OOM。 -
Executors.newFixedThreadPool(10);:創(chuàng)建一個可重用固定線程數(shù)的線程池。 主要問題是堆積的請求處理隊列可能會耗費非常大的內(nèi)存,甚至OOM。 -
Executors.newSingleThreadExecutor();:創(chuàng)建一個只有一個線程的線程池。 主要問題是堆積的請求處理隊列可能會耗費非常大的內(nèi)存,甚至OOM。 -
Executors.newScheduledThreadPool(10);:創(chuàng)建一個線程池,可以在定期執(zhí)行。主要問題是線程數(shù)最大數(shù)是Integer.MAX_VALUE,可能會創(chuàng)建數(shù)量非常多的線程,甚至OOM。
尷尬的是,線程池不建議使用Executors去創(chuàng)建,而是通過ThreadPoolExecutor的方式。如下所示:
public class ThreadPoolFactoryTest {
private static ThreadFactory myThreadFactory = new ThreadFactoryBuilder().setNameFormat("Mythread-pool-%d").build();
private static ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(10, 30, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(), myThreadFactory);
public static void main(String[] args) {
myThreadPool.execute(new MyThread2());
}
}
class MyThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
Thread類的常用方法
-
start():啟動當前線程,并調(diào)用run()方法。 -
Thread.currentThread():得到當前線程。 -
Thread.currentThread().getName();:得到當前線程名稱。 -
Thread.currentThread().setName("");:給當前線程設置名稱。 -
yield();:釋放當前CPU執(zhí)行權,也可能釋放后又被自己搶到。 -
join():就是在線程A中調(diào)用線程B的join()方法,就是線程A進入阻塞 狀態(tài),等線程B 全結束后再繼續(xù)執(zhí)行。 -
sleep(2000):當前線程睡眠時間。也就是阻塞指定時間。 -
isAlive():判斷當前線程是否處于存活狀態(tài)。 -
setPriority(Thread.MAX_PRIORITY);:設置線程的優(yōu)先級,優(yōu)先級高只是說優(yōu)先概念執(zhí)行,并不是一定比優(yōu)先級低的先執(zhí)行。高優(yōu)先級的線程比低優(yōu)先級線程搶得CPU的概率要高一點。
1.2.線程的生命周期
線程的生命周期狀態(tài)定義在Thread.State枚舉類中,NEW、RUNNABLE、BLOCKED、WAITING和TIMED_WAITING``TERMINATED。簡單的將一個線程的狀態(tài)有:新建、就緒、運行、阻塞和死亡五種狀態(tài)。
生命周期圖解

image-20190402234652085
1.3.線程的同步
線程的同步就是為類解決線程安全問題,線程安全問題就是程序在單線程和多線程分別執(zhí)行后,結果不一樣。這就出現(xiàn)了線程不安全。
synchronized關鍵字
使用synchronized關鍵字,同步代碼塊,將操作共享數(shù)據(jù)的代碼放在同步代碼塊中,共享數(shù)據(jù)就是指多個線程共同操作的變量。同步監(jiān)視器就是鎖,任何一個類的對象都可以充當鎖,要求多個線程共用同一把鎖。語法如下:
synchronized (同步監(jiān)視器){
}
public class ThreadState {
public static void main(String[] args) {
TicketThread ticketThread1 = new TicketThread();
Thread thread1 = new Thread(ticketThread1);
thread1.setName("窗口一");
thread1.start();
Thread thread2 = new Thread(ticketThread1);
thread2.setName("窗口二");
thread2.start();
Thread thread3 = new Thread(ticketThread1);
thread3.setName("窗口三");
thread3.start();
}
}
class TicketThread implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (this ) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":買票,票號:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}