zoukankan      html  css  js  c++  java
  • java基础知识回顾之java Thread类学习(二)java多线程安全问题(锁)

    上一节售票系统中我们发现,打印出了错票,0,-1,出现了多线程安全问题。我们分析为什么会发生多线程安全问题?

    看下面线程的主要代码:

    复制代码
    @Override
        public void run() {
            // TODO Auto-generated method stub
            
            while(true){
                    if(ticket > 0){//当线程0被调起的时候,当执行到这条判断语句的时候,线程1被调起抢了CPU资源,线程0进入冻结状态。
                        try {
                            Thread.sleep(100);//中断当前活跃的线程,或者执行的线程
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"正在卖票"+ticket--);
                        //System.out.println(Thread.currentThread().getId());
                        //System.out.println(Thread.currentThread().getName());
                    }
                
            }
        }
    复制代码

    分析:当100张票卖到剩余最后1张的时候,也就是ticket=1的时候,有三个线程Thread-0,Thread-1,Thread-2,这时候,Thread-0进入if(ticket > 0)这个条件具备了执行资格,但不具备执行权。正在这个时候,CPU切换到了Thread-1,Thread-1也进入了if(ticket > 0)这个条件下面,CPU又切换到了Thread-2,Thread-2又进入阻塞状态。这个时候三个线程都通过了if(ticket > 0)判断,都要往下执行了,这个时候CPU的资源被Thread-0第一个线程抢到,执行ticket=1,ticket--,那么第一个线程执行完打印出ticket=1,CPU被Thread-2抢到,打印出ticket=0,同理线程3执行完打印出ticket=-1,这就出现了线程安全的问题。

    通过上面的分析大家知道了多线程安全产生的原因:当多条语句(if(ticket>0)和ticket--)在操作同一个线程的共享数据的时候(这里共享数据为ticket=100),一个线程执行了多条语句的一部分,还没有执行完,另一个线程抢到CPU资源,执行。导致数据共享错误。那么怎么解决呢?

    解决办法:当多条语句在操作共享数据时,只能让一个线程执行完,在执行的过程中,其它线程不可以参与执行。java对于多线程安全问题,提供了专业的解决方法,那就是锁。

    synchronized(对象){
             需要被同步的代码
     }

    这个“对象”被称为锁,持有锁的线程可以在同步代码块中执行,没有持有锁的线程即使获取到CPU的执行权,也进不去,因为没有获取锁。
     * 火车上的卫生间-经典“

    火车售票的问题的解决:代码如下:

    复制代码
    public class TicketsRunnable implements Runnable {
        private int ticket=100;
        Object obj = new Object();//对象锁,共同步代码块使用
        public TicketsRunnable(){
            System.out.println("*****************************");
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            
            while(true){
                synchronized(obj){  //同步代码块
                    if(ticket > 0){//当线程0被调起的时候,当执行到这条判断语句的时候,线程1被调起抢了CPU资源,线程0进入冻结状态。
                        try {
                            Thread.sleep(100);//中断当前活跃的线程,或者执行的线程
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"正在卖票"+ticket--);
                        //System.out.println(Thread.currentThread().getId());
                        //System.out.println(Thread.currentThread().getName());
                    }
                }
                
            }
        }
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            TicketsRunnable runna = new TicketsRunnable();
            Thread t1 = new Thread(runna);
            Thread t2 = new Thread(runna);
            Thread t3 = new Thread(runna);
            t1.start();
            t2.start();
            t3.start();
        }
    
    }
    复制代码

    通过测试,不会出现错票的问题。

    分析代码:当火车票剩余1张的时候,这个时候假设Thread-0获取到了CPU的执行权,并且持有对象锁,进入if条件,打印出买票1。ticket--,这个时候即使其他的线程获取到CPU的执行资格,但是设Thread-0的锁还没有释放,其他的线程拿不到锁,这样就进入不了if条件,那么只要等Thread-0执行完,Thread-0执行完后,ticket=0,其他线程即使拿到锁,因为if(ticket > 0)不能进入,所以执行不了。整个程序结束,卖票终止。

    总结:通过程序可以知道:同步的前提是:1.必须要有两个或者两个以上的线程,才需要同步。

                                                              2.必须是多个线程使用同一个锁。

                                                              3.要分析哪段代码需要加同步,必须保证同步中只能有一个线程在运行。

    同步锁的好处与弊端:1.好处,解决了多线程操作同意资源安全性问题。

                                  2.弊端:多个线程每次都需要判断锁,较为消耗资源

                

    这里举个例子讲解,同步synchronized在什么地方加,以及同步的前提:

     * 1.必须要有两个以上的线程,才需要同步。
     * 2.必须是多个线程使用同一个锁。
     * 3.必须保证同步中只能有一个线程在运行,锁加在哪一块代码

    那么我们要思考的地方有:1.知道我们写的哪些是多线程代码

                                      2.明确共享数据

                                      3.明确多线程运行的代码中哪些语句是操作共享数据的。、

                4.要确保多个线程使用同一个锁。

    下面的代码:需求:两个存户分别往银行存钱,每次村100块,分三次存完。

     第一种写法:使用同步代码块的方式

    复制代码
    class bank{
        private int sum;
        Object obj = new Object();//对象锁
        public  void add(int money){
            synchronized (obj) {
                sum +=money;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("sum="+sum);
            }
            
        }
        
    }
    复制代码

    第二种方法在函数上加synchronized:

    复制代码
    class bank{
        private int sum;
        //函数封装代码(加synchronized)==同步块封装代码
        public synchronized void add(int money){
            sum +=money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("sum="+sum);
        }
        
    }
    
    
    class Cus implements Runnable{
        private bank b = new bank();
    
        @Override
        public void run() {
            for(int i=0;i<3;i++){
                //System.out.println(Thread.currentThread().getName());
                b.add(100);
            }
        }
    }
    
    public class BankDemo {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Cus c1 = new Cus();
            Thread t1 = new Thread(c1);
            Thread t2 = new Thread(c1);
            t1.start();
            t2.start();
    
        }
    
    }
    复制代码

    总结:同步应该加到add方法上面,因为add方法被run方法调用,所以是线程代码,这里的sum是共享数据,add方法 里面 sum +=money;操作共享数据。这里用了在普通方法上面加syncronized代替同步代码块,这也叫做同步函数。那么同步函数用的锁是什么?我们下一节再接着讨论,多线程同步函数。

  • 相关阅读:
    Nginx 日志格式配置介绍
    Java Spring cron表达式使用详解
    Elasticsearch Query DSL
    Elasticsearch Search APIs
    网易2016研发工程师编程题:小易的升级之路
    2016奇虎360研发工程师内推笔试编程题:找镇长
    2016奇虎360研发工程师内推笔试编程题:找到字符串第一个只出现一次的字符
    lintcode: 最长无重复字符的子串
    lintcode :同构字符串
    lintcode : 跳跃游戏
  • 原文地址:https://www.cnblogs.com/hanease/p/15721260.html
Copyright © 2011-2022 走看看