• 线程同步(三)


    介绍完如何创建进程以及线程了,那么我们接着来看一个实例:

      利用多线程模拟 3 个窗口卖票

     

    第一种方法:继承 Thread 类

     创建窗口类 TicketSell 

    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.ys.thread;
     
    public class TicketSell extends Thread{
        //定义一共有 50 张票,注意声明为 static,表示几个窗口共享
        private static int num = 50;
         
        //调用父类构造方法,给线程命名
        public TicketSell(String string) {
            super(string);
        }
        @Override
        public void run() {
            //票分 50 次卖完
            for(int i = 0 ; i < 50 ;i ++){
                if(num > 0){
                    try {
                        sleep(10);//模拟卖票需要一定的时间
                    } 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
    package com.ys.thread;
     
    public class TestTicket {
     
        public static void main(String[] args) {
            //创建 3 个窗口
            TicketSell t1 = new TicketSell("A窗口");
            TicketSell t2 = new TicketSell("B窗口");
            TicketSell t3 = new TicketSell("C窗口");
             
            //启动 3 个窗口进行买票
            t1.start();
            t2.start();
            t3.start();
        }
    }

      结果:这里我们省略了一些,根据电脑配置,结果会随机出现不同

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    B窗口卖出一张票,剩余48
    A窗口卖出一张票,剩余47
    C窗口卖出一张票,剩余49
    C窗口卖出一张票,剩余46
    B窗口卖出一张票,剩余44
    A窗口卖出一张票,剩余45
    A窗口卖出一张票,剩余43
    ...
    C窗口卖出一张票,剩余5
    A窗口卖出一张票,剩余4
    B窗口卖出一张票,剩余3
    A窗口卖出一张票,剩余2
    C窗口卖出一张票,剩余3
    B窗口卖出一张票,剩余1
    C窗口卖出一张票,剩余0
    A窗口卖出一张票,剩余-1

      

     

     

     

    第二种方法:实现 Runnable 接口

      创建窗口类 TicketSellRunnable

    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 TicketSellRunnable implements Runnable{
     
        //定义一共有 50 张票,继承机制开启线程,资源是共享的,所以不用加 static
        private int num = 50;
         
        @Override
        public void run() {
            //票分 50 次卖完
            for(int i = 0 ; i < 50 ;i ++){
                if(num > 0){
                    try {
                        //模拟卖一次票所需时间
                        Thread.sleep(10);
                    } 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
    package com.ys.thread;
     
    public class TicketSellRunnableTest {
        public static void main(String[] args) {
            TicketSellRunnable t = new TicketSellRunnable();
             
            Thread t1 = new Thread(t,"A窗口");
            Thread t2 = new Thread(t,"B窗口");
            Thread t3 = new Thread(t,"C窗口");
             
            t1.start();
            t2.start();
            t3.start();
        }
     
    }

      结果:同理为了篇幅我们也省略了中间的一些结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    B窗口卖出一张票,剩余49
    C窗口卖出一张票,剩余48
    A窗口卖出一张票,剩余49
    B窗口卖出一张票,剩余47
    A窗口卖出一张票,剩余45
    ......
    A窗口卖出一张票,剩余4
    C窗口卖出一张票,剩余5
    A窗口卖出一张票,剩余3
    B窗口卖出一张票,剩余2
    C窗口卖出一张票,剩余1
    B窗口卖出一张票,剩余0
    A窗口卖出一张票,剩余-2
    C窗口卖出一张票,剩余-1

      

     

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

      

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

    1、使用 同步代码块

    2、使用 同步方法

    3、使用 锁机制

     

    ①、使用同步代码块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    语法:
    synchronized (同步锁) {
        //需要同步操作的代码         
    }
     
    同步锁:为了保证每个线程都能正常的执行原子操作,Java 线程引进了同步机制;同步锁也叫同步监听对象、同步监听器、互斥锁;
    Java程序运行使用的任何对象都可以作为同步监听对象,但是一般我们把当前并发访问的共同资源作为同步监听对象
     
    注意:同步锁一定要保证是确定的,不能相对于线程是变化的对象;任何时候,最多允许一个线程拿到同步锁,谁拿到锁谁进入代码块,而其他的线程只能在外面等着

      实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public void run() {
            //票分 50 次卖完
            for(int i = 0 ; i < 50 ;i ++){
                //这里我们使用当前对象的字节码对象作为同步锁
                synchronized (this.getClass()) {
                    if(num > 0){
                        try {
                            //模拟卖一次票所需时间
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
                    }
                }
                 
            }
        }

      

     

    2、使用 同步方法

    语法:即用  synchronized  关键字修饰方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Override
        public void run() {
            //票分 50 次卖完
            for(int i = 0 ; i < 50 ;i ++){
                sell();
                 
            }
        }
        private synchronized void sell(){
            if(num > 0){
                try {
                    //模拟卖一次票所需时间
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
            }
        }

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

     

     

    3、使用 锁机制

      

    1
    public interface Lock

      主要方法:

      常用实现类:

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

      例子:

    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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    package com.ys.thread;
     
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
     
    public class TicketSellRunnable implements Runnable{
     
        //定义一共有 50 张票,继承机制开启线程,资源是共享的,所以不用加 static
        private int num = 50;
        //创建一个锁对象
        Lock l = new ReentrantLock();
         
        @Override
        public void run() {
            //票分 50 次卖完
            for(int i = 0 ; i < 50 ;i ++){
                //获取锁
                l.lock();
                try {
                    if(num > 0){
                    //模拟卖一次票所需时间
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }finally{
                    //释放锁
                    l.unlock();
                }
                 
                 
            }
        }
        private void sell(){
             
        }
     
    }

    作者:KeerDi —— 北方的后生

    出处:http://www.cnblogs.com/keerdi/

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    作品-网站-[原生开发]云鸽信息网
    ASYNC_NETWORK_IO和PREEMPTIVE_OS_WAITFORSINGLEOBJECT等待事件
    还原一直卡在ASYNC_IO_COMPLETION浅析
    B树之C语言实现(包含查找、删除、插入)
    Asp.Net Core 禁用预编译
    ASP.NET Core Razor 视图组件
    WITH RECOMPILE 和 OPTION(RECOMPILE) 使用上的区别
    如何减少SQL Server中的PREEMPTIVE_OS_WRITEFILEGATHER等待类型
    WITH RECOMPILE和OPTION(RECOMPILE)区别
    SQL Server PageIOLatch和PageLatch
  • 原文地址:https://www.cnblogs.com/123hll/p/6902467.html
Copyright © 2020-2023  润新知