這里用java多線程實(shí)現(xiàn)一個(gè)簡單的抽獎(jiǎng)程序,從100個(gè)座位號(hào)中,隨機(jī)抽取10位别智。
我設(shè)計(jì)的思路是用10個(gè)線程,每個(gè)線程從100個(gè)座位號(hào)中隨機(jī)抽取1位稼稿,然后顯示在黑板上薄榛,然后再去抽取,再顯示让歼。這里涉及到從100 個(gè)座位號(hào)中隨機(jī)抽取時(shí)要線程之間要同步敞恋,否則會(huì)出現(xiàn)同一個(gè)號(hào)被多個(gè)線程抽取。一個(gè)線程大致分為如下幾個(gè)步驟:
a.從待抽取的座位列表中隨機(jī)抽取座位谋右。
b.將抽取的座位號(hào)顯示在黑板對應(yīng)的位置上硬猫,并取得那個(gè)位置上當(dāng)前顯示的座位號(hào)。
c.將b步得到的座位號(hào)添加到待抽取列表中。
經(jīng)過a,b,c三步啸蜜,一個(gè)線程一次的動(dòng)作算完成了馏予,將這三步操作視為一個(gè)原子操作,進(jìn)行線程間同步盔性,剩下的就是循環(huán)抽取座位號(hào)就行了。
有幾點(diǎn)需求注意:
1.b步可以不同步呢岗,但發(fā)現(xiàn)冕香,當(dāng)隨機(jī)抽取的數(shù)量多時(shí),此時(shí)一個(gè)線程(假設(shè)代號(hào)為1)還沒有來得及更新黑板上對應(yīng)位置的座位號(hào)后豫,另一個(gè)線程(假設(shè)代號(hào)為2)已經(jīng)將1線程放入座位組中的號(hào)又取出來悉尾,并顯示在黑板上了。這樣就會(huì)造成黑板上同一個(gè)座位號(hào)顯示了多次挫酿。
2.需要區(qū)分哪些座位號(hào)是抽取過的构眯,哪些座位號(hào)是沒有抽取過的,這樣每次抽取只抽取沒有抽取過的早龟。會(huì)自然而然的用兩個(gè)List表示惫霸。但仔細(xì)想想,不需要用兩個(gè)List區(qū)分葱弟,因?yàn)楹诎迳巷@示的座位號(hào)就是已經(jīng)抽取的壹店,只需要每次從座位池中刪除,然后顯示在黑板上芝加,再次抽取時(shí)硅卢,將黑板上原來顯示的座位號(hào)放回座位池中即可。
3.是否可以用LinkedList代替ArrayList藏杖。大家都知道LinkedList的刪除将塑,插入性能優(yōu)于ArrayList,并且這里線程也需要反復(fù)不斷的對座位池進(jìn)行刪除,新增操作蝌麸。但這里的新增只是在最后面加入点寥,影響不大,有影響的就是刪除操作祥楣。但是這樣還會(huì)有不斷的隨機(jī)取號(hào)操作开财。到底是刪除操作影響大,還是取號(hào)操作影響大呢误褪?經(jīng)過驗(yàn)證發(fā)現(xiàn)责鳍,當(dāng)座位池中座位號(hào)比較大時(shí)(假如為10萬),LinkedList速度會(huì)明顯慢于ArrayList,幾乎有10倍之差兽间。
源碼如下:
抽取號(hào)碼部分:
package cn.yanggy.demo03;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* @author Cool-Coding 2017/12/1
* 此類是隨機(jī)樣本抽取工具類历葛,從N個(gè)樣本源中隨機(jī)抽取M個(gè)樣本
*由于N個(gè)樣本不斷的從隊(duì)列取出,再放到隊(duì)列尾部,N個(gè)樣本抽中的概率相等
* 使用方法:
* RandomFetch randomFetch=new RandomFetch(source,sampleCount,SampleShow);
* randomFetch.start()
* randomFetch.stop()
* randomFetch.pause()
* randomFetch.resume()
*/
public final class RandomFetch<T> {
//源數(shù)據(jù)池
private List<T> source;
//樣本數(shù)
private int smaple;
//結(jié)果回調(diào)接口
private SampleShow<T> sampleShow;
//線程列表
private final List<Picker> threadList = new ArrayList<>();
//停止標(biāo)識(shí)恤溶,使用volatile乓诽,是因?yàn)槎嗑€程共享同一變量時(shí),需要變量改變時(shí)咒程,線程能夠即時(shí)發(fā)現(xiàn)
private volatile boolean stop = false;
//暫停標(biāo)識(shí)
private volatile boolean suspend = false;
//暫停鎖
private Object suspendLock=new Object();
public RandomFetch(List<T> source,int sample,SampleShow<T> sampleShow) {
this.source=source;//源數(shù)據(jù)池
this.smaple=sample;//樣本數(shù)
this.sampleShow=sampleShow;//結(jié)果回調(diào)接口
}
//開始抽獎(jiǎng)
public void start() {
for (int i = 0; i < smaple; i++) {
Picker runnable = new Picker();
threadList.add(runnable);
Thread thread = new Thread(runnable, i + "");
thread.start();
}
}
//停止抽獎(jiǎng)
public void stop() {
stop = true;
}
//恢復(fù)
public void resume() {
suspend = false;
//threadList.forEach(pick ->{synchronized(pick){pick.notify();}});
synchronized (suspendLock){
suspendLock.notifyAll();
}
}
//暫停
public void pause() {
suspend = true;
}
//抽獎(jiǎng)線程
private class Picker implements Runnable {
public T pickSeatNumber() {
//有待取的座位時(shí)才去取鸠天,否則跳過
int count = source.size();
if (count > 0) {
//隨機(jī)取一個(gè)序號(hào)[0,count)
int index = new Random().nextInt(count);
//得到座位號(hào)
T seatNumber = source.get(index);
//將座位從待取座位中移除
source.remove(index);
return seatNumber;
}
return null;
}
public T showSeatNumber(T seatNumber) throws RandomFetchException {
//看當(dāng)前線程更新的是黑板上哪個(gè)位置的座位號(hào)[0,sample)
int index = Integer.valueOf(Thread.currentThread().getName());
if(index >=0 && index < smaple) {
T oldSample=sampleShow.show(index,seatNumber);
//如果黑板對應(yīng)位置之前沒有座位號(hào)時(shí),則返回Null
if (oldSample == null || oldSample.equals("")) oldSample = null;
return oldSample;
}else{
throw new RandomFetchException("thread name is not right,it should be between 0 and "+smaple);
}
}
public void readyAgain(T oldSeatNumber) {
//添加到待取座位列表中
source.add(oldSeatNumber);
}
@Override
public void run() {
while (!stop) {
if (suspend) {
//object.wait,object.notify必須是鎖對象
//此處不用RandomFetch.this帐姻,原因是其它線程獲得了RandomFetch.this鎖時(shí)稠集,當(dāng)前
//線程仍可以暫停
synchronized (suspendLock) {
try {
suspendLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized (RandomFetch.this) {
T seatNumnber = pickSeatNumber();
T oldSeatNumber = null;
if (seatNumnber != null) {
try {
oldSeatNumber = showSeatNumber(seatNumnber);
}catch (RandomFetchException e){
e.printStackTrace();
}
}
if (oldSeatNumber != null) {
readyAgain(oldSeatNumber);
}
}
}
}
}
}
package cn.yanggy.demo03;
/**
* @author Cool-Coding 2017/12/1
* 異常類
*/
public class RandomFetchException extends Exception {
public RandomFetchException(){
}
public RandomFetchException(String message){
super(message);
}
}
package cn.yanggy.demo03;
/**
* @author Cool-Coding 2017/12/1
* 隨機(jī)抽獎(jiǎng)返回結(jié)果接口
* 此接口用于調(diào)用者與隨機(jī)抽獎(jiǎng)線程之間進(jìn)行數(shù)據(jù)交換
*/
public interface SampleShow<T> {
public T show(int index, T data);
}
屏幕部分:
package cn.yanggy.demo03;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
/**
* @author Cool-Coding 2017/12/1
*/
public class BlackBoard extends JFrame implements ActionListener {
private final int LUCKYCOUNT =10;
private final String START ="開始";
private final String STOP ="停止";
private final String PAUSE ="暫停";
private final String RESUME ="恢復(fù)";
private final JPanel panel=new JPanel(new GridLayout(5, 2));
private JButton jButton;
private JButton jButton2;
private final RandomFetch randomFetch;
public BlackBoard(){
// 窗口屬性的設(shè)置
setTitle("抽獎(jiǎng)");// 窗口標(biāo)題
setSize(300, 200);// 窗口大小
setLocationRelativeTo(null);// 窗口居中
setDefaultCloseOperation(EXIT_ON_CLOSE);// 關(guān)閉窗口則退出虛擬機(jī)
setLayout(new GridLayout(2, 1));// 設(shè)置布局流式布局
for(int i = 0; i< LUCKYCOUNT; i++){
final JLabel label=new JLabel();
label.setName(i+"");
label.setFont(new Font("宋休",Font.PLAIN,20));
panel.add(label);
}
JPanel panel2=new JPanel();
jButton=new JButton(START);
jButton.setSize(100,100);
jButton.addActionListener(this);
panel2.add(jButton);
jButton2=new JButton(PAUSE);
jButton2.setEnabled(false);
jButton2.setSize(100,100);
jButton2.addActionListener(this);
panel2.add(jButton2);
add(panel);
add(panel2);
//抽取樣本
ArrayList<String> source=new ArrayList<>();
for(int i=0;i<100;i++){
source.add("seat"+i);
}
randomFetch = new RandomFetch<String>(source, LUCKYCOUNT, new SampleShow<String>() {
@Override
public String show(int index, String data) {
JLabel label=BlackBoard.this.getLabel(index);
String oldSeatNumber = label.getText();
label.setText(data);
return oldSeatNumber;
}
});
}
//獲取Lable
public JLabel getLabel(int index){
return (JLabel)panel.getComponent(index);
}
@Override
public void actionPerformed(ActionEvent e) {
String action=e.getActionCommand();
if(action.equals(START)) {
randomFetch.start();
jButton.setText(STOP);
//可以暫停了
jButton2.setText(PAUSE);
jButton2.setEnabled(true);
}else if(action.equals(STOP)){
randomFetch.stop();
//jButton.setText(START);
jButton.setEnabled(false);
//將按鈕變成灰色
//jButton2.setText(PAUSE);
jButton2.setEnabled(false);
//檢查是否有兩個(gè)Lable 內(nèi)容相同
ArrayList<String> list=new ArrayList<>();
for(Component comp:panel.getComponents()){
JLabel label=(JLabel)comp;
String text=label.getText();
boolean yesorno=list.contains(text);
if(!yesorno){
list.add(text);
}else{
System.out.println("wrong:"+text);
}
}
if (list.size()==LUCKYCOUNT) System.out.println("success");
}else if(action.equals(PAUSE)){
randomFetch.pause();
jButton2.setText(RESUME);
}else if(action.equals(RESUME)){
randomFetch.resume();
jButton2.setText(PAUSE);
}
}
public static void main(String args[]){
BlackBoard blackBoard=new BlackBoard();
blackBoard.setVisible(true);
//showComponents(blackBoard.getPanel());
}
}
本文的目的是線程的運(yùn)用,所以屏幕部分寫的比較簡陋饥瓷。其中要注意的是線程的暫停和恢復(fù)剥纷,需要同步的對象是線程本身,即synchronized (this){wait();}呢铆,否則會(huì)報(bào)異常:java.lang.IllegalMonitorStateException