zoukankan      html  css  js  c++  java
  • java多线程系列3-线程同步

    如果一个资源被多个线程同时访问,可能会遭到破坏,这篇文章介绍java线程同步来解决这类问题

    引入问题

    某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。

    方法一:继承Thread类

    public class SellTicket extends Thread {
     
        // 定义100张票
        // private int tickets = 100;
        // 为了让多个线程对象共享这100张票,我们其实应该用静态修饰
        private static int tickets = 100;
     
        @Override
        public void run() {
            // 定义100张票
            // 是为了模拟一直有票
            while (true) {
                if (tickets > 0) {
                    System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
                }
            }
        }
    }
    /*
     * 继承Thread类来实现。
     */
    public class SellTicketDemo {
        public static void main(String[] args) {
            // 创建三个线程对象
            SellTicket st1 = new SellTicket();
            SellTicket st2 = new SellTicket();
            SellTicket st3 = new SellTicket();
     
            // 给线程对象起名字
            st1.setName("窗口1");
            st2.setName("窗口2");
            st3.setName("窗口3");
     
            // 启动线程
            st1.start();
            st2.start();
            st3.start();
        }
    }

    方法二:实现Runnable接口

    public class SellTicket implements Runnable {
        // 定义100张票
        private int tickets = 100;
     
        @Override
        public void run() {
            while (true) {
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售第"
                            + (tickets--) + "张票");
                }
            }
        }
    }
    /*
     * 实现Runnable接口的方式实现
     */
    public class SellTicketDemo {
        public static void main(String[] args) {
            // 创建资源对象
            SellTicket st = new SellTicket();
     
            // 创建三个线程对象
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
     
            // 启动线程
            t1.start();
            t2.start();
            t3.start();
        }
    }

    电影院售票程序,从表面上看不出什么问题,在真实生活中,售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟

    改实现接口方式的卖票程序,每次卖票延迟100毫秒,代码如下:

    public class SellTicket implements Runnable {
        // 定义100张票
        private int tickets = 100;
        @Override
        public void run() {
            while (true) {
                // t1,t2,t3三个线程
                // 这一次的tickets = 1;
                if (tickets > 0) {
                    // 为了模拟更真实的场景,我们稍作休息
                    try {
                        Thread.sleep(100); //t1进来了并休息,t2进来了并休息,t3进来了并休息,
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
     
                    System.out.println(Thread.currentThread().getName() + "正在出售第"
                            + (tickets--) + "张票");
                    //窗口1正在出售第1张票,tickets=0
                    //窗口2正在出售第0张票,tickets=-1
                    //窗口3正在出售第-1张票,tickets=-2
                }
            }
        }
    }
    /*
     * 实现Runnable接口的方式实现
     */
    public class SellTicketDemo {
        public static void main(String[] args) {
            // 创建资源对象
            SellTicket st = new SellTicket();
     
            // 创建三个线程对象
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
     
            // 启动线程
            t1.start();
            t2.start();
            t3.start();
        }
    }

    出现问题:

    相同的票出现多次

    CPU的一次操作必须是原子性的

    还出现了负数的票

    随机性和延迟导致的

    解决线程安全问题的基本思想与方法

    首先想为什么出现问题?(也是我们判断是否有问题的标准)

    • 是否是多线程环境
    • 是否有共享数据
    • 是否有多条语句操作共享数据

    如何解决多线程安全问题呢?

    基本思想:让程序没有安全问题的环境。

    把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。

    解决线程安全问题实现1--同步代码块

    格式:synchronized(对象){需要同步的代码;}

    同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。

    修改上面的代码如下:

    public class SellTicket implements Runnable {
        // 定义100张票
        private int tickets = 100;
        //创建锁对象
        private Object obj = new Object();
        
        @Override
        public void run() {
            while (true) {
                synchronized (obj) {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()
                                + "正在出售第" + (tickets--) + "张票");
                    }
                }
            }
        }
    }
    /*
     * 同步代码块:
     *         synchronized(对象){
     *             需要同步的代码;
     *         }
     * 
     *         A:对象是什么呢?
     *             我们可以随便创建一个对象试试。
     *         B:需要同步的代码是哪些呢?
     *             把多条语句操作共享数据的代码的部分给包起来
     * 
     *         注意:
     *             同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
     *             多个线程必须是同一把锁。
     */
    public class SellTicketDemo {
        public static void main(String[] args) {
            // 创建资源对象
            SellTicket st = new SellTicket();
     
            // 创建三个线程对象
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
     
            // 启动线程
            t1.start();
            t2.start();
            t3.start();
        }
    }

    注意:同步代码块可以用任意对象做锁

    解决线程安全问题实现2--同步方法

    就是把同步关键字加到方法上

    1、同步方法的锁对象:this

    public class SellTicket implements Runnable {
        private static int tickets = 100;
        private Object obj = new Object();
        @Override
        public void run() {
            while (true) {
                synchronized (this) {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()
                                + "正在出售第" + (tickets--) + "张票 ");
                    }
                }
            }
        }
        private synchronized void sellTicket() {
            if(tickets > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()
                        + "正在出售第" + (tickets--) + "张票 ");
            }
        }
    }

    2、静态方法的锁对象:类的字节码文件对象。

    public class SellTicket implements Runnable {
        private static int tickets = 100;
        private Object obj = new Object();
        @Override
        public void run() {
            while (true) {
                synchronized (SellTicket.class) {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()
                                + "正在出售第" + (tickets--) + "张票 ");
                    }
                }
            }
        }
        private static synchronized void sellTicket() {
            if(tickets > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()
                        + "正在出售第" + (tickets--) + "张票 ");
            }
        }
    }

    同步的前提:

    • 多个线程
    • 多个线程使用的是同一个锁对象

    同步的好处:同步的出现解决了多线程的安全问题。

    同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

    解决线程安全问题实现3--Lock锁的使用

    虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

    ReentrantLock (Java Platform SE 6)

    一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

  • 相关阅读:
    关于树状数组区间最值
    Gym 100500B
    RQNOJ Bus
    关于加权的LIS问题
    vs tip1
    小常识
    我的魔方主力
    killer驱动
    从日升的mecha anime看mecha genre的衰退
    关于供给移动端的视频压制
  • 原文地址:https://www.cnblogs.com/wuyudong/p/java-multithreading3.html
Copyright © 2011-2022 走看看