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”的方式获取。

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

[ 优点 ]

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

[ 缺点 ]

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

原文地址:https://www.cnblogs.com/HigginCui/p/6136287.html