多线程Demo学习(使用信号量实现线程同步,使用原子变量实现线程同步)

一.使用信号量实现线程同步

信号量是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()操作线程同步,它以花费更小的开销实现线程同步,同样,它也具有局限性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小牧之

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值