zoukankan      html  css  js  c++  java
  • Java多线程同步锁的理解

    java主要通过synchronized的关键字来实现的。让我们从一个买票程序说起吧。

    package com.day04;
    
    /**
     * 
     * @author Administrator 问题描述:使用多线程的方式来模拟多个窗口买票
     *
     */
    public class SaleWindow implements Runnable {
    
        // 初始化票数10
        private int ticket = 10;
    
        @Override
        public void run() {
            // 获取线程的名称,比如Thread-0,并将它截掉Thread-取0这个数字标识,为了构造下面卖票窗口名称
            int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));
            String saleWindowName = "销售窗口" + threadNum;
            // 开始买票
            while (true) {
                if (ticket > 0) {
                    // 这里为了演示出线程不同步的问题,让线程睡眠一段时间,延时)
                    try {
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(saleWindowName + " 卖 出 了 " + ticket-- + " 号 票 !");
                } else {
                    break;
                }
            }
        }
    
        public static void main(String[] args) {
            // 创建了销售窗口对象
            SaleWindow sw = new SaleWindow();
            // 启动线程,让第一个窗口开始买票
            new Thread(sw).start();
            // 启动线程,让第二个窗口开始买票
            new Thread(sw).start();
            // 启动线程,让第三个窗口开始买票
            new Thread(sw).start();
    
        }
    
    }

    运行结果如下所示:

    销售窗口2 卖 出 了 10 号 票 !
    销售窗口1 卖 出 了 8 号 票 !
    销售窗口0 卖 出 了 9 号 票 !
    销售窗口2 卖 出 了 7 号 票 !
    销售窗口1 卖 出 了 6 号 票 !
    销售窗口0 卖 出 了 5 号 票 !
    销售窗口2 卖 出 了 4 号 票 !
    销售窗口1 卖 出 了 3 号 票 !
    销售窗口0 卖 出 了 2 号 票 !
    销售窗口2 卖 出 了 1 号 票 !
    销售窗口1 卖 出 了 0 号 票 !《-----
    销售窗口0 卖 出 了 -1 号 票 !《------
    可以看到我们的程序出来了问题,上面打红色箭头所示,竟然卖出了0号票和-1号票了。

    让我们画个图来分析一下如下所示:

     通过以上分析,不难得出,造成问题原因,是因为同步操作问题。

     那我们如何确定哪些是同步操作(或者有同步问题)?

    1.明确哪些代码是多线程成运行的代码(run方法中的代码)

    2.明确那些是共享数据(ticket票数)

    3.明确多线程运行代码中那些语句是操作共享数据(System.out.println(saleWindowName + " 卖 出 了 " + ticket-- + " 号 票 !");)

    接下来我们就可以通过Java给我们提供的synchroized关键字使用同步锁来解决以上的问题

    package com.day04;
    
    /**
     * @author Administrator 问题描述:使用多线程的方式来模拟多个窗口买票
     */
    public class SaleWindow implements Runnable {
    
        // 初始化票数10
        private int ticket = 10;
        //线程的锁
        private Object lock;
    
        @Override
        public void run() {
            // 获取线程的名称,比如Thread-0,并将它截掉Thread-取0这个数字标识,为了构造下面卖票窗口名称
            int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));
            String saleWindowName = "销售窗口" + threadNum;
            // 开始买票
            while (true) {
                //加上synchronized,并加入对象锁,new一个任意对象即可,我们这里使用Object来解决同步问题,注意这里必须是公用同一个锁lock
                synchronized (lock) {
    
                    if (ticket > 0) {
                        // 这里为了演示出线程不同步的问题,让线程睡眠一段时间,延时)
                        try {
                            Thread.sleep(1000L);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(saleWindowName + " 卖 出 了 " + ticket-- + " 号 票 !");
                    } else {
                        break;
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            // 创建了销售窗口对象
            SaleWindow sw = new SaleWindow();
            // 启动线程,让第一个窗口开始买票
            new Thread(sw).start();
            // 启动线程,让第二个窗口开始买票
            new Thread(sw).start();
            // 启动线程,让第三个窗口开始买票
            new Thread(sw).start();
    
        }
    
    }

    运行结果如下所示:

    销售窗口1 卖 出 了 10 号 票 !

    销售窗口2 卖 出 了 9 号 票 !

    销售窗口0 卖 出 了 8 号 票 !

    销售窗口0 卖 出 了 7 号 票 !

    销售窗口2 卖 出 了 6 号 票 !

    销售窗口1 卖 出 了 5 号 票 !

    销售窗口2 卖 出 了 4 号 票 !

    销售窗口0 卖 出 了 3 号 票 !

    销售窗口0 卖 出 了 2 号 票 !

    销售窗口2 卖 出 了 1 号 票 !

    这样就有效的解决了同步的问题。

    同样我们也可将上面的操作共享数据的同步操作抽取出来,单独封装成一个同步方法,只需要在方法上面的返回值前面加上synchronized关键字即可,这样可以更方面理解和阅读,优化后代码如下。

    package com.day04;
    
    /**
     * 
     * @author Administrator 问题描述:使用多线程的方式来模拟多个窗口买票
     *
     */
    public class SaleWindow implements Runnable {
    
        // 初始化票数10
        private int ticket = 10;
    
        @Override
        public void run() {
    
            // 开始买票
            while (true) {
                // 当没有票了结束
                if (!saleSuccess()) {
                    break;
                }
            }
        }
    
        public synchronized boolean saleSuccess() {
            // 获取线程的名称,比如Thread-0,并将它截掉Thread-取0这个数字标识,为了构造下面卖票窗口名称
            int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));
            String saleWindowName = "销售窗口" + threadNum;
            if (ticket > 0) {
                // 这里为了演示出线程不同步的问题,让线程睡眠一段时间,延时)
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(saleWindowName + " 卖 出 了 " + ticket-- + " 号 票 !");
                return true;
            } else {
                return false;
            }
        }
    
        public static void main(String[] args) {
            // 创建了销售窗口对象
            SaleWindow sw = new SaleWindow();
            // 启动线程,让第一个窗口开始买票
            new Thread(sw).start();
            // 启动线程,让第二个窗口开始买票
            new Thread(sw).start();
            // 启动线程,让第三个窗口开始买票
            new Thread(sw).start();
    
        }
    
    }

    销售窗口1 卖 出 了 10 号 票 !销售窗口2 卖 出 了 9 号 票 !销售窗口0 卖 出 了 8 号 票 !销售窗口0 卖 出 了 7 号 票 !销售窗口2 卖 出 了 6 号 票 !销售窗口1 卖 出 了 5 号 票 !销售窗口2 卖 出 了 4 号 票 !销售窗口0 卖 出 了 3 号 票 !销售窗口0 卖 出 了 2 号 票 !销售窗口2 卖 出 了 1 号 票 !

    现在又有一个问题出现了,public synchronized boolean saleSuccess()该同步函数用的是哪一个锁?

    我们猜想可能用的是this这个对象锁,如果我们让线程一个执行带有sychronized的同步方法,一个执行带有this对象的sychronized同步代码块的方法,如果能够得到正确的结果,不出现同步问题,即论证正确,反之,如果还是出现同步问题即用的不是this这个对象锁。代码如下:

    package com.day04;
    
    /**
     * 
     * @author Administrator 问题描述:使用多线程的方式来模拟多个窗口买票
     *
     */
    public class SaleWindow implements Runnable {
    
        // 初始化票数10
        private int ticket = 10;
    
        @Override
        public void run() {
            // 获取当前线程的序号从0开始
            int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));
            // 偶数线程执行该方法
            if ((threadNum + 1) % 2 == 0) {
                while (true) {
                    synchronized (this) {
                        String saleWindowName = "奇数销售窗口" + threadNum;
                        if (ticket > 0) {
                            // 这里为了演示出线程不同步的问题,让线程睡眠一段时间,延时)
                            try {
                                Thread.sleep(1000L);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(saleWindowName + " 卖 出 了 " + ticket-- + " 号 票 !");
                        } else {
                            break;
                        }
                    }
                }
            } else {
                // 奇数线程执行该方法
                // 开始买票
                while (true) {
                    // 当没有票了结束
                    if (!saleSuccess()) {
                        break;
                    }
                }
            }
    
        }
    
        public synchronized boolean saleSuccess() {
            // 获取线程的名称,比如Thread-0,并将它截掉Thread-取0这个数字标识,为了构造下面卖票窗口名称
            int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));
            String saleWindowName = "偶数销售窗口" + threadNum;
            if (ticket > 0) {
                // 这里为了演示出线程不同步的问题,让线程睡眠一段时间,延时)
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(saleWindowName + " 卖 出 了 " + ticket-- + " 号 票 !");
                return true;
            } else {
                return false;
            }
        }
    
        public static void main(String[] args) {
            // 创建了销售窗口对象
            SaleWindow sw = new SaleWindow();
            // 启动线程,让第一个窗口开始买票
            new Thread(sw).start();
            // 启动线程,让第二个窗口开始买票
            new Thread(sw).start();
            // 启动线程,让第三个窗口开始买票
            new Thread(sw).start();
    
        }
    
    }

    运行结果如下:

    偶数销售窗口0 卖 出 了 10 号 票 !

    偶数销售窗口0 卖 出 了 9 号 票 !

    偶数销售窗口2 卖 出 了 8 号 票 !

    奇数销售窗口1 卖 出 了 7 号 票 !

    偶数销售窗口2 卖 出 了 6 号 票 !

    偶数销售窗口2 卖 出 了 5 号 票 !

    偶数销售窗口2 卖 出 了 4 号 票 !

    偶数销售窗口2 卖 出 了 3 号 票 !

    偶数销售窗口2 卖 出 了 2 号 票 !

    偶数销售窗口0 卖 出 了 1 号 票 !

    由上面的接口即可论证同步方法使用对的对象锁是this。

    同样的加入我们的将我们的共享数据ticket改成静态的,并将同步方法也改成静态,它用的是那个对象锁?

    我们猜想是本类的class对象这个锁即(SaleWindow.class)这个对象锁。同理如果我们让线程一个执行带有sychronized的静态同步方法,一个执行带有本类的class对象这个锁即(SaleWindow.class)的sychronized同步代码块的方法,如果能够得到正确的结果,不出现同步问题,即论证正确。反之,如果还是出现同步问题,即说明静态同步方法使用的锁不是本类的class对象这个锁即(SaleWindow.class)这个对象锁。代码如下:

    package com.day04;
    
    /**
     * 
     * @author Administrator 问题描述:使用多线程的方式来模拟多个窗口买票
     *
     */
    public class SaleWindow implements Runnable {
    
        // 初始化票数10
        private static int ticket = 10;
    
        @Override
        public void run() {
            // 获取当前线程的序号从0开始
            int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));
            // 偶数线程执行该方法
            if ((threadNum + 1) % 2 == 0) {
                while (true) {
                    synchronized (SaleWindow.class) {
                        String saleWindowName = "奇数销售窗口" + threadNum;
                        if (ticket > 0) {
                            // 这里为了演示出线程不同步的问题,让线程睡眠一段时间,延时)
                            try {
                                Thread.sleep(1000L);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(saleWindowName + " 卖 出 了 " + ticket-- + " 号 票 !");
                        } else {
                            break;
                        }
                    }
                }
            } else {
                // 奇数线程执行该方法
                // 开始买票
                while (true) {
                    // 当没有票了结束
                    if (!saleSuccess()) {
                        break;
                    }
                }
            }
    
        }
    
        public synchronized static boolean saleSuccess() {
            // 获取线程的名称,比如Thread-0,并将它截掉Thread-取0这个数字标识,为了构造下面卖票窗口名称
            int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));
            String saleWindowName = "偶数销售窗口" + threadNum;
            if (ticket > 0) {
                // 这里为了演示出线程不同步的问题,让线程睡眠一段时间,延时)
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(saleWindowName + " 卖 出 了 " + ticket-- + " 号 票 !");
                return true;
            } else {
                return false;
            }
        }
    
        public static void main(String[] args) {
            // 创建了销售窗口对象
            SaleWindow sw = new SaleWindow();
            // 启动线程,让第一个窗口开始买票
            new Thread(sw).start();
            // 启动线程,让第二个窗口开始买票
            new Thread(sw).start();
            // 启动线程,让第三个窗口开始买票
            new Thread(sw).start();
    
        }
    
    }

    运行结果如下:

    偶数销售窗口0 卖 出 了 10 号 票 !

    偶数销售窗口0 卖 出 了 9 号 票 !

    偶数销售窗口2 卖 出 了 8 号 票 !

    奇数销售窗口1 卖 出 了 7 号 票 !

    偶数销售窗口2 卖 出 了 6 号 票 !

    偶数销售窗口2 卖 出 了 5 号 票 !

    偶数销售窗口2 卖 出 了 4 号 票 !

    偶数销售窗口2 卖 出 了 3 号 票 !

    偶数销售窗口2 卖 出 了 2 号 票 !

    偶数销售窗口0 卖 出 了 1 号 票 !

    由上面的接口即可论证同步方法使用对的对象锁是本类的class对象这个锁即(SaleWindow.class)这个对象锁。

    public synchronized boolean saleSuccess()

  • 相关阅读:
    外观模式
    适配器模式
    桥接模式
    中文词频统计
    英文词频统计
    字符串练习
    Python基础
    熟悉常用的Linux操作
    作业
    递归下降分析法
  • 原文地址:https://www.cnblogs.com/nanyangke-cjz/p/7055759.html
Copyright © 2011-2022 走看看