• 11_控制线程_多线程同步


    【线程安全问题例子】

    模拟售票案例,4个窗口售票,总共100张票。

    public class Demo {
        public static void main(String[] args) {
            SaleThread sa=new SaleThread();
            
            Thread t1=new Thread(sa);
            Thread t2=new Thread(sa);
            Thread t3=new Thread(sa);
            Thread t4=new Thread(sa);
            
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
    
    class SaleThread implements Runnable{
        private int tickets=100;    //总共100张票
        
        @Override
        public void run() {
            while(tickets>0){
                try {
                    Thread.sleep(10);    //此处休眠10ms 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }  
                System.out.println(Thread.currentThread().getName()+"----卖出的票号码:"+tickets--);
            }
        }
    }

    【运行结果】

    【分析】

    当只剩下一张票时,某个线程判断满足while(tickets>0)进入循环,然后休眠10ms,此时其它线程在这10ms内依次进入判断while(tickets>0),然后都休眠10ms,最后大家都执行最后的System.out.println(Thread.currentThread().getName()+"----卖出的票号码:"+tickets--);,直接导致出现了结果中出错的几种情况。

    【同步的基础】

    Java中的每一个对象都可以作为锁。

    同步方法     :锁是当前实例对象。

    静态同步方法:锁是当前对象的Class对象。

    同步代码块   :锁是synchronized括号里配置的对象。

    【同步代码块】

    想要解决上面的线程安全问题,必须保证处理共享资源的代码在任何时刻只能有一个线程访问。

    当多个线程使用同一个共享资源的时候,可以将处理共享资源的代码放在一个代码块中,使用synchronized关键字来修饰,被称作同步代码块

    synchronized(lock){
        //操作共享资源的代码块
    }

    lock:是一个锁对象,是执行同步代码块的关键,当线程执行同步代码块时,首先会检查锁对象的标志位,默认情况下标志位为1,此时线程会执行同步代码块,同时将锁对象置为0。当一个新的线程执行到这段同步代码块时,由于锁对象的标志位为0,该新线程会发生阻塞,等待当前线程执行完同步代码块时,锁对象的标志位重新置为1,新线程才能进入同步代码块执行其中的代码。循环往复,直到共享资源被处理完为止。

    【加同步代码块解决线程安全问题案例】

    public class Demo {
        public static void main(String[] args) {
            SaleThread sa=new SaleThread();
            
            Thread t1=new Thread(sa);
            Thread t2=new Thread(sa);
            Thread t3=new Thread(sa);
            Thread t4=new Thread(sa);
            
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
    
    class SaleThread implements Runnable{
        private int tickets=100;    //总共100张票
        
        @Override
        public void run() {
            while(true){
                synchronized (this) {
                    try {
                        Thread.sleep(10);    //此处休眠10ms 
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }  
                    if(tickets>0){
                        System.out.println(Thread.currentThread().getName()+"----卖出的票号码:"+tickets--);
                    }else{
                        break;
                    }
                }
            }
        }
    }

    【运行结果】

    ...............................

    【同步方法】

    在方法前面加上synchronized关键字修饰,被修饰的方法称为同步方法,它能使实现和同步代码快同样的功能。

    synchronized  返回值类型  方法名([参数1,......]){}

    被synchronized修饰的方法在某一时刻只允许一个线程访问,访问该方法的其它线程都会发生阻塞,直到当前线程访问完毕后,其它线程才有机会执行方法。

    【同步方法的案例】

    public class Demo {
        
        public static void main(String[] args) {
            TicketsThread tt=new TicketsThread();
            
            Thread t1=new Thread(tt);
            Thread t2=new Thread(tt);
            Thread t3=new Thread(tt);
            Thread t4=new Thread(tt);
            
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
    
    class TicketsThread implements Runnable{
        private int tickets =100;
        
        @Override
        public void run() {
            while(true){
                saleTickets();  //调用同步方法
                if(tickets<=0){
                    break;
                }
            }
        }
        //定义一个同步方法 saleTickets()
        private synchronized void saleTickets(){
            if(tickets>0){
                try {
                    Thread.sleep(10);   //所有的线程在这里都要休眠10ms
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"----卖出的票号码:"+tickets--);
            }
        }
    }

    【运行结果】

    ......

    【分析】

    同步方法其实也有自己的锁,它的锁就是当前调用该方法的对象,也就是this指向的对象。

    这样做的好处是:同步方法被所有线程共享,方法所在的对象相对于所有线程来说都是唯一的,从而保证了锁方法的唯一性。

    【扩展:静态方法怎么处理?】

    静态方法可以使用"类名.方法名()"方式直接被调用,调用静态方法无需创建对象,如果不创建对象,静态方法的锁就不会是this,JAVA中静态方法的锁该方法所在类的class对象,该对象可以直接使用“类名.class”的方式获取。

    【同步代码块、同步方法的优缺点】

    [ 优点 ]

    解决了多个线程同时访问共享数据时的安全问题,只要加上一个锁,在同一个时间内只能有一条线程执行。

    [ 缺点 ]

    线程执行同步代码块的时都要判断锁的状态,非常消耗资源,效率非常低。

  • 相关阅读:
    基因组注释
    GapCloser
    Endnote参考文献格式修改
    多行变单行
    AD的基因组【转载】
    ROC曲线
    自我觉察-4:觉察“不浪费食物”和“胃过饱食”的信念
    自我觉察6-我的价值感?
    表观遗传、开放染色质测序、ATAC-seq、ChIP-seq简介
    ADNI(Alzheimer`s disease neuroimaging initiative)介绍
  • 原文地址:https://www.cnblogs.com/HigginCui/p/6136287.html
Copyright © 2020-2023  润新知