執行緒的同步
執行緒的同步
執行緒的安全問題
- 多個執行緒執行的不確定性引起執行的結果的不穩定性
- 多個執行緒對數據的共享,會造成操作的不完整性、會破壞數據(例如窗口買票問題,多個窗口對票數進行共享,會出現兩個窗口賣號碼相同的票給不同的人)
通過同步機制解決執行緒安全問題
方法一:同步程式碼塊
格式
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.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—>同步程式碼塊—>同步方法
死鎖
- 不同執行緒分別佔用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了執行緒的死鎖
- 出現死鎖後,不會出現異常,不會出現提示,只是所有的執行緒都處於阻塞狀態,無法繼續
- 使用同步時,避免出現死鎖
- 避免
- 專門的演算法
- 盡量減少同步資源的定義
- 盡量避免嵌套同步