多线程回顾-02

上一节我们说到了线程的基本概念和java中构造线程对象的两种方式。
今天我们来介绍以下线程之间的关系。这里说的线程是指交互线程(若并发执行的多个线程之间需要共享资源或交换数据,则称这组线程为交互线)。并发执行的交互线程之间存在与时间有关的错误,这是因为线程的执行是根据操作系统自动调度的,如果一个线程修改了共享资源,那么另一个线程再使用这个共享资源的时候,并不知道它被另外的线程使用过,会导致bug。所以有必要研究交互线程之间存在的问题,并针对性解决。
交互线程之间存在两种关系:竞争和协同
线程之间的竞争是指两个线程要访问同一资源,一个线程通过操作系统分配得到该资源,那么另一个线程将不得不等待,这时一个线程的执行就可能影响到同其竞争资源的其他线程。
补充知识:资源竞争可能会出现的两个问题:死锁和饥饿。死锁是说一组线程如果都获得了部分资源,还想得到其他线程所占用的资源,最终所有线程都将陷入死锁。饥饿是当一个线程由于其他线程总是优先于它而被无限期延后。
为了协调好线程对资源的竞争,保证线程能互斥的访问临界资源尤为重要。

线程互斥和临界区管理
线程互斥是指若干个线程要使用同一共享资源时,任何时刻最多允许一个线程使用,其他线程必须等待,直到占有资源的线程释放该资源。
我们把共享变量代表的资源称为临界资源,并发线程中与共享变量有关的程序段称为临界区。
由于线程时并发执行,启动后的先后执行顺序不可预知,所以,操作系统规定了对于交互线程各自进入临界区的3个调度原则:
1.一次至多一个线程能在他的临界区。
2.不能让一个线程无限期地停留在它的临界区内。
3.不能强迫一个线程无限地等待进入它的临界区。特别地,进入临界区的任意线程不能妨碍正在等待进入的其他线程的进展。
总结该调度原则就是:无空等待、有空让进、择一而入、算法可行。算法可行时说所选策略不能造成线程饥饿甚至死锁。
java的线程互斥实现
java中采用关键字synchronized用于声明程序段为临界区,使线程对临界资源采用互斥原则。synchronized有两种用法:声明一条语句或声明一个方法。
1.同步语句

    synchronized(对象)
            语句(可为复合语句(大括号包括的一串语句))

2.同步方法

    synchronized   方法声明
该方法与以下方法效果相同:

    方法声明
    synchronized(this)
    {
       方法体
    }

同步语句的执行过程如下:

在这里插入图片描述
说完线程之间的竞争与互斥,接下来我们说一下线程之间的协作关系及相应处理。
所谓线程协作是说多个线程为完成同一任务而分工协作时,它们彼此之间有联系,知道其他线程的存在而且受到其他线程的影响。(PS:这点是和线程互斥不同的地方,线程互斥是并发执行的线程不知道其他线程的存在,但是共用了同一资源,使用互斥避免干扰,而线程协调知道其他线程的存在,并且要利用这种信号协调多个线程共同完成一个操作。)但是由于线程启动后都是以不可知的速度运行,所以需要相互协作的线程在某些协调点上协调各自的工作。当合作线程中的一个到达协调点,在尚未得到其他伙伴线程发来的信号之前应阻塞自己,直到收到其他伙伴线程发来的协调信号后方能被唤醒并继续执行。这种方式称为线程同步。
由于线程同步是基于某个条件来协调多个线程之间的活动,线程互斥是根据互斥区域来协调多个线程,所以将线程互斥和线程同步统称为线程同步。
下面说一下线程同步机制:
操作系统实现线程同步有一种工具称为信号量和PV操作,描述如下:
1.背景
多个线程需要同一个共享变量进行操作,所以多个线程间必须互斥执行。
2.设置信号值
为共享变量设置一个信号量,信号量有多种状态,就像交通信号灯有多种颜色。设置信号量状态可以有多种状态。测试信号量称为P操作,改变信号量称为V操作,这两种操作互斥的执行,并且执行时不能被打断。
3.线程根据信号量状态执行
当一个线程执行时,它先测试信号量的状态,若状态合适,则执行,并且改变信号量的状态;否则等待,阻塞自己,直至被唤醒。
上面提到线程阻塞后需要被唤醒,这种操作需要线程间的通信,java中线程通信的方法有:java.lang.Object类提供了以下方法:

    public final void wait()throws InterruptedException     //等待,最终方法
    public final void wait(long timeout)throws InterruptedException     //等待指定时间
    public final void notify()                              //唤醒一个等待线程
    public final void notifyAll()                           //唤醒所以等待线程

