一、前期基礎知識儲備
(1)線程同步的定義:多線程之間的同步。
(2)多線程同步原因:一個多線程的程序如果是通過Runnable接口實現的,則意味著類中的屬性將被多個線程共享,由此引出資源的同步問題,即當多個線程要操作同一資源時,有可能出現錯誤。
(3)實現多線程同步的方式——引入同步機制:在線程使用一個資源時為其加鎖,這樣其他的線程便不能訪問那個資源了,直到解鎖后才可以訪問。——這樣做的結果,所有線程間會有資源競爭,但是所有競爭的資源是同步的,刷新的,動態的,不會因為線程間的競爭,導致資源“過度消耗”或者“虛擬消耗”。
上代碼,具體展示“過度消耗/虛擬消耗”問題:
- public class TestTicketRunnable{
- public static void main(String[] a){
- TicketThread tThread = new TicketThread();
- new Thread(tThread).start();
- new Thread(tThread).start();
- new Thread(tThread).start();
- }
- };
- class TicketThread implements Runnable {
- private int ticket = 5;
- public void run(){
- for (int i = 0; i < 5; i++){
- if (ticket > 0){
- try {
- Thread.sleep(300);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "賣票:ticket = " + ticket--);
- }
- }
- }
- };
運行結果:
- Thread-0賣票:ticket = 5
- Thread-2賣票:ticket = 5 //虛擬消耗
- Thread-1賣票:ticket = 4
- Thread-1賣票:ticket = 2
- Thread-2賣票:ticket = 3
- Thread-0賣票:ticket = 3 //虛擬消耗
- Thread-0賣票:ticket = -1 //過度消耗
- Thread-1賣票:ticket = 1
- Thread-2賣票:ticket = 0
如上所見,票一共5張,三個線程調用買票,線程1網上賣了售票第1張,緊接著線程2線下也賣了“第一張”出現了“虛擬消耗”的問題;線程3黃牛黨賣了最后1張票,線程1網上又賣了最后1張,出現了“過度消耗”的問題,這兩種問題都是實際生活中不可能發生的,但是在這個3個線程執行中卻出現了,那一定是有問題的,問題的根源就在于,三個渠道的“售票員”不在一個頻道上辦事,或者說沒有相互之間同步所共享的資源,導致這一問題的根本原因,就是相互之間實現方式不同步。
二、使用synchronized實現同步機制
synchronized關鍵字:Java語言的關鍵字,可用來給對象和方法或者代碼塊加鎖,當它鎖定一個方法或者一個代碼塊的時候,同一時刻最多只有一個線程執行這段代碼。
當兩個并發線程訪問同一個對象object中的這個加鎖同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以后才能執行該代碼塊。
它包括兩種用法:synchronized 方法和 synchronized 塊。
即實現線程間同步的方式有兩種:
①使用synchronized同步代碼塊;
②使用synchronized關鍵字創建synchronized()方法
下面分別進行解析,對上面售票的代碼進行改造:
①代碼——使用synchronized同步代碼塊
- class TicketThread implements Runnable {
- private int ticket = 5;
- public void run(){
- for (int i = 0; i < 5; i++){
- synchronized(this){
- if (ticket > 0){
- try {
- Thread.sleep(300);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "賣票:ticket = " + ticket--);
- }
- }
- }
- }
- }
②代碼——使用synchronized關鍵字創建synchronized()方法
- class TicketThreadMethod implements Runnable {
- private int ticket = 5;
- public void run(){
- for (int i = 0; i < 5; i++){
- this.sale();
- }
- }
- public synchronized void sale(){
- if (ticket > 0){
- try {
- Thread.sleep(300);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "賣票:ticket = " + ticket--);
- }
- }
- }
三、synchronized方法和synchronized同步代碼塊的區別:
synchronized同步代碼塊只是鎖定了該代碼塊,代碼塊外面的代碼還是可以被訪問的。
synchronized方法是粗粒度的并發控制,某一個時刻只能有一個線程執行該synchronized方法。
synchronized同步代碼塊是細粒度的并發控制,只會將塊中的代碼同步,代碼塊之外的代碼可以被其他線程同時訪問。
補充:多線程同步鎖synchronized(對象鎖與全局鎖)總結
1.synchronized同步鎖的引入
- /*
- * 非線程安全
- * */
- //多個線程共同訪問一個對象中的實例變量,則會出現"非線程安全"問題
- class MyRunnable1 implements Runnable{
- private int num = 10;
- public void run() {
- try {
- if(num > 0) {
- System.out.println(""+Thread.currentThread().getName()+"開始"+",num= "+num--);
- Thread.sleep(1000);
- System.out.println(""+Thread.currentThread().getName()+"結束");
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }public class Test5_5{
- public static void main(String[] args) {
- MyRunnable1 myRunnable1 = new MyRunnable1();
- Thread thread1 = new Thread(myRunnable1,"線程1");
- Thread thread2 = new Thread(myRunnable1,"線程2");
- thread1.start();
- thread2.start();
- }
- }
上例說明兩個線程同時訪問一個沒有同步的方法,如果兩個線程同時操作業務對象中的實例變量,則會出現“線程不安全”問題。
由此我們引入synchronized關鍵字來實現同步問題:
在Java中使用synchronized關鍵字控制線程同步,控制synchronized代碼段不被多個線程同時執行,synchronized即可以使用在方法上也可以使用在代碼塊中。
2. 對象鎖
(1)synchronized方法(對當前對象進行加鎖)
若我們對如上代碼進行修改,在run()方法上加入synchronized關鍵字使其變為同步方法。
- /*
- * 同步方法
- * */
- class MyRunnable1 implements Runnable{
- private int num = 10;
- public void run() {
- this.print();
- }
- public synchronized void print() {
- if(this.num > 0) {
- System.out.println(""+Thread.currentThread().getName()+"開始"+",num= "+num--);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(""+Thread.currentThread().getName()+"結束");
- }
- }
- }public class Test5_5{
- public static void main(String[] args) {
- MyRunnable1 myRunnable1 = new MyRunnable1();
- Thread thread1 = new Thread(myRunnable1,"線程1");
- Thread thread2 = new Thread(myRunnable1,"線程2");
- thread1.start();
- thread2.start();
- }
- }
結論:若兩個線程同時訪問同一個對象中的同步方法時一定是線程安全的。
(2)synchronized代碼塊(對某一個對象進行加鎖)
如果要使用同步代碼塊必須設置一個要鎖定的對象,所以一般可以鎖定當前對象:this.
- /*
- * 同步代碼塊
- * */
- class MyRunnable1 implements Runnable{
- private int num = 10;
- public void run() {
- try {
- synchronized (this) {
- if(num > 0) {
- System.out.println(""+Thread.currentThread().getName()+"開始"+",num= "+num--);
- Thread.sleep(1000);
- System.out.println(""+Thread.currentThread().getName()+"結束");
- }
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- public class Test5_5{
- public static void main(String[] args) {
- MyRunnable1 myRunnable1 = new MyRunnable1();
- Thread thread1 = new Thread(myRunnable1,"線程1");
- Thread thread2 = new Thread(myRunnable1,"線程2");
- thread1.start();
- thread2.start();
- }
- }
(3)synchronized鎖多對象
- /*
- * synchronized鎖多對象
- * */
- class Sync{
- public synchronized void print() {
- System.out.println("print方法開始:"+Thread.currentThread().getName());
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- System.out.println("print方法結束"+Thread.currentThread().getName());
- }
- }
- class MyThread extends Thread{
- public void run() {
- Sync sync = new Sync();
- sync.print();
- }
- }
- public class Test5_5{
- public static void main(String[] args) {
- for(int i = 0; i < 3;i++) {
- Thread thread = new MyThread();
- thread.start();
- }
- }
- }
根據上例我們可以發現當synchronized鎖多個對象時不能實現同步操作,由此可以得出關鍵字synchronized取得的鎖都是對象鎖,而不是將一段代碼或者方法(函數)當作鎖。哪個線程先執行帶synchronized關鍵字的方法或synchronized代碼塊,哪個線程就有該方法或該代碼塊所持有的鎖,其他線程只能呈現等待狀態,前提是多個線程訪問同一個對象。
只有共享資源的讀寫需要同步化,如果不是共享資源,那么就不需要同步化操作。
3.全局鎖
實現全局鎖有兩種方式:
(1) 將synchronized關鍵字用在static方法上
synchronized加到static靜態方法上是對Class類上鎖,而synchronized加到非static方法上是給對對象上鎖。Class鎖可以對類的所有對象實例起作用。
- /*
- * synchronized用在static方法上
- * */
- class Sync{
- static public synchronized void print() {
- System.out.println("print方法開始:"+Thread.currentThread().getName());
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- System.out.println("print方法結束"+Thread.currentThread().getName());
- }
- }
- class MyThread extends Thread{
- public void run() {
- Sync.print();
- }
- }
- public class Test5_5{
- public static void main(String[] args) {
- for(int i = 0; i < 3;i++) {
- Thread thread = new MyThread();
- thread.start();
- }
- }
- }
(2) 用synchronized對類的Class對象進行上鎖
synchronized(class)代碼塊的作用與synchronized static方法的作用一樣。
- /*
- * synchronized對類的Class對象上鎖
- * */
- class Sync{
- public void print() {
- synchronized (Sync.class) {
- System.out.println("print方法開始:"+Thread.currentThread().getName());
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- System.out.println("print方法結束"+Thread.currentThread().getName());
- }
- }
- }
- class MyThread extends Thread{
- public void run() {
- Sync sync = new Sync();
- sync.print();
- }
- }
- public class Test5_5{
- public static void main(String[] args) {
- for(int i = 0; i < 3;i++) {
- Thread thread = new MyThread();
- thread.start();
- }
- }
- }
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持我們。如有錯誤或未考慮完全的地方,望不吝賜教。
原文鏈接:https://blog.csdn.net/weixin_41101173/article/details/79705582