黑马程序员技术交流社区

标题: 【上海校区】Java 多线程详解-----线程的同步 [打印本页]

作者: 不二晨    时间: 2019-3-22 09:12
标题: 【上海校区】Java 多线程详解-----线程的同步
利用多线程模拟 5 个窗口卖票实例

创建窗口类 TicketSell

package com.cn.test;

public class SellWindow extends Thread{
    //定义一共有 500张票,注意声明为 static,表示几个窗口共享
    private static int num = 500;

    //调用父类构造方法,给线程命名
    public SellWindow(String string) {
        super(string);
    }
    @Override
    public void run() {
        //票分 500 次卖完
        for(int i = 0 ; i < 500 ;i ++){
            if(num > 0){
                try {
                    sleep(20);//模拟卖票需要一定的时间20秒
                } catch (InterruptedException e) {
                    // 由于父类的 run()方法没有抛出任何异常,根据继承的原则,子类抛出的异常不能大于父类, 故我们这里也不能抛出异常
                    e.printStackTrace();
                }
                System.out.println(this.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
            }
        }
    }


}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
创建主线程测试:

package com.cn.test;

public class TestSell{

    public static void main(String[] args) {
        //创建 5 个窗口
        SellWindow t1 = new SellWindow("A窗口");
        SellWindow t2 = new SellWindow("B窗口");
        SellWindow t3 = new SellWindow("C窗口");
        SellWindow t4 = new SellWindow("D窗口");
        SellWindow t5 = new SellWindow("E窗口");

        //启动 5 个窗口进行买票
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
结果:这里我们省略了一些,根据电脑配置,结果会随机出现不同

C窗口卖出一张票,剩余498张
D窗口卖出一张票,剩余496张
B窗口卖出一张票,剩余499张
A窗口卖出一张票,剩余497张
E窗口卖出一张票,剩余495张
B窗口卖出一张票,剩余493张
D窗口卖出一张票,剩余494张
A窗口卖出一张票,剩余492张
C窗口卖出一张票,剩余492张
E窗口卖出一张票,剩余491张
...
A窗口卖出一张票,剩余5张
E窗口卖出一张票,剩余4张
D窗口卖出一张票,剩余3张
C窗口卖出一张票,剩余2张
B窗口卖出一张票,剩余1张
A窗口卖出一张票,剩余0张
C窗口卖出一张票,剩余-1张
D窗口卖出一张票,剩余-2张
E窗口卖出一张票,剩余-3张
B窗口卖出一张票,剩余-4张
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
第二种方法:实现 Runnable 接口
创建窗口类 SellWindowRunnable

package com.ys.thread;

public class SellWindowRunnable implements Runnable{

    //定义一共有 500 张票,继承机制开启线程,资源是共享的,所以不用加 static
    private int num = 500;

    @Override
    public void run() {
        //票分 500 次卖完
        for(int i = 0 ; i < 500;i ++){
            if(num > 0){
                try {
                    //模拟卖一次票所需时间
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
            }
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
创建主线程测试:

package com.ys.thread;

public class SellWindowRunnableTest {
    public static void main(String[] args) {
        SellWindowRunnable t = new SellWindowRunnable();

        Thread t1 = new Thread(t,"A窗口");
        Thread t2 = new Thread(t,"B窗口");
        Thread t3 = new Thread(t,"C窗口");
        Thread t4 = new Thread(t,"D窗口");
        Thread t5 = new Thread(t,"E窗口");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
结果:同理为了篇幅我们也省略了中间的一些结果

C窗口卖出一张票,剩余499张
A窗口卖出一张票,剩余498张
E窗口卖出一张票,剩余497张
B窗口卖出一张票,剩余496张
D窗口卖出一张票,剩余495张
C窗口卖出一张票,剩余494张
A窗口卖出一张票,剩余493张
B窗口卖出一张票,剩余492张
E窗口卖出一张票,剩余491张
D窗口卖出一张票,剩余490张
C窗口卖出一张票,剩余489张
......
D窗口卖出一张票,剩余5张
C窗口卖出一张票,剩余4张
E窗口卖出一张票,剩余3张
A窗口卖出一张票,剩余3张
D窗口卖出一张票,剩余2张
C窗口卖出一张票,剩余1张
B窗口卖出一张票,剩余0张
E窗口卖出一张票,剩余-1张
A窗口卖出一张票,剩余-2张
C窗口卖出一张票,剩余-3张
D窗口卖出一张票,剩余-4张

结果分析:这里出现了票数为 负数的情况,这在现实生活中肯定是不存在的,那么为什么会出现这样的情况呢?

解决办法分析:即我们不能同时让超过两个以上的线程进入到 if(num>0)的代码块中,不然就会出现上述的错误。我们可以通过以下三个办法来解决:

1、使用 同步代码块

2、使用 同步方法

3、使用 锁机制

①、使用同步代码块

语法:
synchronized (同步锁) {
    //需要同步操作的代码         
}

同步锁:为了保证每个线程都能正常的执行原子操作,Java 线程引进了同步机制;同步锁也叫同步监听对象、同步监听器、互斥锁;
Java程序运行使用的任何对象都可以作为同步监听对象,但是一般我们把当前并发访问的共同资源作为同步监听对象

注意:同步锁一定要保证是确定的,不能相对于线程是变化的对象;任何时候,最多允许一个线程拿到同步锁,谁拿到锁谁进入代码块,而其他的线程只能在外面等着

实例

public void run() {
        //票分 500 次卖完
        for(int i = 0 ; i < 500 ;i ++){
            //这里我们使用当前对象的字节码对象作为同步锁
            synchronized (this.getClass()) {
                if(num > 0){
                    try {
                        //模拟卖一次票所需时间
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
                }
            }

        }
    }

②、使用 同步方法
语法:即用 synchronized 关键字修饰方法

@Override
    public void run() {
        //票分 500 次卖完
        for(int i = 0 ; i < 500 ;i ++){
            sell();

        }
    }
    private synchronized void sell(){
        if(num > 0){
            try {
                //模拟卖一次票所需时间
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
        }
    }

注意:不能直接用 synchronized 来修饰 run() 方法,因为如果这样做,那么就会总是第一个线程进入其中,而这个线程执行完所有操作,即卖完所有票了才会出来。

 ③、使用 锁机制

public interface Lock
1

  常用实现类:

public class ReentrantLock
extends Object
implements Lock, Serializable<br>//一个可重入互斥Lock具有与使用synchronized方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。
1
2
3
例子:

package com.cn.test;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellWindowRunnable implements Runnable{

    //定义一共有 500 张票,继承机制开启线程,资源是共享的,所以不用加 static
    private int num = 500;
    //创建一个锁对象
    Lock l = new ReentrantLock();

    @Override
    public void run() {
        //票分 500 次卖完
        for(int i = 0 ; i < 500 ;i ++){
            //获取锁
            l.lock();
            try {
                if(num > 0){
                //模拟卖一次票所需时间
                Thread.sleep(0);
                System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally{
                //释放锁
                l.unlock();
            }


        }
    }
    private void sell(){

    }

}

参考http://www.cnblogs.com/ysocean/p/6883729.html
---------------------
【转载,仅作分享,侵删】
作者:小志的博客
原文:https://blog.csdn.net/li1325169021/article/details/86692419
版权声明:本文为博主原创文章,转载请附上博文链接!


作者: 不二晨    时间: 2019-3-25 17:13
奈斯,感谢分享




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2