• Java 线程安全


    参照:https://www.cnblogs.com/lizhangyong/p/8029287.html

    一个程序在运行起来时,会转换为进程,通常含有多个线程。

    通常情况下,一个进程中的比较耗时的操作(如长循环、文件上传下载、网络资源获取等),往往会采用多线程来解决。

    比如,现实生活中,银行取钱问题、火车票多个窗口售票问题等,通常会涉及并发问题,从而需要用到多线程技术

    当进程中有多个并发线程进入一个重要数据的代码块时,在修改数据的过程中,很有可能引发线程安全问题,从而造成数据异常。例如,正常逻辑下,同一个编号的火车票只能售出一次,却由于线程安全问题而被多次售出,从而引起实际业务异常。

    接下来,我以售票问题,来演示多线程问题中对核心数据保护的重要性。我们先来看不对多线程数据进行保护时会引发什么样的状况。

    public class SellTicket extends Thread {
    
        static int tickets = 10;
        @Override
        public void run(){
            while(tickets > 0){
                System.out.println(Thread.currentThread().getName() + "-->售出第 " + tickets + "张票" );
                tickets--;
            }try{
                Thread.sleep(100);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            
            if(tickets < 0){
                System.out.println(Thread.currentThread().getName()+"-->售票结束!");
            }
        }
    
        public static void main(String[] args) {
            SellTicket sell = new SellTicket();
            Thread t1=new Thread(sell, "1号窗口");
            Thread t2=new Thread(sell, "2号窗口");
            Thread t3=new Thread(sell, "3号窗口");
            Thread t4=new Thread(sell, "4号窗口");
    //        t1.setName("1号窗口");
    //        SellTicket t2 = new SellTicket();
    //        t2.setName("2号窗口");
    //        SellTicket t3 = new SellTicket();
    //        t3.setName("3号窗口");
    //        SellTicket t4 = new SellTicket();
    //        t4.setName("4号窗口");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

     运行结果:

    1号窗口-->售出第 10张票
    1号窗口-->售出第 9张票
    1号窗口-->售出第 8张票
    1号窗口-->售出第 7张票
    1号窗口-->售出第 6张票
    3号窗口-->售出第 6张票
    3号窗口-->售出第 4张票
    3号窗口-->售出第 3张票
    3号窗口-->售出第 2张票
    3号窗口-->售出第 1张票
    1号窗口-->售出第 5张票
    4号窗口-->售票结束!
    1号窗口-->售票结束!
    3号窗口-->售票结束!
    2号窗口-->售票结束!

    同一张票会被售出多次,显然不符合实际逻辑。

    为了解决上述脏数据的问题,我为大家介绍3种使用比较普遍的三种同步方式。

    第一种,同步代码块。

    有synchronized关键字修饰的语句块,即为同步代码块。同步代码块会被JVM自动加上内置锁,从而实现同步。

    public class SellTicket2 {
        
        static int tickets=10;
        
        class SellTickets implements Runnable{
            @Override
            public void run() {
                synchronized(this){
                    while(tickets > 0){
                        System.out.println(Thread.currentThread().getName()+" -->售出第 "+tickets+" 张票");
                        tickets--;
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if(tickets<=0){
                        System.out.println(Thread.currentThread().getName()+" -->售票结束!");
                    }
                }
            }
        }
        
        public static void main(String[] args) {
            SellTickets sell = new SellTicket2().new SellTickets();
            Thread t1=new Thread(sell, "1号窗口");
            Thread t2=new Thread(sell, "2号窗口");
            Thread t3=new Thread(sell, "3号窗口");
            Thread t4=new Thread(sell, "4号窗口");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

    运行结果:

    1号窗口 -->售出第 10 张票
    1号窗口 -->售出第 9 张票
    1号窗口 -->售出第 8 张票
    1号窗口 -->售出第 7 张票
    1号窗口 -->售出第 6 张票
    1号窗口 -->售出第 5 张票
    1号窗口 -->售出第 4 张票
    1号窗口 -->售出第 3 张票
    1号窗口 -->售出第 2 张票
    1号窗口 -->售出第 1 张票
    1号窗口 -->售票结束!
    2号窗口 -->售票结束!
    4号窗口 -->售票结束!
    3号窗口 -->售票结束!

    第二种,同步方法 。

    即有synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

    public class SellTicket2 {
        
        static int tickets = 10;
        
        class SellTickets implements Runnable{
            @Override
            public void run() {
                while(tickets > 0){
                    synMethod();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(tickets <= 0){
                        System.out.println(Thread.currentThread().getName()+" -->售票结束!");
                    }
                }
    
            }
            
            synchronized void synMethod(){
                synchronized(this){
                    if(tickets <= 0){
                        return;
                    }
                    System.out.println(Thread.currentThread().getName()+" -->售出第 "+tickets+" 张票");
                    tickets--;
                }
            }
        }
        
        public static void main(String[] args) {
            SellTickets sell = new SellTicket2().new SellTickets();
            Thread t1=new Thread(sell, "1号窗口");
            Thread t2=new Thread(sell, "2号窗口");
            Thread t3=new Thread(sell, "3号窗口");
            Thread t4=new Thread(sell, "4号窗口");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

    第三种,Lock锁机制。

    通过创建Lock对象,采用lock()加锁,采用unlock()解锁,来保护指定代码块。

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class SellTicket2 {
        
        static int tickets = 10;
        
        class SellTickets implements Runnable{
            Lock lock = new ReentrantLock();
            
            @Override
            public void run() {
                while(tickets > 0){
                    try {
                        lock.lock();
                        if(tickets <= 0){
                            return;
                        }
                        System.out.println(Thread.currentThread().getName()+" -->售出第 "+tickets+" 张票");
                        tickets--;
                        
                    } finally{ 
                        lock.unlock();
                        try{
                            Thread.sleep(100);
                        }catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if(tickets <= 0){
                        System.out.println(Thread.currentThread().getName()+" -->售票结束!");
                    }
                }
    
            }
        }
        
        public static void main(String[] args) {
            SellTickets sell = new SellTicket2().new SellTickets();
            Thread t1=new Thread(sell, "1号窗口");
            Thread t2=new Thread(sell, "2号窗口");
            Thread t3=new Thread(sell, "3号窗口");
            Thread t4=new Thread(sell, "4号窗口");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
  • 相关阅读:
    《深入浅出WPF》4.0x名称空间
    《深入浅出WPF》3.0 XAML语法
    DataGridview 自动切换到 下一行
    C# 生成条形码图片,效果不错
    将DataTable 导出为csv
    DataTable,DataGridVIew转换到xls 方法 (转)
    NPOI读取Excel 数据 转。。。
    修改 字段大小
    VC
    统计每天 指定 时间段内 的数据
  • 原文地址:https://www.cnblogs.com/jszfy/p/12720900.html
Copyright © 2020-2023  润新知