**
1. 什麼是線程同步?
**
當多個線程共同訪問同一資源時,會引發問題,所以需要線程同步來保證對資源的訪問有序進行
例如:
class Toilet implements Runnable{
//記錄可用的廁所數目
private int toilet = 1;
public void run() {
if(toilet>0) {
toilet--;
System.out.println(Thread.currentThread().getName()+"在廁所,別人不要進來");
}
toilet++;
System.out.println(Thread.currentThread().getName()+"從廁所出來了,別人可以進來");
}
}
public class Hello{
public static void main(String []args) {
Toilet t = new Toilet();
for(int i =0;i<10;i++) {
new Thread(t,i+"").start();
}
}
}
輸出:
0在廁所,別人不要進來
0從廁所出來了,別人可以進來
1在廁所,別人不要進來
1從廁所出來了,別人可以進來
3在廁所,別人不要進來
3從廁所出來了,別人可以進來
2在廁所,別人不要進來
2從廁所出來了,別人可以進來
4在廁所,別人不要進來
4從廁所出來了,別人可以進來
5在廁所,別人不要進來
5從廁所出來了,別人可以進來
6在廁所,別人不要進來 //6還沒出來,7就進去了,這顯然不合理
7在廁所,別人不要進來
7從廁所出來了,別人可以進來
6從廁所出來了,別人可以進來
9在廁所,別人不要進來
8在廁所,別人不要進來
8從廁所出來了,別人可以進來
9從廁所出來了,別人可以進來
以上顯然不合理,線程對資源的使用很隨意,當多個線程對同一資源進行修改的時候,會照成混亂。
2. JAVA中線程同步的方式
1. 採用synchronized關鍵字定義代碼塊
synchronized (obj){
…
代碼
}
其中obj是同步監視器,一般將可能被多個線程使用的共享資源充當同步監視器,任何時刻只可能有一個線程能夠訪問同步監視器,當代碼執行完之後纔會釋放同步監視器。
以下情況會釋放同步監視器
- 同步方法、同步代碼塊執行完畢
- 同步方法、同步代碼中遇到return、break
- 出現Error、Exception導致異常結束
- 當前程序執行了同步監視器的wait方法
以下情況不會釋放同步監視器
- 當前程序調用Thread.sleep、Thread.yield方法,暫停當前方法的執行
- 其他線程調用當前線程的suspend方法將當前線程掛起
class Toilet implements Runnable{
//記錄可用的廁所數目
private int toilet = 1;
public void run() {
synchronized(this) { //當前的對象
if(toilet>0) {
toilet--;
System.out.println(Thread.currentThread().getName()+"在廁所,別人不要進來");
}
toilet++;
System.out.println(Thread.currentThread().getName()+"從廁所出來了,別人可以進來");
}
}
}
public class Hello{
public static void main(String []args) {
Toilet t = new Toilet();
for(int i =0;i<10;i++) {
new Thread(t,i+"").start();
}
}
}
輸出:
0在廁所,別人不要進來
0從廁所出來了,別人可以進來
1在廁所,別人不要進來
1從廁所出來了,別人可以進來
2在廁所,別人不要進來
2從廁所出來了,別人可以進來
3在廁所,別人不要進來
3從廁所出來了,別人可以進來
4在廁所,別人不要進來
4從廁所出來了,別人可以進來
5在廁所,別人不要進來
5從廁所出來了,別人可以進來
6在廁所,別人不要進來
6從廁所出來了,別人可以進來
8在廁所,別人不要進來
8從廁所出來了,別人可以進來
7在廁所,別人不要進來
7從廁所出來了,別人可以進來
9在廁所,別人不要進來
9從廁所出來了,別人可以進來
一切正常了
2.採用synchronized關鍵字定義方法
class Toilet implements Runnable{
//記錄可用的廁所數目
private int toilet = 1;
public synchronized void run() { //synchronized應該放在void之前
if(toilet>0) {
toilet--;
System.out.println(Thread.currentThread().getName()+"在廁所,別人不要進來");
}
toilet++;
System.out.println(Thread.currentThread().getName()+"從廁所出來了,別人可以進來");
}
}
public class Hello{
public static void main(String []args) {
Toilet t = new Toilet();
for(int i =0;i<10;i++) {
new Thread(t,i+"").start();
}
}
}
輸出:
0在廁所,別人不要進來
0從廁所出來了,別人可以進來
3在廁所,別人不要進來
3從廁所出來了,別人可以進來
1在廁所,別人不要進來
1從廁所出來了,別人可以進來
2在廁所,別人不要進來
2從廁所出來了,別人可以進來
4在廁所,別人不要進來
4從廁所出來了,別人可以進來
5在廁所,別人不要進來
5從廁所出來了,別人可以進來
8在廁所,別人不要進來
8從廁所出來了,別人可以進來
6在廁所,別人不要進來
6從廁所出來了,別人可以進來
9在廁所,別人不要進來
9從廁所出來了,別人可以進來
7在廁所,別人不要進來
7從廁所出來了,別人可以進來
一切也正常。
3.採用同步鎖Lock來控制線程同步
Lock是JAVA.util.concurrent.locks裏的接口
一般使用ReentrantLock(可重入鎖)類。
例如:
class Toilet implements Runnable{
private final ReentrantLock lock = new ReentrantLock();
public void run() {
lock.lock(); //上鎖
try {
System.out.println(Thread.currentThread().getName()+"在廁所,別人不要進來");
System.out.println(Thread.currentThread().getName()+"出來了,別人可以進去");
} finally {
lock.unlock(); //釋放鎖
}
}
}
public class Hello{
public static void main(String []args) {
Toilet t = new Toilet();
for(int i =0;i<10;i++) {
new Thread(t,i+"").start();
}
}
}
使用線程同步會使程序運行速度變慢,所以只有需要同步的地方纔去使用同步