实战演练:
斗地主的游戏相信大家都玩过,共有54张牌,一位地主20张牌,2位农民各17张牌,然后按照出牌规则进行游戏。我们选择斗地主的发牌过程,基于多线程的相关知识,模仿以下斗地主的发牌过程。
主要分为两部分发牌线程和收牌线程。一个发牌线程和3个收牌线程需要进行同步机制处理。说明如下:
1.约定一个缓冲区存放一张牌,为缓冲区是否为空设置一个信号量(两种状态)
2.有一个发牌线程,随机产生1-54中的一个数,每次发一张牌。
3.对于发出的每一张牌,同时有3个收牌线程在争抢。所以需要为每个收牌线程约定一个收牌次序,就是再设置一个信号量,信号量有三种状态,约定为0,1,2,每个值指定一个线程可执行。
4.农民线程在收到17张牌后应该终止,剩余的收发牌过程应该由发牌线程和地主收牌线程实现,这时候就不再需要为区别取牌次序而设置的信号量。
5.发牌线程的优先级应该高于收牌线程。因为每次取走一张牌,接下来应该执行发牌线程。
程序如下:

  1package ThreadTest;
  2
  3import java.awt.Font;
  4import java.util.ArrayList;
  5import java.util.Collections;
  6
  7import javax.swing.JFrame;
  8import javax.swing.JTextArea;
  9
 10public class DealCard {
 11
 12    public DealCard(int cardMax, int number) {
 13        CardBuffer buffer = new CardBuffer(number);//创建缓冲区
 14        new SendCardThread(buffer, cardMax, number).start();//启动发牌线程
 15        String[] titles = {"地主","农民1","农民2"};
 16        int[] xs = {300,550,300},ys = {200,320,440};
 17        for(int i=0;i<number;i++)
 18            new ReceiveCardJframe(buffer, i, titles[i], xs[i], ys[i]);//启动收牌线程
 19
 20    }
 21
 22    public static void main(String[] args) {
 23        new DealCard(54, 3);
 24    }
 25}
 26
 27class CardBuffer{
 28    private int value;
 29    private boolean isEmpty = true;
 30    private int order = 0;
 31    private int number;
 32    public CardBuffer(int number) {
 33        super();
 34        this.number = number;
 35    }
 36
 37    synchronized void put(int i) {
 38        while(!isEmpty) {
 39            try {
 40                this.wait();//阻塞当前线程
 41            } catch (InterruptedException e) {
 42            }
 43        }
 44
 45        this.value = i;
 46        isEmpty = false;
 47        this.notifyAll();//唤醒所有等待线程
 48    }
 49
 50    synchronized int get(int order) {
 51        while(isEmpty || this.order != order) {
 52            try {
 53                this.wait();
 54            } catch (InterruptedException e) {
 55            }
 56        }
 57
 58        isEmpty = true;
 59        this.notifyAll();
 60        this.order = (this.order + 1) % this.number;
 61        return this.value;
 62    }
 63
 64    synchronized int get() {
 65        while(isEmpty) {
 66            try {
 67                this.wait();
 68            } catch (InterruptedException e) {
 69            }
 70        }
 71
 72        isEmpty = true;
 73        this.notify();
 74        return this.value;
 75    }
 76}
 77
 78class SendCardThread extends Thread{
 79
 80    private CardBuffer buffer;
 81    private ArrayList<Integer> cardlist;
 82
 83    public SendCardThread(CardBuffer buffer, int cardMax, int number) {
 84        this.buffer = buffer;
 85        cardlist = new ArrayList<>();
 86        for(int i=1;i<=cardMax;i++) {
 87            cardlist.add(i);
 88        }
 89        Collections.shuffle(cardlist);//打乱卡牌
 90
 91        this.setPriority(MAX_PRIORITY);
 92    }
 93
 94    @Override
 95    public void run() {
 96        for(int i : cardlist) {
 97            this.buffer.put(i);
 98        }
 99    }   
100}
101
102class ReceiveCardJframe extends JFrame implements Runnable{
103
104    /**
105     * 
106     */
107    private static final long serialVersionUID = 1L;
108    private CardBuffer buffer;
109    private JTextArea text;
110    private int order;
111    private Thread thread;
112    private ArrayList<Integer> cardlist;
113
114
115    public ReceiveCardJframe(CardBuffer buffer, int order, String title, int x, int y){
116        super(title);
117        this.setBounds(x, y, 270, 250);
118        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
119        this.buffer = buffer;
120        this.order = order;
121        this.cardlist = new ArrayList<>();
122        this.text = new JTextArea();
123        this.getContentPane().add(text);
124        this.text.setLineWrap(true);
125        this.text.setEditable(false);
126        this.text.setFont(new Font("Dialog",Font.PLAIN,20));
127        this.setVisible(true);
128        thread = new Thread(this);
129        thread.start();
130    }
131
132
133    @Override
134    public void run() {
135        String[] numbers = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
136        String[] colors = {"方块","梅花","红桃","黑桃"};
137        String[] wangs = {"小王","大王"};
138        String card = "";
139        String title = this.getTitle();
140
141        while(true) {
142            if((title.startsWith("农民") && cardlist.size() < 17) 
143                    ||(title.equals("地主") && cardlist.size() < 20)) {
144                int value;
145                if(cardlist.size() > 17)
146                    value = this.buffer.get();
147                else
148                    value = this.buffer.get(order);
149
150                if(value <= 52)
151                    card = colors[(value-1) % 4] + numbers[(value-1) / 4];
152                else
153                    card = wangs[value - 53];
154                cardlist.add(value);
155                this.text.append(String.format("%-8s", card));
156
157                try {
158                    Thread.sleep(100);
159                } catch (InterruptedException e) {
160                }
161                continue;
162            }
163            cardlist.sort((O1,O2)->O2-O1);
164            this.text.setText("");
165            for(int i: cardlist) {
166                if(i <= 52)
167                    card = colors[(i-1) % 4] + numbers[(i-1) / 4];
168                else
169                    card = wangs[i - 53];
170                this.text.append(String.format("%-8s", card));
171            }
172            break;
173        }
174    }
175}

到这里呢,我们的多线程回顾部分就结束了,多线程的深入知识有很多,有兴趣的同学可以看看《JAVA并发编程实践》这本书。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值