多執行緒Demo學習(執行緒的同步,簡單的執行緒通訊)
一.執行緒的同步
我們使用多執行緒程式設計的一個重要原因在於方便資料的共享。 但是共享就意味著存在安全性問題:如果兩個執行緒同時修改一個資料,該聽誰的?這就引發了同步問題。
1.下面我們用一個銀行存入的例子來演示多執行緒程式設計的非同步的場景:
下面一個銀行例項類:
public class Bank {
private int account = 100;// 假設賬戶的初始金額是100
public void deposit(int money) {// 向賬戶存錢的方法
account += money;
}
public int getAccount() {// 獲得賬戶金額的方法
return account;
}
}
下面是一個銀行的操作任務類:
import javax.swing.JTextArea;
public class Transfer implements Runnable {
private Bank bank;
private JTextArea textArea;
public Transfer(Bank bank, JTextArea textArea) {// 利用構造方法初始化變數
this.bank = bank;
this.textArea = textArea;
}
public void run() {
for (int i = 0; i < 100; i++) {// 迴圈10次向賬戶存錢
bank.deposit(10);// 向賬戶存入10塊錢
String text = textArea.getText();// 獲得文字域內容
textArea.setText(text + "賬戶的餘額是:" + bank.getAccount() + "\n");
}
}
}
下面是應用場景類:
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.GridLayout;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import java.awt.Font;
public class UnsynchronizedBankFrame extends JFrame {
private static final long serialVersionUID = 2671056183299397274L;
private JPanel contentPane;
private JTextArea thread1TextArea;
private JTextArea thread2TextArea;
public static void main(String[] args) {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Throwable e) {
e.printStackTrace();
}
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UnsynchronizedBankFrame frame = new UnsynchronizedBankFrame();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public UnsynchronizedBankFrame() {
setTitle("\u975E\u540C\u6B65\u7684\u6570\u636E\u8BFB\u5199");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(new BorderLayout(0, 0));
JPanel buttonPanel = new JPanel();
contentPane.add(buttonPanel, BorderLayout.SOUTH);
JButton startButton = new JButton("\u5F00\u59CB\u5B58\u94B1");
startButton.setFont(new Font("微軟雅黑", Font.PLAIN, 16));
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
do_button_actionPerformed(arg0);
}
});
buttonPanel.add(startButton);
JPanel processPanel = new JPanel();
contentPane.add(processPanel, BorderLayout.CENTER);
processPanel.setLayout(new GridLayout(1, 2, 5, 5));
JPanel thread1Panel = new JPanel();
processPanel.add(thread1Panel);
thread1Panel.setLayout(new BorderLayout(0, 0));
JLabel thread1Label = new JLabel("\u4E00\u53F7\u7EBF\u7A0B");
thread1Label.setFont(new Font("微軟雅黑", Font.PLAIN, 16));
thread1Label.setHorizontalAlignment(SwingConstants.CENTER);
thread1Panel.add(thread1Label, BorderLayout.NORTH);
JScrollPane thread1ScrollPane = new JScrollPane();
thread1Panel.add(thread1ScrollPane, BorderLayout.CENTER);
thread1TextArea = new JTextArea();
thread1TextArea.setFont(new Font("微軟雅黑", Font.PLAIN, 16));
thread1ScrollPane.setViewportView(thread1TextArea);
JPanel thread2Panel = new JPanel();
processPanel.add(thread2Panel);
thread2Panel.setLayout(new BorderLayout(0, 0));
JLabel thread2Label = new JLabel("\u4E8C\u53F7\u7EBF\u7A0B");
thread2Label.setFont(new Font("微軟雅黑", Font.PLAIN, 16));
thread2Label.setHorizontalAlignment(SwingConstants.CENTER);
thread2Panel.add(thread2Label, BorderLayout.NORTH);
JScrollPane thread2ScrollPane = new JScrollPane();
thread2Panel.add(thread2ScrollPane, BorderLayout.CENTER);
thread2TextArea = new JTextArea();
thread2TextArea.setFont(new Font("微軟雅黑", Font.PLAIN, 16));
thread2ScrollPane.setViewportView(thread2TextArea);
}
protected void do_button_actionPerformed(ActionEvent arg0) {
Bank bank = new Bank();
Thread thread1 = new Thread(new Transfer(bank, thread1TextArea));
thread1.start();
Thread thread2 = new Thread(new Transfer(bank, thread2TextArea));
thread2.start();
}
}
我們通過檢視上面的程式碼可以知道的是,此程式會建立兩個執行緒,在沒有任何防護措施的情況下對銀行賬戶同時進行存錢,每個執行緒存入100塊,如果沒有差錯的話,銀行賬戶的餘額最後應為2100,好,下面我們執行一下看看結果:
很明顯,發生了實際與期望不一致的情況(由於同步問題存在一定的概率性,執行結果如果沒有問題可以多試幾次)。
所以,如果我們想要避免這種問題的發生,就需要通過一些鎖或其他的同步策略來解決。
2.使用內建鎖來解決非同步問題:
如何解決上面的問題呢? 其實我們可以發現兩個執行緒的關鍵操作在於:
bank.deposit(10);// 向賬戶存入10塊錢
public void deposit(int money) {// 向賬戶存錢的方法
account += money;
}
在這裡兩個執行緒呼叫的是同一個物件的同一個方法,這個deposit方法就相當於一個臨界資源,我們需要對它採取一定的措施來解決方法競爭問題:
1)最簡單的方法就是給它加一個內建鎖:
public synchronized void deposit(int money) {// 向賬戶存錢的方法
account += money;
}
這樣的話它每次只能被一個執行緒擁有,也就是說,不會同時有兩個執行緒執行它。
下面我們執行下更改後的程式:
執行結果是我們期望的。
2)我們也可以通過程式碼塊來解決(比前一種方法更優):
public void deposit(int money) {// 向賬戶存錢的方法
synchronized(this){
account += money;
}
}
下面看下效果:
是我們期望的結果。
注意:volatile它只提供可見性(每個執行緒都保證讀取的是最新的值),並不提供互斥性。 所以它不可以解決上面的問題。
3)使用顯示鎖解決
private Lock lock = new ReentrantLock();
public void deposit(int money) {// 向賬戶存錢的方法
lock.lock();
try {
account += money;
}finally{
lock.unlock();
}
}
執行效果:
二.簡單的執行緒通訊
使用多執行緒程式設計的一個重要原因就是執行緒間通訊的代價比較小。下面的例子演示了簡單的執行緒通訊:
package Dome.exa179;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JButton;
import java.awt.GridLayout;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.UIManager;
import java.awt.Font;
public class TransactionFrame extends JFrame {
private static final long serialVersionUID = -4239009401384819805L;
private JPanel contentPane;
private JTextArea senderTextArea;
private JTextArea receiverTextArea;
public static void main(String[] args) {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Throwable e) {
e.printStackTrace();
}
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
TransactionFrame frame = new TransactionFrame();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public TransactionFrame() {
setTitle("\u7B80\u5355\u7684\u7EBF\u7A0B\u901A\u4FE1");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(new BorderLayout(0, 0));
JPanel buttonPanel = new JPanel();
contentPane.add(buttonPanel, BorderLayout.SOUTH);
JButton button = new JButton("\u5F00\u59CB\u4EA4\u6613");
button.setFont(new Font("微軟雅黑", Font.PLAIN, 16));
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
do_button_actionPerformed(arg0);
}
});
buttonPanel.add(button);
JPanel transactionPanel = new JPanel();
contentPane.add(transactionPanel, BorderLayout.CENTER);
transactionPanel.setLayout(new GridLayout(1, 2, 5, 5));
JPanel senderPanel = new JPanel();
transactionPanel.add(senderPanel);
senderPanel.setLayout(new BorderLayout(0, 0));
JLabel senderLabel = new JLabel("\u5356\u5BB6");
senderLabel.setFont(new Font("微軟雅黑", Font.PLAIN, 16));
senderLabel.setHorizontalAlignment(SwingConstants.CENTER);
senderPanel.add(senderLabel, BorderLayout.NORTH);
JScrollPane senderScrollPane = new JScrollPane();
senderPanel.add(senderScrollPane, BorderLayout.CENTER);
senderTextArea = new JTextArea();
senderTextArea.setFont(new Font("微軟雅黑", Font.PLAIN, 16));
senderScrollPane.setViewportView(senderTextArea);
JPanel receiverPanel = new JPanel();
transactionPanel.add(receiverPanel);
receiverPanel.setLayout(new BorderLayout(0, 0));
JLabel receiverLabel = new JLabel("\u4E70\u5BB6");
receiverLabel.setFont(new Font("微軟雅黑", Font.PLAIN, 16));
receiverLabel.setHorizontalAlignment(SwingConstants.CENTER);
receiverPanel.add(receiverLabel, BorderLayout.NORTH);
JScrollPane receiverScrollPane = new JScrollPane();
receiverPanel.add(receiverScrollPane, BorderLayout.CENTER);
receiverTextArea = new JTextArea();
receiverTextArea.setFont(new Font("微軟雅黑", Font.PLAIN, 16));
receiverScrollPane.setViewportView(receiverTextArea);
}
protected void do_button_actionPerformed(ActionEvent arg0) {
Sender sender = new Sender();
Receiver receiver = new Receiver(sender);
Thread st = new Thread(sender);
Thread rt = new Thread(receiver);
st.start();
rt.start();
}
private class Sender implements Runnable {
private String[] products = { "《Java程式設計詞典》", "《Java範例大全》", "《視訊學Java程式設計》", "《細說Java》", "《Java開發實戰寶典》" };// 模擬商品列表
private volatile String product;// 儲存一個商品名稱
private volatile boolean isValid;// 儲存賣家是否傳送商品的狀態
public boolean isIsValid() {// 讀取狀態
return isValid;
}
public void setIsValid(boolean isValid) {// 設定狀態
this.isValid = isValid;
}
public String getProduct() {// 獲得商品
return product;
}
public void run() {
for (int i = 0; i < 5; i++) {// 向買家傳送5次商品
while (isValid) {// 如果已經傳送商品就進入等待狀態,等待買家接收
Thread.yield();
}
product = products[i];// 獲得一件商品
String text = senderTextArea.getText();// 獲得賣家文字域資訊
senderTextArea.setText(text + "傳送:" + product + "\n");// 更新賣家文字域資訊
try {
Thread.sleep(100);// 當前執行緒休眠0.1秒實現傳送的效果
} catch (InterruptedException e) {
e.printStackTrace();
}
isValid = true;// 將狀態設定為已經傳送商品
}
}
}
private class Receiver implements Runnable {
private Sender sender;// 建立一個對傳送者的引用
public Receiver(Sender sender) {// 利用構造方法初始化傳送者引用
this.sender = sender;
}
public void run() {
for (int i = 0; i < 5; i++) {// 接收5次商品
while (!sender.isIsValid()) {// 如果傳送者沒有傳送商品就進行等待
Thread.yield();
}
String text = receiverTextArea.getText();// 獲得賣家文字域資訊
// 更新賣家文字域資訊
receiverTextArea.setText(text + "收到:" + sender.getProduct() + "\n");
try {
Thread.sleep(1000);// 執行緒休眠1秒實現動態傳送的效果
} catch (InterruptedException e) {
e.printStackTrace();
}
sender.setIsValid(false);// 設定賣家傳送商品的狀態為未傳送,這樣賣家就可以繼續傳送商品
}
}
}
}
下面我們看一下執行效果:
同樣我們從程式碼中提取出核心部分(三個):
1)傳送者任務:
private class Sender implements Runnable {
private String[] products = { "《Java程式設計詞典》", "《Java範例大全》", "《視訊學Java程式設計》", "《細說Java》", "《Java開發實戰寶典》" };// 模擬商品列表
private volatile String product;// 儲存一個商品名稱
private volatile boolean isValid;// 儲存賣家是否傳送商品的狀態
public boolean isIsValid() {// 讀取狀態
return isValid;
}
public void setIsValid(boolean isValid) {// 設定狀態
this.isValid = isValid;
}
public String getProduct() {// 獲得商品
return product;
}
public void run() {
for (int i = 0; i < 5; i++) {// 向買家傳送5次商品
while (isValid) {// 如果已經傳送商品就進入等待狀態,等待買家接收
Thread.yield();
}
product = products[i];// 獲得一件商品
String text = senderTextArea.getText();// 獲得賣家文字域資訊
senderTextArea.setText(text + "傳送:" + product + "\n");// 更新賣家文字域資訊
try {
Thread.sleep(100);// 當前執行緒休眠0.1秒實現傳送的效果
} catch (InterruptedException e) {
e.printStackTrace();
}
isValid = true;// 將狀態設定為已經傳送商品
}
}
}
我們可以看到它使用了一個狀態變數來幫助通訊,它的執行邏輯如下:
首先第一次迴圈獲得商品陣列中的一個物品放入一個快取字串中,然後將狀態變數設為true,然後進入第二次迴圈,第二次迴圈的while執行此執行緒進入等待狀態。等待另一個執行緒執行。
2)接收者任務:
private class Receiver implements Runnable {
private Sender sender;// 建立一個對傳送者的引用
public Receiver(Sender sender) {// 利用構造方法初始化傳送者引用
this.sender = sender;
}
public void run() {
for (int i = 0; i < 5; i++) {// 接收5次商品
while (!sender.isIsValid()) {// 如果傳送者沒有傳送商品就進行等待
Thread.yield();
}
String text = receiverTextArea.getText();// 獲得賣家文字域資訊
// 更新賣家文字域資訊
receiverTextArea.setText(text + "收到:" + sender.getProduct() + "\n");
try {
Thread.sleep(1000);// 執行緒休眠1秒實現動態傳送的效果
} catch (InterruptedException e) {
e.printStackTrace();
}
sender.setIsValid(false);// 設定賣家傳送商品的狀態為未傳送,這樣賣家就可以繼續傳送商品
}
}
}
裡面組合了傳送者,這個是實現通訊的關鍵。它的執行邏輯是這樣的:
首先它進行第一次迴圈進入等待狀態,然後傳送者執行緒進入了等待狀態後,它繼續執行,然後將狀態變數設為false,然後第二次迴圈進入等待狀態。 依次類推。
3)應用程式碼:
protected void do_button_actionPerformed(ActionEvent arg0) {
Sender sender = new Sender();
Receiver receiver = new Receiver(sender);
Thread st = new Thread(sender);
Thread rt = new Thread(receiver);
st.start();
rt.start();
}
相關文章
- Java多執行緒學習(3)執行緒同步與執行緒通訊Java執行緒
- Java多執行緒學習——執行緒通訊Java執行緒
- Java多執行緒-執行緒通訊Java執行緒
- 多執行緒和多執行緒同步執行緒
- #大學#Java多執行緒學習02(執行緒同步)Java執行緒
- Java多執行緒—執行緒同步(單訊號量互斥)Java執行緒
- java多執行緒5:執行緒間的通訊Java執行緒
- 【多執行緒總結(二)-執行緒安全與執行緒同步】執行緒
- 多執行緒,執行緒類三種方式,執行緒排程,執行緒同步,死鎖,執行緒間的通訊,阻塞佇列,wait和sleep區別?執行緒佇列AI
- 多執行緒之間通訊及執行緒池執行緒
- Java多執行緒學習(1)建立執行緒與執行緒的生命週期Java執行緒
- 多執行緒學習一(多執行緒基礎)執行緒
- Java多執行緒學習(2)執行緒控制Java執行緒
- 多執行緒------執行緒與程式/執行緒排程/建立執行緒執行緒
- 非同步/同步,阻塞/非阻塞,單執行緒/多執行緒概念梳理非同步執行緒
- 【Java】【多執行緒】執行緒池簡述Java執行緒
- 多執行緒(2)-執行緒同步互斥鎖Mutex執行緒Mutex
- 多執行緒--執行緒管理執行緒
- 執行緒與多執行緒執行緒
- 多執行緒【執行緒池】執行緒
- Swift多執行緒:使用Thread進行多執行緒間通訊,協調子執行緒任務Swift執行緒thread
- ObjC 多執行緒簡析(一)-多執行緒簡述和執行緒鎖的基本應用OBJ執行緒
- 多執行緒的同步和非同步學習執行緒非同步
- Java 多執行緒學習(執行緒通訊——消費者和生產者)Java執行緒
- Java多執行緒學習(五)執行緒間通訊知識點補充Java執行緒
- 多執行緒(2)-執行緒同步條件變數執行緒變數
- Java多執行緒之執行緒同步【synchronized、Lock、volatitle】Java執行緒synchronized
- C#多執行緒開發-執行緒同步 02C#執行緒
- 多執行緒(五)---執行緒的Yield方法執行緒
- 【Java多執行緒】執行緒安全的集合Java執行緒
- Java多執行緒-執行緒池的使用Java執行緒
- Java多執行緒-執行緒中止Java執行緒
- 多執行緒之初識執行緒執行緒
- JavaSE_多執行緒入門 執行緒安全 死鎖 狀態 通訊 執行緒池Java執行緒
- Java多執行緒學習(一)Java多執行緒入門Java執行緒
- 子執行緒與UI執行緒的通訊(委託)執行緒UI
- 【Java】【多執行緒】兩個執行緒間的通訊、wait、notify、notifyAllJava執行緒AI
- 執行緒的同步執行緒