一.使用阻塞队列实现线程同步
实际开发中使用java.util.concurrent包将有助于简化开发。 本Demo使用其中的LinkedBlockingQueue类来解决生产者消费者问题。
首先我们先了解下这个API:LinkedBlockingQueue< E >是一个基于已连接节点的,范围任意的阻塞队列,此队列按照先进先出排序元素。 队列的头部是在队列中时间最长的元素,队列的尾部是在队列中时间最短的元素,新元素插入到队列的尾部。获取队列元素的操作只会获取头部元素,且如果队列满了或者为空会进入阻塞状态。
方法名 | 作用 |
---|---|
LinkedBloockingQueue() | 创建一个容量为Integer.MAX_VALUE的LinkedBlockQueue |
put(E e) | 在队尾添加一个元素,如果队列满则阻塞 |
size() | 返回队列中的元素个数 |
take() | 移除并返回头部元素,如果队列空则阻塞 |
然后我们先看一下Demo的代码并运行,看下效果:
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.UIManager;
import java.awt.Font;
public class ProducerAndConsumerFrame extends JFrame {
private static final long serialVersionUID = -1644485036183526329L;
private JPanel contentPane;
private BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
private final int size = 10;
private JTextArea producerTextArea;
private JTextArea consumerTextArea;
private JTextArea storageTextArea;
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 {
ProducerAndConsumerFrame frame = new ProducerAndConsumerFrame();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public ProducerAndConsumerFrame() {
setTitle("\u4F7F\u7528\u963B\u585E\u961F\u5217\u5B9E\u73B0\u7EBF\u7A0B\u540C\u6B65");
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\u751F\u4EA7");
startButton.setFont(new Font("微软雅黑", Font.PLAIN, 16));
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
do_startButton_actionPerformed(arg0);
}
});
buttonPanel.add(startButton);
JPanel resultPanel = new JPanel();
contentPane.add(resultPanel, BorderLayout.CENTER);
resultPanel.setLayout(new GridLayout(1, 3, 5, 5));
JPanel producerPanel = new JPanel();
resultPanel.add(producerPanel);
producerPanel.setLayout(new BorderLayout(0, 0));
JLabel producerLabel = new JLabel("\u751F\u4EA7\u8005");
producerLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
producerLabel.setHorizontalAlignment(SwingConstants.CENTER);
producerPanel.add(producerLabel, BorderLayout.NORTH);
JScrollPane producerScrollPane = new JScrollPane();
producerPanel.add(producerScrollPane, BorderLayout.CENTER);
producerTextArea = new JTextArea();
producerTextArea.setFont(new Font("微软雅黑", Font.PLAIN, 16));
producerScrollPane.setViewportView(producerTextArea);
JPanel consumerPanel = new JPanel();
resultPanel.add(consumerPanel);
consumerPanel.setLayout(new BorderLayout(0, 0));
JLabel consumerLabel = new JLabel("\u6D88\u8D39\u8005");
consumerLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
consumerLabel.setHorizontalAlignment(SwingConstants.CENTER);
consumerPanel.add(consumerLabel, BorderLayout.NORTH);
JScrollPane consumerScrollPane = new JScrollPane();
consumerPanel.add(consumerScrollPane, BorderLayout.CENTER);
consumerTextArea = new JTextArea();
consumerTextArea.setFont(new Font("微软雅黑", Font.PLAIN, 16));
consumerScrollPane.setViewportView(consumerTextArea);
JPanel storagePanel = new JPanel();
resultPanel.add(storagePanel);
storagePanel.setLayout(new BorderLayout(0, 0));
JLabel storageLabel = new JLabel("\u4ED3\u5E93");
storageLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
storageLabel.setHorizontalAlignment(SwingConstants.CENTER);
storagePanel.add(storageLabel, BorderLayout.NORTH);
JScrollPane storageScrollPane = new JScrollPane();
storagePanel.add(storageScrollPane, BorderLayout.CENTER);
storageTextArea = new JTextArea();
storageTextArea.setFont(new Font("微软雅黑", Font.PLAIN, 16));
storageScrollPane.setViewportView(storageTextArea);
}
protected void do_startButton_actionPerformed(ActionEvent arg0) {
new Thread(new Producer()).start();
new Thread(new Consumer()).start();
}
private class Producer implements Runnable {
@Override
public void run() {
for (int i = 0; i < size; i++) {// size是域变量,表示添加商品的次数
int b = new Random().nextInt(255); // 生成一个随机数
String text = producerTextArea.getText();// 获得生产者文本域信息
producerTextArea.setText(text + "生产商品:" + b + "\n");// 更新文本域信息
try {
queue.put(b);// 向队列中添加元素
} catch (InterruptedException e) {
e.printStackTrace();
}
String storage = storageTextArea.getText();// 获得仓库文本域信息
storageTextArea.setText(storage + "仓库中还有" + queue.size() + "个商品\n");
try {
Thread.sleep(1000);// 休眠1秒实现动态效果
} catch (InterruptedException ex) {
}
}
}
}
private class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < size; i++) {
int b = 0;
try {
b = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
String text = consumerTextArea.getText();
consumerTextArea.setText(text + "消费商品:" + b + "\n");
String storage = storageTextArea.getText();
storageTextArea.setText(storage + "仓库中还有" + queue.size() + "个商品\n");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行效果:
这其实也是线程间的一种通信,只不过是通过阻塞队列实现的。
此程序的运行逻辑很简单:点击开始生产,开始执行下面代码:
protected void do_startButton_actionPerformed(ActionEvent arg0) {
new Thread(new Producer()).start();
new Thread(new Consumer()).start();
}
也就是消费者线程和生产者线程并发运行。
我们先来看一些生产者任务:
private class Producer implements Runnable {
@Override
public void run() {
for (int i = 0; i < size; i++) {// size是域变量,表示添加商品的次数
int b = new Random().nextInt(255); // 生成一个随机数
String text = producerTextArea.getText();// 获得生产者文本域信息
producerTextArea.setText(text + "生产商品:" + b + "\n");// 更新文本域信息
try {
queue.put(b);// 向队列中添加元素
} catch (InterruptedException e) {
e.printStackTrace();
}
String storage = storageTextArea.getText();// 获得仓库文本域信息
storageTextArea.setText(storage + "仓库中还有" + queue.size() + "个商品\n");
try {
Thread.sleep(1000);// 休眠1秒实现动态效果
} catch (InterruptedException ex) {
}
}
}
}
它的运行逻辑也很简单:进行size(本程序为10)次循环每次循环都执行下面操作:生成一个随机数然后将这个随机数放入阻塞队列中,在GUI上显示生产商品(b)的信息和仓库剩余商品个数,然后线程休眠1s后进入下次循环
消费者任务:
private class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < size; i++) {
int b = 0;
try {
b = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
String text = consumerTextArea.getText();
consumerTextArea.setText(text + "消费商品:" + b + "\n");
String storage = storageTextArea.getText();
storageTextArea.setText(storage + "仓库中还有" + queue.size() + "个商品\n");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
类似,消费者就是从阻塞队列中拿出元素,然后在GUI上显示消费商品(b)的信息和queue.size()的信息,然后进入休眠1s后进入下一次循环。
总结:使用阻塞队列可以大大简化编程,让我们能够快速的编写出生产者消费者的程序。
二.新建有返回值的线程
这里就使用到了Callable接口的方式来创建线程,它可以创建有返回值的线程。
我们先看下例子:
Bank类:
public class Bank {
private static ThreadLocal<Integer> account = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 100;
}
};
public void deposit(int money) {
account.set(account.get() + money);
}
public int getAccount() {
return account.get();
}
}
Transfer类:
import java.util.concurrent.Callable;
import javax.swing.JTextArea;
public class Transfer implements Callable<Integer> {
private Bank bank;
private JTextArea textArea;
public Transfer(Bank bank, JTextArea textArea) {// 利用构造方法初始化变量
this.bank = bank;
this.textArea = textArea;
}
public Integer call() {
for (int i = 0; i < 10; i++) {// 循环10次向账户中存钱
bank.deposit(10);
String text = textArea.getText();
textArea.setText(text + "账户的余额是:" + bank.getAccount() + "\n");
}
return bank.getAccount();// 获得账户的余额
}
}
主类:
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import java.awt.Font;
import javax.swing.UIManager;
public class SynchronizedBankFrame extends JFrame {
private static final long serialVersionUID = 2671056183299397274L;
private JPanel contentPane;
private JTextArea thread1TextArea;
private JTextArea thread2TextArea;
private JTextArea thread3TextArea;
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 {
SynchronizedBankFrame frame = new SynchronizedBankFrame();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public SynchronizedBankFrame() {
setTitle("\u65B0\u5EFA\u6709\u8FD4\u56DE\u503C\u7684\u7EBF\u7A0B");
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(0, 3, 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);
JPanel thread3Panel = new JPanel();
processPanel.add(thread3Panel);
thread3Panel.setLayout(new BorderLayout(0, 0));
JLabel thread3Label = new JLabel("\u4E09\u53F7\u7EBF\u7A0B");
thread3Label.setFont(new Font("微软雅黑", Font.PLAIN, 16));
thread3Label.setHorizontalAlignment(SwingConstants.CENTER);
thread3Panel.add(thread3Label, BorderLayout.NORTH);
JScrollPane thread3ScrollPane = new JScrollPane();
thread3Panel.add(thread3ScrollPane, BorderLayout.CENTER);
thread3TextArea = new JTextArea();
thread3TextArea.setFont(new Font("微软雅黑", Font.PLAIN, 16));
thread3ScrollPane.setViewportView(thread3TextArea);
}
protected void do_button_actionPerformed(ActionEvent arg0) {
Bank bank = new Bank();
Transfer transfer1 = new Transfer(bank, thread1TextArea);// 创建Transfer对象
Transfer transfer2 = new Transfer(bank, thread2TextArea);// 创建Transfer对象
FutureTask<Integer> task1 = new FutureTask<Integer>(transfer1);// 创建FutureTask对象
FutureTask<Integer> task2 = new FutureTask<Integer>(transfer2);// 创建FutureTask对象
Thread thread1 = new Thread(task1);// 创建一号线程
Thread thread2 = new Thread(task2);// 创建二号线程
thread1.start();// 运行一号线程
thread2.start();// 运行二号线程
try {
int thread1Result = task1.get();// 获得一号线程的计算结果
int thread2Result = task2.get();// 获得二号线程的计算结果
thread3TextArea.setText(thread3TextArea.getText() + "一号计算结果是:" + thread1Result + "\n");// 更新三号线程文本域信息
thread3TextArea.setText(thread3TextArea.getText() + "二号计算结果是:" + thread2Result + "\n");// 更新三号线程文本域信息
thread3TextArea.setText(thread3TextArea.getText() + "实际的金额是:" + (thread1Result + thread2Result - 100) + "\n");// 更新三号线程文本域信息
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
里面的关键是:task1.get(); 这个方法会等待计算的完成并获取结果。
效果: