多线程Demo学习(使用阻塞队列实现线程同步,新建有返回值的线程)

本文介绍如何使用阻塞队列简化生产者消费者模式编程,并演示了通过Callable接口创建有返回值线程的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.使用阻塞队列实现线程同步

实际开发中使用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(); 这个方法会等待计算的完成并获取结果。

效果:在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序员小牧之

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

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

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

打赏作者

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

抵扣说明:

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

余额充值