執行緒的同步
執行緒的安全問題
- 多個執行緒執行的不確定性引起執行的結果的不穩定性
- 多個執行緒對資料的共享,會造成操作的不完整性、會破壞資料(例如視窗買票問題,多個視窗對票數進行共享,會出現兩個視窗賣號碼相同的票給不同的人)
通過同步機制解決執行緒安全問題
方法一:同步程式碼塊
格式
synchronized(同步監視器){
需要被同步的程式碼
}
舉例說明
class Thread implements Runnable{
private Object obj = new Object();
public void run() {
//使用類物件充當鎖
synchronized(obj){
.......
}
}
}
說明
- 操作共享資料的程式碼即為需要被同步的程式碼
- 不能多包含程式碼,也不能少包含程式碼
- 共享資料:多個執行緒共同操作的變數
- 同步監視器:俗稱鎖
- 任何一個類的物件都可以來充當鎖
- 要求多個執行緒必須共用同一把鎖
- 在實現Runnable介面建立多執行緒的方式中,考慮使用this充當同步監視器
- 在繼承Thread類建立多執行緒的方式中,慎用this來充當同步監視器,考慮使用當前類來充當同步監視器
特點
- 好處:解決執行緒的安全問題
- 侷限性:操作同步程式碼時,只能有一個執行緒參與,其他執行緒等待。相當於一個單執行緒的過程,效率低
程式碼實現
實現Runnable介面建立多執行緒的方式
/**
* 建立三個視窗買票,票數100張:使用實現Runnable介面的方式實現的
*/
class WindowThread implements Runnable{
private int ticket = 100;
// private Object obj = new Object();
public void run() {
while (true) {
//此時this:唯一的WindowThread物件
synchronized(this){// 方式二:synchronized(obj){
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread(). getName() + ":" + "買票,票號為" + ticket);
ticket--;
}else{
break;
}
}
}
}
}
public class Test1 {
public static void main(String[] args) {
WindowThread window = new WindowThread();
Thread w1 = new Thread(window);
Thread w2 = new Thread(window);
Thread w3 = new Thread(window);
w1.setName("視窗1");
w1.start();
w2.setName("視窗2");
w2.start();
w3.setName("視窗3");
w3.start();
}
}
繼承Thread類建立多執行緒的方式
class Window extends Thread {
// 大家公用資料,只有100張票
private static int ticket = 100;
private static Object obj = new Object();
public void run() {
while (true) {
//方式二
synchronized(Window.class){
// 方式一:synchronized(obj){
//synchronized(this)錯誤的,此時this代表著三個物件
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":" + "買票,票號為" + ticket);
ticket--;
}else{
break;
}
}
}
}
}
public class Test2 {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("視窗1");
w2.setName("視窗2");
w3.setName("視窗3");
w1.start();
w2.start();
w3.start();
}
}
同步方法
如果操作共享資料的程式碼完整的宣告在一個方法中,就可以將此方法宣告同步的
格式
利用synchronized 修飾方法
public synchronized void XXX(){
}
或
public static synchronized void XXX(){
}
說明
- synchronized修飾方法時鎖定的是呼叫該方法的物件
- 同步方法仍然涉及到同步監視器,只是不需要我們顯示的宣告
- 非靜態的同步方法,同步監視器是this
- 靜態的同步方法,同步監視器是當前類本身(Window.class)
程式碼實現
實現Runnable介面建立多執行緒的方式
非靜態同步方法,呼叫this
class WindowThread3 implements Runnable{
private int ticket = 100;
private static boolean isFlag = true;
// private Object obj = new Object();
public void run() {
while (isFlag) {
show();
}
}
public synchronized void show(){//同步監視器:this
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread(). getName() + ":" + "買票,票號為" + ticket);
ticket--;
}else{
isFlag = false;
}
}
}
public class Test3 {
public static void main(String[] args) {
WindowThread3 window = new WindowThread3();
Thread w1 = new Thread(window);
Thread w2 = new Thread(window);
Thread w3 = new Thread(window);
w1.setName("視窗1");
w1.start();
w2.setName("視窗2");
w2.start();
w3.setName("視窗3");
w3.start();
}
}
繼承Thread類建立多執行緒的方式
靜態同步方法,呼叫當前類本身
class Window4 extends Thread{
private static int ticket = 100;
private static boolean isFlag = true;
@Override
public void run() {
while(isFlag){
show();
}
}
public static synchronized void show(){
//同步監視器:Window.class
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread(). getName() + ":" + "買票,票號為" + ticket);
ticket--;
}else{
isFlag = false;
}
}
}
public class Test4 {
public static void main(String[] args) {
Window4 w1 = new Window4();
Window4 w2 = new Window4();
Window4 w3 = new Window4();
w1.setName("視窗1");
w2.setName("視窗2");
w3.setName("視窗3");
w1.start();
w2.start();
w3.start();
}
}
通過Lock(鎖)解決執行緒安全問題
步驟
-
例項化ReentrantLock
private ReentrantLock lock = new ReentrantLock(true);
- true代表公平
- 不填預設為false
-
呼叫鎖的方法
在可能會出現安全問題程式碼前呼叫Lock介面中的方法Lock獲取鎖
lock.lock(); -
呼叫解鎖的方法
lock.unlock();
注意:其中呼叫lock()方法和unlock()方法時要用try()finally()包住
程式碼實現
class Window5 implements Runnable {
private int ticket = 100;
//1.例項化ReentrantLock
private ReentrantLock lock = new ReentrantLock(true);
public void run() {
while (true) {
try{
//2.呼叫鎖定的方法:lock()
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "賣票" + ":" + "票號為" + ticket);
ticket--;
}else{
break;
}
}finally{
//3.呼叫解鎖的方法:unlock()
lock.unlock();
}
}
}
}
public class Test5 {
public static void main(String[] args) {
Window5 window = new Window5();
Thread t1 = new Thread(window);
Thread t2 = new Thread(window);
Thread t3 = new Thread(window);
t1.setName("視窗1:");
t2.setName("視窗2:");
t3.setName("視窗3:");
t1.start();
t2.start();
t3.start();
}
}
synchronized和Lock的異同
異
- synchronized機制在執行完相應的程式碼邏輯後自動釋放同步監視器
- Lock需要手動的啟動同步(lock),同時結束同步也需要手動的實現(unlock)
同
- 都可以解決執行緒安全問題
釋放鎖與不釋放鎖的操作
釋放鎖的操作
- 當前執行緒的同步方法、同步程式碼塊執行結束
- 當前執行緒在同步程式碼塊、同步方法中出現了未處理的Error或Exception,導致異常結束
- 當前執行緒在同步程式碼塊、同步方法中遇到了break、return終止了該程式碼塊、方法的繼續執行
- 當前執行緒在同步程式碼塊、同步方法中執行了執行緒物件的wait()方法,當前執行緒暫停,並釋放鎖
不釋放鎖的操作
- 執行緒在執行同步程式碼塊或同步方法時,程式呼叫了Thread.sleep()或Thread.yield()方法暫停當前執行緒的執行
- 執行緒在執行同步程式碼塊時,其他執行緒呼叫了該執行緒的suspend()方法將該執行緒掛起,該執行緒不會釋放鎖(同步監視器)
- 儘量避免使用suspend()(掛起)和resume()(繼續執行)來控制執行緒
使用順序
Lock--->同步程式碼塊--->同步方法
死鎖
- 不同執行緒分別佔用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了執行緒的死鎖
- 出現死鎖後,不會出現異常,不會出現提示,只是所有的執行緒都處於阻塞狀態,無法繼續
- 使用同步時,避免出現死鎖
- 避免
- 專門的演算法
- 儘量減少同步資源的定義
- 儘量避免巢狀同步