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();
    }
}

 

posted on 2020-04-17 16:41  青柠锦鲤  阅读(175)  评论(0编辑  收藏  举报