多线程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();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序员小牧之

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

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

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

打赏作者

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

抵扣说明:

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

余额充值