美文网首页
Java多线程 ----(2)多线程同步

Java多线程 ----(2)多线程同步

作者: 艾剪疏 | 来源:发表于2018-08-15 09:03 被阅读70次

    1 同步函数
    2 同步代码块
    3 锁

    另外,这篇文章尝试了一种新的行文风格,轻喷。

    1、多线程存在的一个问题:当多个线程存在时,可能会出现同步问题。

    例如,在某一个线程休眠的时候,可能其他线程会进入if语句继续执行,而当这个线程苏醒继续执行剩余的代码,程序的状态已经发生巨大变化,继续执行就会导致无法预计的后果。
    例如,常见的卖票程序,总共100张票,在某个线程休眠之后,后进来的接着执行,会出现-1,-2号票。

    //创建Ticket类,实现Runnable方法
    public class Ticket implements Runnable{
    
        private int ticketCount = 100;
        public void run(){
            while(true){
                if(ticketCount>0){//该语句未生效,结果中出现了0,-1,-2号票
                     try{
                       Thread.sleep(10);//先进来的线程休眠,后进来的接着执行,可能出现异常
                    }catch(InterruptException){
                        e.printStack();
                    }
                    System.out.println(Thread.currentThread().getName()+"------"+ticketCount--);
                }
            }
        }
    }
    
    public static void main(String args[]){
            Ticket t = new Ticket();
            //启动四个线程,共享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.png

    2、解决这个问题

    常见的方法是使用Java提供的同步代码块,语法如下:

    synchronized(对象){
    }
    

    修改后代码如下:

    //创建Ticket类,实现Runnable方法
    public class Ticket implements Runnable{
    
        private int ticketCount = 100;
        Object obj = new Object();//必须有对象,代表锁,必须放在执行代码的外层,保证其唯一性
        public void run(){
            while(true){
              synchronized(obj){
                if(ticketCount>0){
                     try{
                       Thread.sleep(10);//可能出现异常
                    }catch(InterruptException){
                        e.printStack();
                    }
                    System.out.println(Thread.currentThread().getName()+"------"+ticketCount--);
                }
              }
            }
        }
    }
    

    通过上述的锁即可解决多线程的安全问题。

    对象就如同锁。持有锁的线程可以在同步中执行。
    没有持有锁的线程即使获取到了CPU的执行权,也无法进入上锁的代码块。

    同步的前提:
    1、必须要有两个或者两个以上的线程;
    2、必须是多个线程同时使用一个锁;
    同步的好处:解决了多线程安全问题。
    弊端:多个线程需要判断,较为消耗资源。

    3、同步函数

    除了同步代码块之外,也可以用同步函数来来实现代码同步的功能。
    例如:

    //创建Ticket类,实现Runnable方法
    public class Ticket implements Runnable{
        private int ticketCount = 100;
        public synchronized void run(){
            while(true){
                if(ticketCount>0){
                     try{
                       Thread.sleep(10);//可能出现异常
                    }catch(InterruptException){
                        e.printStack();
                    }
                    System.out.println(Thread.currentThread().getName()+"------"+ticketCount--);
                }
              }
        }
    }
    

    但是,上述代码中同步函数的使用会导致一个问题,如图所示:

    image.png

    原因是:在0线程未执行完毕之前,其他的线程无法获得执行权,进入到该方法中来。

    所以,在使用同步代码时:
    必须要搞清楚“哪些些代码需要被同步,哪些则不能被同步!”

    那么在改方法中需要同步的代码只涉及,ticket的操作,故可以将该部分代码抽取出来,使用一个同步函数表示,如下:

    private int ticketCount = 100;
        public void run(){
            while(true){
                show();
            }
        }
    
       //抽取出需要同步的代码
        public synchronized void show(){
            if(ticketCount>0){
                try{
                    Thread.sleep(10);
                }catch(Exception e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"**********"+ticketCount--);
            }
        }
    

    如此,即可实现多线程安全的功能


    image.png

    4 、新的问题:在上述代码中,似乎并没有新创建一个锁对象

    Object obj = new Object();//被删除了
    

    那么,在这个时候,同步函数使用的锁是哪个呢?

    结论:同步函数使用的是this锁。

    验证:
    写一个小程序来测试一下,如下:
    功能:声明两种加锁的方式来实现同步,一种是函数块,一种是代码块;

    假设1:如果说两种方式使用的是同一个锁。如果是这样,下面的程序应该可以很好的完成同步功能,不会出现异常。

    代码如下:

    public class Ticket implements Runnable{
    
        private int ticketCount = 100;
        Object obj = new Object();
        boolean flag = true;
        public void run(){
            if(flag){
                while(true){
                    show();
                }
            }else{
                while(true){
                    synchronized (obj) {
                        if(ticketCount>0){
                            try{
                                Thread.sleep(10);
                            }catch(Exception e){
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName()+"*****NOT-SHOW*****"+ticketCount--);
                        }
                    }
                }
            }
        }
    
        public synchronized void show(){
            if(ticketCount>0){
                try{
                    Thread.sleep(10);
                }catch(Exception e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"****SHOW******"+ticketCount--);
            }
        }
    }
    

    主函数如下:

    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(10);//必须让主线程休眠一会儿,否则flag总是false
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t.flag = false;
            t2.start();
        }
    

    运行结果如下


    image.png

    通过上图中0号票的存在,
    假设1被推翻,即两种同步代码用的并不是同一个锁。

    假设2:show()同步函数使用的是this锁,代码块使用的是new的Object锁(已知)。

    验证代码如下:将obj换成this,若未出现安全问题,则说明show()使用的是this锁。

    public class Ticket implements Runnable{
    
        private int ticketCount = 100;
        Object obj = new Object();
        boolean flag = true;
        public void run(){
            if(flag){
                while(true){
                    show();
                }
            }else{
                while(true){
                    synchronized (this) {
                        if(ticketCount>0){
                            try{
                                Thread.sleep(10);
                            }catch(Exception e){
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName()+"*****NOT-SHOW*****"+ticketCount--);
                        }
                    }
                }
            }
        }
    
        public synchronized void show(){
            if(ticketCount>0){
                try{
                    Thread.sleep(10);
                }catch(Exception e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"****SHOW******"+ticketCount--);
            }
        }
    }
    

    运行结果如下:


    image.png

    结果中并未出现0号异常票,所以,假设得证!

    结论:同步函数使用的是this锁。

    5 再说一个要注意的地方:

    如果同步函数被静态所修饰,使用的锁是该方法所在类的字节码文件对象,可通过类名称.class获得。
    代码例子:

    public class Ticket implements Runnable{
    private static int ticketCount = 100;
        Object obj = new Object();
        boolean flag = true;
        public void run(){
            if(flag){
                while(true){
                    show();
                }
            }else{
                while(true){
                    synchronized (Ticket.class) {
                        if(ticketCount>0){
                            try{
                                Thread.sleep(10);
                            }catch(Exception e){
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName()+"*****NOT-SHOW*****"+ticketCount--);
                        }
                    }
                }
            }
        }
    
        public static synchronized void show(){
            if(ticketCount>0){
                try{
                    Thread.sleep(10);
                }catch(Exception e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"****SHOW******"+ticketCount--);
            }
        }
    }
    

    结果如下:


    image.png

    结果正常,无0号票,但是如果不使用Ticket.class,使用this就会出现0号票。

    END


    P.S.第一次采用这种行文方式来写,感觉不错,更加面向浏览者,是个好的转变!O(∩_∩)O哈哈~

    相关文章

      网友评论

          本文标题:Java多线程 ----(2)多线程同步

          本文链接:https://www.haomeiwen.com/subject/zybvmftx.html