>多線程創建方式
繼承一個類 Thread
或者實現一個接口 Runnable
重寫其中的 run() 方法(對於實現接口的形式,並沒有繼承run() 方法,要通過構造扔到一個Thread對象中去運行)
使用 start() 方法
>原子性操作
多線程環境下,如果一個操作不是原子性操作,那麼需要考慮它的安全性。
什麼是原子性操作?
不可分割的操作
++i;是不是原子性操作?
不是,在 jvm 中分爲三步:取出 i 的值;讓 i 的值加一; 將自加後的值賦給 i 。
>舉個例子,四個線程,每個線程加一萬次
public class ReTh {
int i = 0;
//加鎖,因爲 i 是非原子性操作,可能第一線程自加未賦值,後續線程又進行了操作
public synchronized void add() {
++i;
}
public static void main(String[] args) {
ReTh rt = new ReTh();
for (int i = 0; i < 4; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
rt.add();
}
}
}).start();
}
while (Thread.activeCount() > 1) {
//等待,併發就是多個分支一併往下走,如果這裏不等待,主線程就在四個線程沒加完
//的時刻就進行了打印 i,得不到40000
}
System.out.println(rt.i);//40000
}
}
>線程控制
首先要明確,併發的線程,搶佔式運行。
三種線程控制:
join() 加塞:
一個線程過來,有"急事",讓他獨立佔用cpu,等它執行完了,我再執行,我們並不是並行關係。
sleep() 睡眠 :
讓當前線程睡眠多久。當前線程醒來的時候,是重新競爭CPU,還是?
當這個線程"睡覺"的時候,是抱着cpu"睡覺",醒來的時候直接獲得執行權力。
yield() 讓步/放棄:
當一個線程獲得cpu執行權力,調用yield,放棄執行權力,退一步,大家再重新搶。
>線程的生命週期
新建:當我們創建了線程對象的時候。
就緒:當我們調用了線程的 start 方法之後。
運行:當獲取了cpu的時候。
阻塞:單說。
死亡:運行完。或者運行的時候拋出異常。
>阻塞
線程進入阻塞狀態的情況:
- 線程調用了一個阻塞方法,方法返回之前該線程一直阻塞。
- 線程調用 sleep() 方法進入阻塞。
- 線程嘗試獲取 同步監視器 ,但該同步監視器被其他線程持有。
- 線程調用 suspend() 方法掛起。
>線程優先級(不可靠,不要依賴優先級控制線程)
new Thread().setPriority(Thread.MAX_PRIORITY);
>線程安全(重點)
多個線程操作的同一個類的同一個對象時,會存在線程安全問題。
解決:同步代碼塊/同步方法
public class TestSafe {
V v = new V();
public void go() {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
v.printString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
v.printString("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
}
}
}).start();
}
class V {
public synchronized void printString(String s) {
/**
* 假設這裏有大量業務代碼,如何降低 鎖 的粒度?
*/
synchronized (String.class) {
}
for (int i = 0; i < s.length(); i++) {
System.out.print(s.charAt(i));
}
System.out.println();
/**
* 假設這裏有大量業務代碼
*/
}
}
public static void main(String[] args) {
TestSafe t = new TestSafe();
t.go();
}
}
>wait / notify,notifyAll(重點)
1.首先明確一點,這三個方法不是Thread類的方法,是Object類的native方法。
2.要調用這些方法,必須獲得同步監視器(鎖),否則會報異常(換句話說我們線程在調用這些方法的地方,要有synchronized關鍵字,這樣執行,就會獲取鎖)。
補充:
1)靜態方法上的鎖
靜態方法是屬於“類”,不屬於某個實例,是所有對象實例所共享的方法。也就是說如果在靜態方法上加入synchronized,那麼它獲取的就是這個類的鎖,鎖住的就是這個類。
2)實例方法(普通方法)上的鎖,鎖住的就是這個對象實例,synchronized(this){}也是鎖的對象。
3.wait 的意思:等待時機成熟再執行,否則就等待。
再執行的時候,必須被別的線程喚醒,它自己醒不了。
4.notify/notifyAll ,專門喚醒等待隊列中的線程(隨機喚醒某一個/喚醒所有的),喚醒之後再判斷條件是否滿足。
>輪詢執行的問題
這種問題的思路:同一時刻只能讓一個線程在運行,其他線程wait() 等待喚醒,如果同一時刻多個線程搶佔CPU就失去了控制;線程的運行和喚醒要通過額外的 條件/flag 進行輪詢的控制。
寫一個程序,讓兩個線程輪詢執行 ?
public class TestWaitNotify {
public static void main(String[] args) {
TestWaitNotify t = new TestWaitNotify();
t.go();
}
V v = new V();
private void go() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
v.f1();
}
}
}, "A線程").start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
v.f2();
}
}
}, "B線程").start();
}
class V {
public boolean flag=true;
public synchronized void f1() {
while(flag){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("F1 " + Thread.currentThread().getName());
flag=true;
notifyAll();
}
public synchronized void f2() {
while(!flag){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("F2 " + Thread.currentThread().getName());
flag=false;
notifyAll();
}
}
}
三個線程輪詢執行?
public class ThreeThreadInTurn {
public static void main(String[] args) {
ThreeThreadInTurn t = new ThreeThreadInTurn();
t.go();
}
V v = new V();
public void go() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
v.f1();
}
}
}, "線程1").start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
v.f2();
}
}
}, "線程2").start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
v.f3();
}
}
}, "線程3").start();
}
class V {
int flag = 1;
public synchronized void f1() {
while (flag != 1) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("F1 " + Thread.currentThread().getName());
flag = 2;
notifyAll();
}
public synchronized void f2() {
while (flag != 2) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("F1 " + Thread.currentThread().getName());
flag = 3;
notifyAll();
}
public synchronized void f3() {
while (flag != 3) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("F1 " + Thread.currentThread().getName());
flag = 1;
notifyAll();
}
}
}