文章目錄
前言
java多線程同步和通信的方法有如下幾種:
- synchronized關鍵字修飾方法或代碼段,實現數據的互斥訪問
- volatile修飾變量,實現多線程環境下數據的同步
- ReentrantLock可重入鎖,實現數據的互斥訪問
- synchronized結合Object的wait和notify方法,實現線程間的等待通知機制
- ReentrantLock結合Condition接口的await()和signal()方法,實現線程間的等待通知機制
1、synchronized 關鍵字修飾方法或代碼段,只保證臨界數據是互斥訪問的
java的每個對象都有一個內置鎖,當用synchronized關鍵字修飾時,線程會獲取該對象的內置鎖,其他線程沒有獲取該對象的內置鎖就會進入阻塞狀態。
- 對象鎖: synchronized關鍵字修飾代碼段時,需要傳入一個對象,通過該對象的內置鎖來實現代碼塊的同步。
synchronized關鍵字修飾方法時,會將實例化的java對象的內置鎖傳進去,通過該鎖來實現代碼塊的同步。 - 類鎖: synchronized關鍵字修飾靜態方法,會鎖住該類的Class對象,此時如果調用該靜態方法,將會鎖住整個類。
- 注意: 同步是一種高開銷的操作,因此應該儘量減少同步的內容。通常沒有必要同步整個方法,使用synchronized代碼塊同步關鍵代碼即可。
package com.kgc;
/**
* @author:Tokgo J
* @date:2020/2/23
* @aim:
*/
public class Demo1 {
static class MyThread extends Thread{
Bank bank;
public MyThread(String 線程, Bank bank){
this.bank=bank;
}
@Override
public void run(){
for(int i=0;i<10;i++){
bank.save(100); // 多線程調用該同步方法
}
}
}
static class Bank{
private int account = 100 ; // 臨界數據
public int getAccount(){
return account;
}
// 同步方法
public synchronized void save(int money){
account+=money;
System.out.println("當前線程:"+Thread.currentThread().getId()+"當前餘額:"+getAccount());
}
public void save1(int money){
//同步代碼塊
synchronized (this){ // 獲取當前對象鎖
account+=money;
System.out.println("當前線程:"+Thread.currentThread().getId()+"當前餘額:"+getAccount());
}
}
}
public static void main(String[] args) {
Bank bank = new Bank();
MyThread th1 = new MyThread("線程1",bank);
th1.start();
MyThread th2 = new MyThread("線程2",bank);
th2.start();
}
}
2、volatile修飾變量
使用 volatile 修飾域相當於告訴虛擬機該域可能會被其他線程更新,因此可以保證在多線程環境下保證數據的一致性
class Bank {
//需要同步的變量加上volatile
private volatile int account = 100;
public int getAccount() {
return account;
}
//這裏不再需要synchronized
public void save(int money) {
account += money;
}
}
3、ReentrantLock可重入鎖,實現數據的互斥訪問
class Bank{
private ReentrantLock lock = new ReentrantLock();
private int account = 100;//臨界區數據
public int getAccount(){
return account;
}
public void put(int money)
{
lock.lock();
try {
account+=money;
System.out.println(Thread.currentThread().getId()+"存入"+money+" 當前餘額:"+getAccount());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void get(int money)
{
lock.lock();
try {
account-=money;
System.out.println(Thread.currentThread().getId()+"取出"+money+" 當前餘額:"+getAccount());
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}
}
4、wait和notify,實現線程間的等待通知機制
通常在synchronized修飾的代碼塊中使用wait、notify/notifyAll函數。
- 當wait()被執行的時,會釋放當前所持有的鎖,然後讓出CPU,進入等待阻塞狀態;
- 當notify/notifyAll()被執行時候,會喚醒一個或多個正處於等待狀態的線程,然後繼續往下執行,因此最好在同步代碼塊最後執行notify / notifyAll。
//消費者線程類,每隔100ms消費一個產品
class CustomerThread extends Thread{
Bank bank;
public CustomerThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
while(true){
bank.get(1);
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//生產者線程類,每隔300ms生產一個產品
class ProductorThread extends Thread{
Bank bank;
public ProductorThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
while(true){
bank.put(1);
try {
sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//數據緩衝區
class Bank{
private int account = 100;//臨界區數據
public int getAccount(){
return account;
}
public void put(int money){
synchronized(this) //獲取當前對象鎖
{
if(getAccount() >= 120){ //若數量大於120則阻塞當前線程,釋放對象鎖
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
account+=money;//生產一個產品
System.out.println(Thread.currentThread().getId()+"存入"+money+" 當前餘額:"+getAccount());
this.notifyAll();//喚醒其他線程
}
}
public void get(int money){
synchronized(this)
{
if(getAccount() <= 0){ // 若數量小於等於0則阻塞當前線程,釋放對象鎖
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
account-=money; // 消費一個產品
System.out.println(Thread.currentThread().getId()+"取出"+money+" 當前餘額:"+getAccount());
this.notifyAll();//喚醒其他線程
}
}
}
//主函數調用例子
public static void main(String[] args)
{
Bank bank = new Bank();//創建一個緩衝區對象
ProductorThread th1 = new ProductorThread(bank);//創建生產者線程1
th1.start();
ProductorThread th2 = new ProductorThread(bank);//創建生產者線程2
th2.start();
CustomerThread th3 = new CustomerThread(bank);//創建消費者線程1
th3.start();
}
5、ReentrantLock結合Condition接口,實現線程間的等待通知機制
class CustomerThread extends Thread{
Bank bank;
public CustomerThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
while(true){
bank.get(1);
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class ProductorThread extends Thread{
Bank bank;
public ProductorThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
while(true){
bank.put(1);
try {
sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Bank{
private ReentrantLock lock = new ReentrantLock(); // 創建可重入鎖
private Condition notEmpty = lock.newCondition(); // 創建非空條件變量
private Condition notFullCondition = lock.newCondition(); // 創建非滿條件變量
private int account = 100;//臨界區數據
public int getAccount(){
return account;
}
public void put(int money){
lock.lock();
try {
if(getAccount() >= 120){ //當數量已滿時,等待非滿條件
notFullCondition.await();
}
//進行生產
account+=money;
System.out.println(Thread.currentThread().getId()+"存入"+money+" 當前餘額:"+getAccount());
notEmpty.signal();// 非空條件釋放信號
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}
public void get(int money){
lock.lock();
try {
if(getAccount() <= 0){ //當數量爲空時,等待非空條件
notEmpty.await();
}
// 進行消費
account-=money;
System.out.println(Thread.currentThread().getId()+"取出"+money+" 當前餘額:"+getAccount());
notFullCondition.signal();// 非滿條件釋放信號
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}
}
public static void main(String[] args)
{
Bank bank = new Bank();
ProductorThread th1 = new ProductorThread(bank);
th1.start();
CustomerThread th3 = new CustomerThread(bank);
th3.start();
}