zoukankan      html  css  js  c++  java
  • java线程详解(二)

    1,线程安全

    先看上一节程序,我们稍微改动一下:

    //线程安全演示
    //火车站有16张票,需要从四个窗口卖出,如果按照上面的多线程实现,程序如下
    class Ticket implements Runnable   
    {
        private int tick = 16;//票的张数---16
        public void run(){   
            while(true){
                if(tick>0){
                    //这里的sleep(100)是这次程序要表的关键,只是个模拟而已
                    try{
                        Thread.sleep(100);
                    }catch(Exception e){
                        
                    }
                  System.out.println(Thread.currentThread().getName() + "...sale:" + tick--);    
                }
            }
            
        }
        public static void main(String[] args)
        {
            Ticket t = new Ticket();
    
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            Thread t3 = new Thread(t);
            Thread t4 = new Thread(t);
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

    其他之处没有改变,只是在判断还有票之后立即睡眠一段时间,这在现实中有可能发生的,线程A刚执行到这里,突然cpu切换到其他线程B中,而B线程也刚好执行到判断语句又被A线程抢夺cpu,此时A线程不用再判断,直接输出,并修改ticket的值,此时有可能使得tickte>0不再满足,但是下一次B线程执行时,也不去判断tickte的值,导致出错。看看上面的程序结果(不为一),数了一下,打印了20张票,还有-1,-2,0的 情况,这就是线程安全问题了。

    image

    问题原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完另一个线程就来执行,导致共享数据的错误。

    解决办法:对多条操作共享数据的语句,只能让一个线程都执行过程中,其他线程也不能执行。即使其他线程拿到执行权!这就是java提供的同步代码块方案。其格式如下:

    synchronized(对象){

    需要同步的代码块

    }

    分析:上面的synchronized后面的对象如同锁,也叫同步监视器,持有锁的线程可以同步执行,没有持有锁的线程即使获取cpu的执行权,也不能进不去同步代码块。线程开始执行同步代码块必须先对同步监视器的锁定。

    同步前提:

    (1)必须有两个或者两个以上的线程。

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

    任何时刻只有一个线程获得对同步监视器的锁定,当同步代码块执行结束时,该线程会释放对同步监视器的锁定。

    如何看哪些代码需要同步?看看共享数据参与运算的范围if(tick>0),还有tick—;因此把上面程序改为如下:

    //线程安全演示
    //火车站有16张票,需要从四个窗口卖出,如果按照上面的多线程实现,程序如下
    class Ticket implements Runnable   
    {
        private int tick = 16;//票的张数---16
        Object obj = new Object();
        public void run(){   
            while(true){
                synchronized(obj){
                    if(tick>0){
                        //这里的sleep(100)是这次程序要表的关键,只是个模拟而已
                        try{
                            Thread.sleep(10);
                        }catch(Exception e){
                            
                        }
                      System.out.println(Thread.currentThread().getName() + "...sale:" + tick--);    
                    }
                }
            }
            
        }
        public static void main(String[] args)
        {
            Ticket t = new Ticket();
    
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            Thread t3 = new Thread(t);
            Thread t4 = new Thread(t);
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

    此时运行就没有出现错误。结果就不展示了。

    好处:解决线程安全问题

    缺点:多线程需要判断锁,较为消耗资源

    2,线程同步

    再看下面的例子

    /*
    需求:银行有一个金库,有两个储户分别存入300元,都是分3次存入
    目的:查看该程序是否有安全问题,如果有,如何解决?
    如何找问题:
    1,明确哪些代码是多线程运行代码
    2,明确哪些是共享数据
    3,明确哪些代码使用共享数据
    */
    
    class Bank
    {
        //Object obj = new Object();  ------1
        private int sum;
        public synchronized void add(int n){ //synchronized可以放在此处作为同步函数
            //synchronized(obj){  ------2
                sum = sum + n;
                try{
                    Thread.sleep(10);
                }catch(Exception e){
                            
                }
                System.out.println("sum = " + sum);
            //} ------3
        }
    }
    
    class Cus implements Runnable
    {
        private Bank b = new Bank();
        public void run(){
            for(int x = 0; x < 3; x++){
                b.add(100);
            }
        }
    }
    
    class BankDemo
    {
        public static void main(String[] agrs){
            Cus c = new Cus();
            Thread t1 = new Thread(c);
            Thread t2 = new Thread(c);
            t1.start();
            t2.start();
        }
    
    }

    上面代码如果不同步就会出现线程安全问题,但是同步不止刚才那种同步代码块,还有同步函数可以使用,本程序就是个例子。但是对于卖票那个程序来试试

    //线程安全演示
    //火车站有16张票,需要从四个窗口卖出,如果按照上面的多线程实现,程序如下
    class Ticket implements Runnable   
    {
        private int tick = 160;//票的张数---160
        //Object obj = new Object();
        public synchronized void run(){   
            while(true){
                //synchronized(obj){
                    if(tick>0){
                        //这里的sleep(1000)是这次程序要表的关键,只是个模拟而已
                        try{
                            Thread.sleep(100);
                        }catch(Exception e){
                            
                        }
                      System.out.println(Thread.currentThread().getName() + "...sale:" + tick--);    
                    }
                //}
            }
            
        }
        public static void main(String[] args)
        {
            Ticket t = new Ticket();
    
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            Thread t3 = new Thread(t);
            Thread t4 = new Thread(t);
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
    结果

    image

    其实这里面一直是线程0在执行,因为run方法是在同步,当0线程执行时别的线程都不能执行,这是因为同步的代码块范围不正确,只需要把需要同步的代码块封装为一个函数,便可以在run方法中调用这个函数即可。

    //线程安全演示
    //火车站有16张票,需要从四个窗口卖出,如果按照上面的多线程实现,程序如下
    class Ticket implements Runnable   
    {
        private int tick = 160;//票的张数
        public void run(){   
            while(true){
                show();//此处调用show方法
            }
            
        }
    
        public synchronized void show(){//将同步代码块封装起来
            if(tick>0){
                try{
                    Thread.sleep(100);
                }catch(Exception e){
                            
                }
                 System.out.println(Thread.currentThread().getName() + "...sale:" + tick--);    
            }
        }
        public static void main(String[] args)
        {
            Ticket t = new Ticket();
    
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            Thread t3 = new Thread(t);
            Thread t4 = new Thread(t);
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

    image

    此时可以看到几个线程都启动了。那么有个疑问,同步代码块这种方法有个同步监视器来进行锁住。那么同步方法怎么实现呢?

    其实函数都需要对象调用,每个函数都有一个对象引用,就是隐含的this,所以同步函数使用的锁就是this。

    通过程序来验证:

    使用两个线程来实现卖票,第一个线程在同步函数中,第二个线程在同步代码块中。都在执行卖票动作。

    class Ticket implements Runnable   
    {
        private int tick = 16;//票的张数
        Object obj = new Object();
        boolean flag = true;
        public  void run(){ 
            if(flag){
                while(true){
                    synchronized(obj){
                        if(tick>0){
                            try{
                                Thread.sleep(100);
                            }catch(Exception e){
                            
                            }
                          System.out.println(Thread.currentThread().getName() + "...code:" + tick--);    
                        }
                    }
                }
            }
            else{
                while(true){
                    show();
                }
            }
        }
    
        public synchronized void show(){
            if(tick>0){
                try{
                    Thread.sleep(100);
                }catch(Exception e){
                            
                }
                 System.out.println(Thread.currentThread().getName() + "...show:" + tick--);    
            }
        }
        public static void main(String[] args)
        {
            Ticket t = new Ticket();
    
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
    
            t1.start();
            try{
                Thread.sleep(100);
            }catch(Exception e){
                            
            }
            t.flag = false;
            t2.start();
    
        }
    }

    结果如下

    image

    先来解释一下,这个程序的run()方法中有同步代码块,还有一个同步函数。我们的意思是想让t1先执行,在main函数中让其sleep是因为如果没有sleep的话,主线程可能没有分配给t1的cpu执行权直接执行下面的flag = false这句,导致同步代码块中的代码永远不能执行。加入sleep可以执行同步代码块的代码,也可以执行同步函数中的代码,但是看到结果,打印了0号票,这是不允许的,是什么造成了这种情况?

    看看文章最前面的同步的前提,有两个,再次提出:

    (1)必须有两个或者两个以上的线程。-----满足

    (2)必须是多个线程使用同一个锁。-----不满足-----〉第一个锁是synchronized(obj)中的obj,还有一个同步函数,它的锁是this,就是调用它的对象。

    synchronized(obj)这句换为synchronized(this),就不会出现上述问题,所以这样满足第二个前提,说明同步函数使用的的确是this这个锁。

    有个情况,如果同步函数使用static修饰,那么静态方法中的锁是谁呢?肯定不是this,因为静态方法没有this。静态进内存时没有本类对象,但是有本类的字节码对象,该对象的类型是class.因此,对于静态方法,他的同步监视器(也就是锁)就是类名.class。把synchronized(obj)这句换为synchronized(Ticket.class),就不会出现0号票了。

    3,死锁

    死锁原因:同步中嵌套同步,但是锁却不同步。

    先修改上面的程序

    class Ticket implements Runnable   
    {
        private int tick = 1000;//票的张数
        Object obj = new Object();
        boolean flag = true;
        public  void run(){ 
            if(flag){
                while(true){
                    synchronized(obj){
                        System.out.println("*****************");
                        show();
                    }
                }
            }
            else{
                while(true){
                    System.out.println("------------------");
                    show();
                }
            }
        }
    
        public synchronized void show(){
            synchronized(obj){
                if(tick>0){
                    try{Thread.sleep(10);}catch(Exception e){}
                     System.out.println(Thread.currentThread().getName() + "...code:" + tick--);    
                }
            }
        }
        public static void main(String[] args)
        {
            Ticket t = new Ticket();
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            t1.start();
            try{Thread.sleep(100);}catch(Exception e){}
            t.flag = false;
            t2.start();
    
        }
    }

    image

    上面的程序出现死锁,原因是同步代码块中锁是obj,且嵌套同步函数(其锁是this),同步函数的锁是this,而其中又嵌套同步代码块,其锁是obj。可以修改锁即可。比如把obj全部换为this。这样代码就不会出错。但上述代码的输出-----和***之前加入一句判断语句if(tick>0)不然票卖完了还在打印。

    上面的例子是同步代码块中包含同步函数,同步函数中又包含同步代码块,下面再看一个只在同步代码块中出现的死锁的例子。

    class Test implements Runnable
    {
        private boolean flag;
        Test(boolean flag){
            this.flag = flag;
        }
        public void run(){
            if(flag){
                synchronized(MyLock.locka){
                    System.out.println("if a");
                    synchronized(MyLock.lockb){
                        System.out.println("if b");
                    }
                }
            }
    
            else{
                synchronized(MyLock.lockb){
                    System.out.println("else b");
                    synchronized(MyLock.locka){
                        System.out.println("else a");
                    }
                }
            }
        }
    }
    
    class MyLock
    {
        static Object locka = new Object();
        static Object lockb = new Object();
    }
    
    class DeadLockTest
    {
        public static void main(String[] agrs){
    
            Thread t1 = new Thread(new Test(true)); 
            Thread t2 = new Thread(new Test(false));
            t1.start();
            t2.start();
        }
    }

    image

    结合代码,在run()方法中,if语句中的同步代码块有嵌套,else也一样。他们也是这种情况:同步中嵌套同步,但是锁却不同步

    所以在以后的编程过程中一定要防止此类情况的发生。

    注:这些代码和相关结论参见毕向东的java基础视频教程和李刚的《疯狂java讲义》,都非常不错!

  • 相关阅读:
    【Redfin SDE intern】跪经
    刷题upupup【Java中Queue、Stack、Heap用法总结】
    刷题upupup【Java中HashMap、HashSet用法总结】
    【lintcode】二分法总结 II
    【lintcode】 二分法总结 I
    201671010117 2016-2017-2 《Java程序设计》面向对象程序设计课程学习进度条
    201671010117 2016-2017-2《Java程序设计》第十八周学习心得
    201671010117 2016-2017-2 《Java程序设计》Java第十七周学习心得
    201671010117 2016-2017-2 《JAVA程序设计》java第十六周学习心得
    201671010117 2016-2017-2 《JAVA程序设计》java第十五周学习心得
  • 原文地址:https://www.cnblogs.com/yefengyu/p/4854967.html
Copyright © 2011-2022 走看看