線程 Thread
應用程序以進程爲單位運行,一個進程之內可以分爲一到多個線程
window下可以通過任務管理器查看進程
linux 下可以通過ps -fe
進程、線程都可以並行執行, cpu
---程序1
---程序2
---程序1
操作系統中有一個組件叫做任務調度器,將cpu的時間片分給不同的程序使用, 微觀串行(單核),宏觀並行.(多核cpu可以並行)
好處:
1) 多進程,多線程可以讓程序不被阻塞.
2) 充分利用多核cpu的優勢,提高運行效率
1.java中的多線程
Thread 類
創建線程方法1:
Thread t = new Thread() {
public void run() { // 運行
// 線程需要執行的代碼
// 不能拋出檢查異常,只能自己處理檢查異常
}
};
啓動線程 t.start()
並行(parallel)和併發(concurrent)
創建線程的方法2:
把線程和要執行的代碼分開
Thread 代表線程
Runnable 可運行的(線程要執行的代碼)
Runnable runnable = new Runnable() {
public void run(){
// 要執行的代碼
}
};
Thread t = new Thread( runnable );
t.start();
例子:
/*Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
};*/
Runnable r = () -> System.out.println("hello");
// () -> System.out.println("hello");
Thread t = new Thread( r );
t.start();
2. 線程中的常見方法
Thread.sleep(long n); // 讓當前線程休眠n毫秒
Thread.currentThread(); // 找到當前線程
main 方法實際是由主線程(main)來調用的
注意 可以使用jconsle 來查看某個java進程中線程的運行情況
實例方法:
start() 讓線程啓動, 只能調用一次,如果調用了多次會出現IllegalThreadStateException
直接調用run和使用start間接調用run的區別:
直接調用run是在主線程中執行了run,沒有啓動新的線程
使用start是啓動新的線程,通過新的線程間接執行run
join() 等待某個線程執行結束,例:
static int r = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1= new Thread(()->{
System.out.println("t1運行開始");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1運行結束");
r = 100;
});
t1.start();
/*System.out.println("main開始");
t1.join();
System.out.println("main結束");*/
t1.join();
System.out.println(r);
}
join(long n) 表示等待線程結束,但是最多等待n毫秒
其它方法:
getName() 得到線程的名稱
yield() 謙讓
不推薦使用的方法
.stop() 讓線程停止
.suspend() 讓線程暫停
.resume() 讓線程繼續
默認情況下,java進程需要等待所有線程都運行結束,纔會結束.
有一種特殊的線程叫做守護(守護的是主線程)線程,只要主線程運行結束,即使守護線程的代碼沒有執行完,也會跟着主線程一起結束,例:
Thread t1= new Thread(()->{
System.out.println("守護線程開始執行...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("守護線程結束");
});
t1.setDaemon(true); // 設置該線程爲守護線程
t1.start();
Thread.sleep(1000);
interrupt() 可以打斷正在等待的線程(包括sleep, join的等待)例如:
Thread t = new Thread(()->{
try {
Thread.sleep(5000); // 被打斷線程會拋出InterruptedException
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("結束 。。。");
});
t.start();
Thread.sleep(1000);
System.out.println("打斷t線程。。。");
t.interrupt();
3. 線程的併發(Concurrent)
synchronized (同步關鍵字)
語法
synchronized(對象) {
要作爲原子操作代碼
}
用synchronized 解決併發問題:
static int i = 0;
static Object obj = new Object(); // 房間,能容納一個人
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> { // 甲
for (int j = 0; j < 5000; j++) {
synchronized (obj) { // 甲會鎖住這個房間
i++;
} // 甲從房間出來解開了鎖
}
});
Thread t2 = new Thread(() -> { // 乙
for (int j = 0; j < 5000; j++) {
synchronized (obj) { // 乙 在門外等待
i--;
} //
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
每個對象都有一個自己的monitor(監視器),當一個線程調用synchronized(對象),就相當於進入了這個對象的監視器。要檢查有沒有owner,如果沒有,此線程成爲owner; 但如果已經有owner了,這個線程在entryset的區域等待owner的位置空出來。
成爲owner可以理解爲獲得了對象的鎖
在競爭的時候,是非公平的
synchronized必須是進入同一個對象的monitor 纔有上述的效果
4. volatile 易變的
可以用來修飾成員變量和靜態成員變量,他可以防止線程從自己的高速緩存中查找變量的值,必須到主存中獲取它的值。
它保證的是變量在多個線程之間的可見性, 不能保證原子性
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(run){
// ....
}
});
t.start();
Thread.sleep(1000);
run = false;
}
一個線程對run變量的修改對於另一個線程不可見,導致了另一個線程無法停止
synchronized 語句塊既可以保證代碼塊的原子性,也可以保證代碼塊內變量的可見性。但缺點是synchronized是屬於重量級操作,性能會受到影響。
5. synchronized的另外兩種寫法
public synchronized void test() {
}
等價於
public void test() {
synchronized(this) {
}
}
class Test{
public synchronized static void test() {
}
}
等價於
public static void test() {
synchronized(Test.class) {
}
}
6. 線程死鎖
a 線程 獲得 A 對象 鎖
接下來獲取B對象的鎖
b 線程獲得 B對象 鎖
接下來獲取A對象的鎖
例:
Object A = new Object();
Object B = new Object();
Thread a = new Thread(()->{
synchronized (A) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
System.out.println("操作...");
}
}
});
Thread b = new Thread(()->{
synchronized (B) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A) {
System.out.println("操作...");
}
}
});
a.start();
b.start();
檢測死鎖可以使用 jconsole工具
7. wait() notify() notifyAll()
都屬於Object對象的方法
wait() 等待
notify() 喚醒
它們都是線程之間進行協作的手段
obj.wait(); 讓object監視器的線程等待
obj.notify(); 讓object上正在等待的線程中挑一個喚醒
obj.notifyAll(); 讓object上正在等待的線程全部喚醒
必須獲得此對象的鎖,才能調用這幾個方法
Object obj = new Object();
new Thread(()-> {
synchronized (obj) {
System.out.println("thread-0線程執行....");
try {
obj.wait(); // 讓線程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread-0其它代碼....");
}
}).start();
new Thread(()-> {
synchronized (obj) {
System.out.println("thread-1線程執行....");
try {
obj.wait(); // 讓線程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread-1其它代碼....");
}
}).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("喚醒obj上其它線程");
synchronized (obj){
// obj.notify();
obj.notifyAll();
}
}
wait() 方法實際是會釋放對象的鎖,進入WaitSet等待區,從而讓其他線程就機會獲取對象的鎖。無限制等待,直到notify爲止
wait(long n) 有時限的等待, 到n毫秒後結束等待,或是被notify
面試題:sleep(long n) 睡眠n毫秒, wait(long n) 等待n毫秒
1) sleep是Thread方法,而wait是Object的方法
2) sleep不需要強制和synchronized配合使用,但wait需要和synchronized一起用
3) sleep 在睡眠的同時,不會釋放對象鎖的,但wait在等待的時候會釋放對象鎖。
8. 線程的狀態
NEW(新建) 線程剛被創建,但是還沒有調用 start方法
RUNNABLE(可運行) 當調用了start() 方法之後
BLOCKED(阻塞) 當線程進入了monitor監視器區,處於entrySet裏準備競爭鎖的時候,處於阻塞狀態
WAITING(等待) 當調用了對象的wait方法,或調用了線程對象的join方法,進入了WaitSet,處於等待狀態
TIMED_WAITING 當調用wait(long n) join(long n) 進入了WaitSet,處於有限時的等待狀態
當調用sleep(long n) 是讓當前線程放棄cpu的時間片,睡眠一會
TERMINATED (終止)當線程代碼運行結束
五種狀態:
NEW(新建), RUNNABLE(可運行) , RUNNING(正在運行), 阻塞(BLOCKED,WAITING, TIMED_WAITING )TERMINATED(終止)
9. 如何讓兩個線程以固定的順序運行
static Object obj = new Object();
static boolean t2runed = false;// t2是否執行過
// 打印 2, 1
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (obj) {
while(!t2runed) { // 如果t2沒有執行過
try {
obj.wait(); // 線程t1 先等一會
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(1);
});
Thread t2 = new Thread(()->{
System.out.println(2);
synchronized (obj) {
t2runed = true;
obj.notify();
}
});
t1.start();
t2.start();
}
```# 線程
t1 輸出 ++++++++++++++
t2 輸出 -----------------------
t3 輸出 ****************
t1 obj.wait() flag==1
t2 obj.wait() flag ==2
t3 obj.wait() flag ==3
jdk 1.5 5.0
## java.util.concurrent.* java併發工具包
### 1. 創建線程的第三種方式
```java
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
與Runnable的區別,1)有返回結果,2)可以拋出檢查異常
創建代碼:
// 代表一個任務對象
// Callable代表線程中要執行的代碼
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName()+"開始執行");
Thread.sleep(2000);
return "ok";
}
});
// 創建和啓動新線程
new Thread(task).start();
// 獲取返回結果
System.out.println(task.get());
2. 線程池
創建有限的線程資源爲更多的任務提供服務。享元模式
// java中對線程池的抽象:ExecutorService 創建一個固定大小的線程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
/*for (int i = 0; i < 10; i++) {
threadPool.submit(()->{
System.out.println(Thread.currentThread().getName()+"任務執行");
});
}*/
// 執行帶有返回結果的任務
Future future = threadPool.submit(() -> {
System.out.println(Thread.currentThread().getName()+"執行計算...");
Thread.sleep(1000);
return 10;
});
System.out.println(future.get());
threadPool.shutdown(); // 不接收新任務,當所有任務運行結束,整個線程池關閉
一個核心的ExecutorService的實現類:ThreadPoolExecutor
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
corePoolSize 核心線程數目 (最多保留的線程數)
5
maximumPoolSize
10
workQueue 阻塞隊列 如果任務超過了核心線程數,進入隊列進行排隊,直到有空閒的線程
10
如果任務過多,阻塞隊列都放不下了,還會創建新的線程來救急
corePoolSize+救急的線程 <= maximumPoolSize(最大線程數)
21 會拋出拒絕提交任務異常
keepAliveTime 生存時間- 針對救急線程
60
unit 時間單位 秒
// 創建固定大小的線程池
Executors.newFixedThreadPool(2);
核心線程數=最大線程數(沒有救急線程被創建)
阻塞隊列 ×××,可以放任意數量的任務,
適合執行數量有限,長時間運行的任務
// 創建緩衝線程池
Executors.newCachedThreadPool()
核心線程數是0, 最大線程數是Integer的最大值(救急線程可以無限創建)
生存時間是60s
適合任務數比較密集,但每個任務執行時間較短的情況
// 創建單線程線程池
Executors.newSingleThreadExecutor()
使用場景:希望多個任務排隊執行
區別:
Executors.newSingleThreadExecutor() 線程個數始終爲1,不能修改
Executors.newFixedThreadPool(1) 初始時爲1,以後還可以修改
// 帶有日程安排功能的線程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
// 讓任務推遲一段時間執行
// 參數1.任務對象, 參數2,3 推遲的時間
/*service.schedule(()->{
System.out.println("執行任務...");
}, 10L, TimeUnit.SECONDS);*/
// 以一定的頻率反覆執行任務(任務不會重疊)
// 參數1,任務對象, 參數2,初始推遲時間, 參數3,4 時間間隔和單位
/*service.scheduleAtFixedRate(()->{
try {
Thread.sleep(1200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello");
}, 0, 1, TimeUnit.SECONDS);*/
// delay表示從上一個任務結束,到下一個任務開始之間的時間
service.scheduleWithFixedDelay(()->{
System.out.println("hello");
}, 0, 1, TimeUnit.SECONDS);
// service.shutdown();
3. 原子操作類
AtomicInteger
AtomicBoolean
...
// 創建原子整數類
private static AtomicInteger i = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
i.getAndIncrement(); // 獲取並且自增 i++
// i.incrementAndGet(); // 自增並且獲取 ++i
}
});
Thread t2 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
i.getAndDecrement(); // 獲取並且自減 i--
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
4. 線程安全集合類
StringBuffer 線程安全
String 不可變類 , 都是線程安全的
Random 線程安全
Vector 實現了List,並且線程安全
Hashtable 實現了Map,並且線程安全
5.0新增的線程安全集合類
ConcurrentHashMap 實現了Map,並且線程安全
ConcurrentSkipListMap 實現了Map(可排序),並且線程安全
CopyOnWriteArrayList 實現了List,並且線程安全
BlockingQueue 阻塞隊列
隊列 FIFO , first in first out
Queue --> LinkedList
private static BlockingQueue<Product> queue = new ArrayBlockingQueue<>(5);
public static void main(String[] args) {
// 生產者線程
new Thread(()->{
for (int i = 0; i < 10; i++) {
Product p = new Product(i);
System.out.println(Thread.currentThread().getName()+"生產了:"+p);
try {
queue.put(p);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 消費者線程
for (int j = 0; j < 5; j++) {
new Thread(()->{
for (int i = 0; i < 2; i++) {
try {
Product p = queue.take();
System.out.println(Thread.currentThread().getName()+"消費了:"+p);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
5. ThreadLocal
// 線程局部變量
private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal() {
@Override // 初始值
protected Object initialValue() {
return new SimpleDateFormat("yyyy-MM-dd"); // 存入當前線程
}
};
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
SimpleDateFormat sdf = local.get(); // 獲取本線程自己的局部變量
Date date = sdf.parse("1951-10-09"); // 每個線程使用的是自己的SimpleDateFormat因此沒有爭用
System.out.println(Thread.currentThread().getName() + " " + date);
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}
/*for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse("1951-10-09");
System.out.println(Thread.currentThread().getName() + " " + date);
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}*/