假如一個程序有多條執行流程,那么,該程序就是多線程程序。
1.多線程概述
1.1 進程與線程
進程:
正在運行的程序,是系統進行資源分配和調用的獨立單位。
每一個進程都有它自己的內存空間和系統資源。
線程:
是進程中的單個順序控制流,是一條執行路徑
一個進程如果只有一條執行路徑,則稱為單線程程序。
一個進程如果有多條執行路徑,則稱為多線程程序。
1.2 線程調度
CPU分配使用權的機制
線程有兩種調度模型:
分時調度模型: 所有線程輪流使用 CPU 的使用權,平均分配每個線程占用 CPU 的時間片
搶占式調度模型 : 優先讓優先級高的線程使用 CPU,如果線程的優先級相同,那么會隨機選擇一個,優先級高的線程獲取的 CPU 時間片相對多一些。
Java使用的是搶占式調度模型。
1.3 Java程序運行原理
java 命令會啟動 java 虛擬機,啟動 JVM,等于啟動了一個應用程序,也就是啟動了一個進程。該進程會自動啟動一個 “主線程” ,然后主線程去調用某個類的 main 方法。所以 main方法運行在主線程中。在此之前的所有程序都是單線程的。
思考題:jvm虛擬機的啟動是單線程的還是多線程的?
?????答:多線程的。原因是垃圾回收線程也要先啟動,否則很容易會出現內存溢出?,F在的垃圾回收線程加上前面的主線程,最低啟動了兩個線程,所以,jvm的啟動其實是多線程的。
2.Thread類
public class Thread extends Object implements Runnable
繼承了Object類實現了Runnable接口
2.1 構造方法
public Thread();
public Thread(String name);
public Thread(Runnable target);
public Thread(Runnable target, String name);
public Thread(ThreadGroup group, Runnable target);
...
2.2 成員方法即線程控制
//線程休眠
public static void sleep(long millis)
//線程加入,等待該線程終止。
public final void join()
//線程禮讓,暫停當前正在執行的線程對象,并執行其他線程。
public static void yield()
//后臺線程,將該線程標記為守護線程或用戶線程。
//當正在運行的線程都是守護線程時,Java 虛擬機退出。 該方法必須在啟動線程前調用
public final void setDaemon(boolean on)
//中斷線程
//讓線程停止,過時了,但是還可以使用。
public final void stop()
中斷線程。 把線程的狀態終止,并拋出一個InterruptedException。
public void interrupt()
// 設置線程優先級,范圍為1-10,默認為5
// 線程優先級高僅僅表示線程獲取的 CPU時間片的幾率高,但是要在次數比較多,或者多次運行的時候才能看到比較好的效果。
public final void setPriority(int newPriority)
注意:
run與Start 的區別:
1.start方法
用 start方法來啟動線程,是真正實現了多線程, 通過調用Thread類的start()方法來啟動一個線程,這時此線程處于就緒(可運行)狀態,并沒有運行,一旦得到cpu時間片,就開始執行run()方法。但要注意的是,此時無需等待run()方法執行完畢,即可繼續執行下面的代碼。所以run()方法并沒有實現多線程。
2.run方法
run()方法只是類的一個普通方法而已,如果直接調用Run方法,程序中依然只有主線程這一個線程,其程序執行路徑還是只有一條,還是要順序執行,還是要等待run方法體執行完畢后才可繼續執行下面的代碼。
2.3 Thread.State
枚舉類,表示線程運行周期中的幾種狀態。
**NEW **
狀態是指線程剛創建, 尚未啟動
**RUNNABLE **
狀態是線程正在正常運行中, 當然可能會有某種耗時計算/IO等待的操作/CPU時間片切換等, 這個狀態下發生的等待一般是其他系統資源, 而不是鎖, Sleep等
**BLOCKED **
這個狀態下, 是在多個線程有同步操作的場景, 比如正在等待另一個線程的synchronized 塊的執行釋放, 或者可重入的 synchronized塊里別人調用wait() 方法, 也就是這里是線程在等待進入臨界區
**WAITING **
這個狀態下是指線程擁有了某個鎖之后, 調用了他的wait方法, 等待其他線程/鎖擁有者調用 notify / notifyAll 一遍該線程可以繼續下一步操作, 這里要區分 BLOCKED 和 WATING 的區別, 一個是在臨界點外面等待進入, 一個是在臨界點里面wait等待別人notify, 線程調用了join方法 join了另外的線程的時候, 也會進入WAITING狀態, 等待被他join的線程執行結束
**TIMED_WAITING **
這個狀態就是有限的(時間限制)的WAITING, 一般出現在調用wait(long), join(long)等情況下, 另外一個線程sleep后, 也會進入TIMED_WAITING狀態
**TERMINATED **
這個狀態下表示該線程的run方法已經執行完畢了, 基本上就等于死亡了(當時如果線程被持久持有, 可能不會被回收)
幾種狀態之間的關系圖解如下:
注意:在JDK8的API中 Thread.State 并沒有RUNNING這個狀態,這里是為了便于理解畫在圖中。
3.多線程實現的幾種方式
JAVA多線程實現方式主要有三種:
3.1、繼承Thread類,并重寫run()方法
** Demo:**
public class SellTicketDemo {
public static void main(String[] args) {
// 創建三個線程對象
SellTicket st1 = new SellTicket();
SellTicket st2 = new SellTicket();
SellTicket st3 = new SellTicket();
// 給線程對象起名字
st1.setName("窗口1");
st2.setName("窗口2");
st3.setName("窗口3");
// 啟動線程
st1.start();
st2.start();
st3.start();
}
}
class SellTicket extends Thread {
// 定義100張票
// 為了讓多個線程對象共享這100張票,我們其實應該用靜態修飾
private static int tickets = 100;
@Override
public void run() {
// 是為了模擬一直有票
while (tickets > 0) {
System.out.println(getName() + "正在出售第" + (tickets--) + "張票");
}
}
}
3.2、 實現Runnable接口,實現run()方法
** Demo:**
/*
* 實現Runnable接口的方式實現
*/
public class SellTicketDemo {
public static void main(String[] args) {
// 創建資源對象
SellTicket st = new SellTicket();
// 創建三個線程對象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 啟動線程
t1.start();
t2.start();
t3.start();
}
}
class SellTicket implements Runnable {
// 定義100張票
private int tickets = 100;
@Override
public void run() {
while (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "張票");
}
System.out.println(Thread.currentThread().getName() + ":沒票了哦!");
}
}
3.3、使用ExecutorService、Callable、Future實現有返回結果的多線程。
其中前兩種方式線程執行完后都沒有返回值,只有最后一種是帶返回值的。
** Demo:**
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableDemo {
public static void main(String[] args) throws InterruptedException,
ExecutionException {
FutureTask<Integer> f1 = new FutureTask<Integer>(new MyCallable(100));
FutureTask<Integer> f2 = new FutureTask<Integer>(new MyCallable(200));
// FutureTask實現了兩個接口,Runnable和Future
new Thread(f1).start();
new Thread(f2).start();
Integer i1 = f1.get();
Integer i2 = f2.get();
System.out.println(i1);
System.out.println(i2);
}
}
class MyCallable implements Callable<Integer> {
private int number;
public MyCallable(int number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int x = 1; x <= number; x++) {
sum += x;
}
return sum;
}
}
** Demo:**使用線程池
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableDemo {
public static void main(String[] args) throws InterruptedException,
ExecutionException {
// 創建線程池對象
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以執行Runnable對象或者Callable對象代表的線程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
// V get()
Integer i1 = f1.get();
Integer i2 = f2.get();
System.out.println(i1);
System.out.println(i2);
// 結束
pool.shutdown();
}
}
class MyCallable implements Callable<Integer> {
private int number;
public MyCallable(int number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int x = 1; x <= number; x++) {
sum += x;
}
return sum;
}
}
3.4 匿名內部類方式使用多線程
new Thread(){代碼…}.start();
New Thread(new Runnable(){代碼…}).start();
** Demo:**
/*
* 匿名內部類的格式:
* new 類名或者接口名() {
* 重寫方法;
* };
* 本質:是該類或者接口的子類對象。
*/
public class ThreadDemo {
public static void main(String[] args) {
// 繼承Thread類來實現多線程
new Thread() {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":"
+ x);
}
}
}.start();
// 實現Runnable接口來實現多線程
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":"
+ x);
}
}
}) .start();
// 更有難度的
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println("hello" + ":" + x);
}
}
}) {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println("world" + ":" + x);
}
}
}.start();
}
}
4.線程同步
4.1 同步代碼塊
** Demo:**
public class SellTicketDemo {
public static void main(String[] args) {
// 創建資源對象
SellTicket st = new SellTicket();
// 創建三個線程對象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 啟動線程
t1.start();
t2.start();
t3.start();
}
}
class SellTicket implements Runnable {
// 定義100張票
private int tickets = 100;
// 創建鎖對象
private Object obj = new Object();
@Override
public void run() {
while (tickets > 0) {
synchronized (obj) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "張票");
}
}
}
System.out.println(Thread.currentThread().getName() + ":沒票了哦!");
}
}
4.2 同步方法
** Demo:**
public class SellTicketDemo {
public static void main(String[] args) {
// 創建資源對象
SellTicket st = new SellTicket();
// 創建三個線程對象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 啟動線程
t1.start();
t2.start();
t3.start();
}
}
class SellTicket implements Runnable {
// 定義100張票
private static int tickets = 100;
private int x = 0;
@Override
public void run() {
while (tickets > 0) {
if (x % 2 == 0) {
// 當同步方法為靜態時鎖對象應該為SellTicket.class
synchronized (this) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "張票 ");
}
}
} else {
sellTicket();
}
x++;
}
System.out.println(Thread.currentThread().getName() + ":沒票了哦!");
}
// 當同步方法為靜態時鎖對象應該為SellTicket.class
private synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "張票 ");
}
}
}
** 注意:**
1.同步代碼塊的鎖對象是任意對象。
2.同步方法的鎖對象是this。
3.靜態同步方法的鎖對象是類的字節碼文件對象。
4.3 Lock鎖的使用
雖然我們可以理解同步代碼塊和同步方法的鎖對象問題,但是我們并沒有直接看到在哪里加上了鎖,在哪里釋放了鎖,為了更清晰的表達如何加鎖和釋放鎖,JDK5以后提供了一個新的鎖對象Lock
Demo:
/*
* Lock:
* void lock(): 獲取鎖。
* void unlock():釋放鎖。
* ReentrantLock是Lock的實現類.
*/
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicketDemo {
public static void main(String[] args) {
// 創建資源對象
SellTicket st = new SellTicket();
// 創建三個窗口
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 啟動線程
t1.start();
t2.start();
t3.start();
}
}
class SellTicket implements Runnable {
// 定義票
private int tickets = 100;
// 定義鎖對象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (tickets > 0) {
try {
// 加鎖
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "張票");
}
} finally {
// 釋放鎖
lock.unlock();
}
}
System.out.println(Thread.currentThread().getName() + ":沒票了哦!");
}
}
4.4 死鎖
是指兩個或者兩個以上的線程在執行的過程中,因爭奪資源產生的一種互相等待現象.
如果出現了同步嵌套,就容易產生死鎖問題
Demo:
/*
* 同步的弊端:
* A:效率低
* B:容易產生死鎖
*
*
* 舉例:
* 中國人,美國人吃飯案例。
* 正常情況:
* 中國人:筷子兩支
* 美國人:刀和叉
* 現在:
* 中國人:筷子1支,刀一把
* 美國人:筷子1支,叉一把
*/
public class DieLockDemo {
public static void main(String[] args) {
DieLock dl1 = new DieLock(true);
DieLock dl2 = new DieLock(false);
dl1.start();
dl2.start();
}
}
class DieLock extends Thread {
private boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (MyLock.objA) {
System.out.println("if objA");
synchronized (MyLock.objB) {
System.out.println("if objB");
}
}
} else {
synchronized (MyLock.objB) {
System.out.println("else objB");
synchronized (MyLock.objA) {
System.out.println("else objA");
}
}
}
}
}
class MyLock {
// 創建兩把鎖對象
public static final Object objA = new Object();
public static final Object objB = new Object();
}
5. 線程通信
針對同一個資源的操作有不同種類的線程
Demo:生產者消費者模型
import java.util.Vector;
/*
* 籃子容量為5,生產者每1秒鐘生產一個產品,消費者每2秒鐘消費一個產品
* 當籃子為空時消費者會等待生產者生產,當籃子裝滿時生產者等消費者消費
*/
public class ProcucerAndConsumerDemo {
public static void main(String[] args) {
// 用Vector來模擬籃子
Vector obj = new Vector();
// 通過構造函數來共享一個對象
Thread consumer = new Thread(new Consumer(obj));
Thread producter = new Thread(new Producer(obj));
consumer.start();
producter.start();
}
}
class Producer implements Runnable {
private Vector obj;
public Producer(Vector v) {
this.obj = v;
}
public void run() {
while (true) {
synchronized (this.obj) {
try {
if (this.obj.size() > 4) {
System.out.println("Producter:the basked has full!");
this.obj.wait();
}
this.obj.add(new String("apples"));
System.out.println("Producter:I have produced one");
this.obj.notify();
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
class Consumer implements Runnable {
private Vector obj;
public Consumer(Vector v) {
this.obj = v;
}
public void run() {
while (true) {
synchronized (this.obj) {
try {
if (this.obj.size() == 0) {
System.out.println("Consumer:the basked is null!");
this.obj.wait();
}
this.obj.remove(0);
System.out.println("Consumer:I have taken one");
System.out.println("obj size: " + this.obj.size());
this.obj.notify();
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
Result:
Consumer:the basked is null!
Producter:I have produced one
Producter:I have produced one
Consumer:I have taken one
obj size: 1
Consumer:I have taken one
obj size: 0
Consumer:the basked is null!
Producter:I have produced one
Producter:I have produced one
Producter:I have produced one
Producter:I have produced one
Producter:I have produced one
Consumer:I have taken one
obj size: 4
Consumer:I have taken one
obj size: 3
Consumer:I have taken one
obj size: 2
Consumer:I have taken one
obj size: 1
Consumer:I have taken one
obj size: 0
Consumer:the basked is null!
Producter:I have produced one
Producter:I have produced one
6.線程組
把多個線程組合到一起。它可以對一批線程進行分類管理,Java允許程序直接對線程組進行控制。
構造方法
private ThreadGroup();
public ThreadGroup(String name);
public ThreadGroup(ThreadGroup parent, String name);
成員方法
public final void setDaemon(boolean daemon) ;
public final void interrupt();
void add(Thread t);
...
Demo:
public class ThreadGroupDemo {
public static void main(String[] args) {
method1();
// method2();
}
private static void method2() {
// ThreadGroup(String name)
ThreadGroup tg = new ThreadGroup("這是一個新的組");
MyRunnable my = new MyRunnable();
// Thread(ThreadGroup group, Runnable target, String name)
Thread t1 = new Thread(tg, my, "林青霞");
Thread t2 = new Thread(tg, my, "劉意");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
t1.start();
t2.start();
// 通過組名稱設置后臺線程,表示該組的線程都是后臺線程
tg.setDaemon(true);
}
private static void method1() {
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my, "林青霞");
Thread t2 = new Thread(my, "劉意");
// 我不知道他們屬于那個線程組,我想知道,怎么辦
// 線程類里面的方法:public final ThreadGroup getThreadGroup()
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
// 線程組里面的方法:public final String getName()
String name1 = tg1.getName();
String name2 = tg2.getName();
System.out.println(name1);
System.out.println(name2);
// 通過結果我們知道了:線程默認情況下屬于main線程組
// 通過下面的測試,你應該能夠看到,默任情況下,所有的線程都屬于同一個組
System.out.println(Thread.currentThread().getThreadGroup().getName());
t1.start();
t2.start();
// 通過組名稱設置后臺線程,表示該組的線程都是后臺線程
Thread.currentThread().getThreadGroup().setDaemon(true);
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
7.線程池
程序啟動一個新線程成本是比較高的,因為它涉及到要與操作系統進行交互。而使用線程池可以很好的提高性能,尤其是當程序中要創建大量生存期很短的線程時,更應該考慮使用線程池。
線程池里的每一個線程代碼結束后,并不會死亡,而是再次回到線程池中成為空閑狀態,等待下一個對象來使用。
JDK5新增了一個Executors工廠類來產生線程池,有如下幾個方法
public static ExecutorService newCachedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
這些方法的返回值是ExecutorService對象,該對象表示一個線程池,可以執行Runnable對象或者Callable對象代表的線程。
Demo:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
*
* 如何實現線程池的代碼呢?
* A:創建一個線程池對象,控制要創建幾個線程對象。
* public static ExecutorService newFixedThreadPool(int nThreads)
* B:這種線程池的線程可以執行:
* 可以執行Runnable對象或者Callable對象代表的線程
* 做一個類實現Runnable接口。
* C:調用如下方法即可
* Future<?> submit(Runnable task)
* <T> Future<T> submit(Callable<T> task)
* D:我就要結束,可以嗎?
* 可以。
*/
public class ExecutorsDemo {
public static void main(String[] args) {
// 創建一個線程池對象,控制要創建幾個線程對象。
// public static ExecutorService newFixedThreadPool(int nThreads)
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以執行Runnable對象或者Callable對象代表的線程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
// 結束線程池
pool.shutdown();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
8.定時器
定時器是一個應用十分廣泛的線程工具,可用于調度多個定時任務以后臺線程的方式執行。
在Java中,可以通過Timer和TimerTask類來實現定義調度的功能
//Timer
public Timer()
//在指定延遲(delay)后執行任務
public void schedule(TimerTask task, long delay)
//在指定延遲(delay)后間隔(period)執行任務
public void schedule(TimerTask task,long delay,long period)
//在指定時間(time)執行任務
public void schedule(TimerTask task, Date time)
//在指定時間( firstTime)開始間隔(period)執行任務
public void schedule(TimerTask task, Date firstTime, long period)
//TimerTask
public abstract void run()
public boolean cancel()
Demo1:
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo2 {
public static void main(String[] args) {
// 創建定時器對象
Timer t = new Timer();
// 3秒后執行爆炸任務第一次,如果不成功,每隔2秒再繼續炸
t.schedule(new MyTask2(), 3000, 2000);
}
}
// 做一個任務
class MyTask2 extends TimerTask {
@Override
public void run() {
System.out.println("beng,爆炸了");
}
}
Demo2:
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/*
* 需求:在指定的時間刪除我們的指定目錄(你可以指定c盤,但是我不建議,我使用項目路徑下的demo)
*/
public class TimerTest {
public static void main(String[] args) throws ParseException {
Timer t = new Timer();
String s = "2014-11-27 15:45:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(s);
t.schedule(new DeleteFolder(), d);
}
}
class DeleteFolder extends TimerTask {
@Override
public void run() {
File srcFolder = new File("demo");
deleteFolder(srcFolder);
}
// 遞歸刪除目錄
public void deleteFolder(File srcFolder) {
File[] fileArray = srcFolder.listFiles();
if (fileArray != null) {
for (File file : fileArray) {
if (file.isDirectory()) {
deleteFolder(file);
} else {
System.out.println(file.getName() + ":" + file.delete());
}
}
System.out.println(srcFolder.getName() + ":" + srcFolder.delete());
}
}
}