一.使用信号量实现线程同步
信号量是Dijkstra在1968年发明,它最初是用来在进程间发信号的一个整数值,一个信号量有且仅有3种操作,且它们全部都是原子操作:初始化,增加和减少。 它的增加可以为一个进程解除阻塞,减少可以让一个进程进入阻塞。 Java为线程提供了信号量支持,本实例将通过向银行存款的例子来实现信号量的同步。
我们先运行看一下效果:
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 java.util.concurrent.Semaphore;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
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;
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("\u4F7F\u7528\u4FE1\u53F7\u91CF\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\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();
Semaphore semaphore = new Semaphore(1, true);
Thread thread1 = new Thread(new Transfer(bank, semaphore, thread1TextArea));
thread1.start();
Thread thread2 = new Thread(new Transfer(bank, semaphore, thread2TextArea));
thread2.start();
}
private class Transfer implements Runnable {
private Bank bank;
private Semaphore semaphore;
private JTextArea textArea;
public Transfer(Bank bank, Semaphore semaphore, JTextArea textArea) {// 初始化变量
this.bank = bank;
this.semaphore = semaphore;
this.textArea = textArea;
}
public void run() {
for (int i = 0; i < 10; i++) {// 循环10次向账户存钱
try {
semaphore.acquire();// 获得一个许可
bank.deposit(10);// 向账户存入10块钱
String text = textArea.getText();
textArea.setText(text + "账户的余额是:" + bank.getAccount() + "\n");
semaphore.release();// 释放一个许可
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private class Bank {
private int account = 100;
public void deposit(int money) {
account += money;
}
public int getAccount() {
return account;
}
}
}
注意:在本代码中没有对Bank类做任何的保护操作,就是使用信号量策略来实现同步的。
下面我们来看一下它的核心代码:
protected void do_button_actionPerformed(ActionEvent arg0) {
Bank bank = new Bank();
Semaphore semaphore = new Semaphore(1, true);
Thread thread1 = new Thread(new Transfer(bank, semaphore, thread1TextArea));
thread1.start();
Thread thread2 = new Thread(new Transfer(bank, semaphore, thread2TextArea));
thread2.start();
}
当我们点击按钮时,程序将会新建一个Bank对象,下面的操作都是对它进行操作的。 然后也新建了一个大小为1信号量对象.
然后新建两个线程,分别将Bank对象和信号量对象作为参数放入线程任务中并运行。
下面我们来看下任务类:
private class Transfer implements Runnable {
private Bank bank;
private Semaphore semaphore;
private JTextArea textArea;
public Transfer(Bank bank, Semaphore semaphore, JTextArea textArea) {// 初始化变量
this.bank = bank;
this.semaphore = semaphore;
this.textArea = textArea;
}
public void run() {
for (int i = 0; i < 10; i++) {// 循环10次向账户存钱
try {
semaphore.acquire();// 获得一个许可
bank.deposit(10);// 向账户存入10块钱
String text = textArea.getText();
textArea.setText(text + "账户的余额是:" + bank.getAccount() + "\n");
semaphore.release();// 释放一个许可
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
我们下面来看一下他到底如何使用信号量来实现同步:
每个线程的执行代码是执行10次循环,每次循环都会通过信号量获得一个许可,如果成功获得,就往下执行,否则进入阻塞状态。 当获得信号量并执行完下面的操作时,就会释放信号量,给下一个线程获取。这就相当于让这两个线程的20次循环串行执行。最后得到正确的结果。
二.使用原子变量实现线程同步
需要使用线程同步的根本原因在于对普通变量的操作不是原子的。 所谓原子操作是将读取变量值,修改变量值,保存变量值看成一个整体,要么同时完成,要么不同时完成。 在javaSE 5.0版新增的java.util.concurrent.atomic包提供了原子类型变量的工具类,使用该类可以简化线程同步编程。
本实例中用到的API:AtomicInteger类
方法名 | 作用 |
---|---|
AtomicInteger(int initialValue) | 创建具有给定初始值的新AtomicInteger |
addAndGet(int delta) | 以原子方式将给定值与当前值相加 |
get() | 获取当前值 |
这个使用原子变量对Bank类进行保护:
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();
}
private class Bank {
private AtomicInteger account = new AtomicInteger(100);// 创建AtomicInteger对象
public void deposit(int money) {
account.addAndGet(money);// 实现存钱
}
public int getAccount() {
return account.get();// 实现取钱
}
}
private 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 < 10; i++) {
bank.deposit(10);
String text = textArea.getText();
textArea.setText(text + "账户的余额是:" + bank.getAccount() + "\n");
}
}
}
我们看一下运行效果:
其关键在于:
private class Bank {
private AtomicInteger account = new AtomicInteger(100);// 创建AtomicInteger对象
public void deposit(int money) {
account.addAndGet(money);// 实现存钱
}
public int getAccount() {
return account.get();// 实现取钱
}
}
上面的每个addAndGet()操作线程同步,它以花费更小的开销实现线程同步,同样,它也具有局限性。