Java多線程應(yīng)用示例-抽獎(jiǎng)

這里用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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晦鞋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子棺克,更是在濱河造成了極大的恐慌悠垛,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娜谊,死亡現(xiàn)場離奇詭異鼎文,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)因俐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門拇惋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抹剩,你說我怎么就攤上這事撑帖。” “怎么了澳眷?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵胡嘿,是天一觀的道長。 經(jīng)常有香客問我钳踊,道長衷敌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任拓瞪,我火速辦了婚禮缴罗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘祭埂。我一直安慰自己面氓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著舌界,像睡著了一般掘譬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上呻拌,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天葱轩,我揣著相機(jī)與錄音,去河邊找鬼藐握。 笑死酿箭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的趾娃。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼缔御,長吁一口氣:“原來是場噩夢啊……” “哼抬闷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起耕突,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤笤成,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后眷茁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炕泳,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年上祈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了培遵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡登刺,死狀恐怖籽腕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情纸俭,我是刑警寧澤皇耗,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站揍很,受9級(jí)特大地震影響郎楼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窒悔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一呜袁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧简珠,春花似錦傅寡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芜抒。三九已至,卻和暖如春托启,著一層夾襖步出監(jiān)牢的瞬間宅倒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工屯耸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拐迁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓疗绣,卻偏偏與公主長得像线召,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子多矮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350