• Java基础_通过模拟售票情景解决线程不安全问题


      用代码来模拟铁路售票系统,实现通过四个售票点发售某日某次列车的100张车票,一个售票点用一个线程表示

      第一种方法:通过继承Thread类的方法创建线程

      

    package com.Gary1;
    
    public class TicketThread extends Thread{
    
        //设置有100张票
        private static int count = 100;
    
        public TicketThread(String name) {
            super(name);
        }
    
        @Override
        public void run() {
            while(true) {
                //当票大于0张的时候卖票
                if(count>0) {
                    System.out.println(getName() + "卖出第" + count + "卖票");
                    count--;
                }else {
                    break;
                }
            }
            
        }
    
    }
    TicketThread.java
    package com.Gary1;
    
    public class GaryTest {
    
        public static void main(String[] args) {
            
            TicketThread t1 = new TicketThread("售票点1");
            TicketThread t2 = new TicketThread("售票点2");
            TicketThread t3 = new TicketThread("售票点3");
            TicketThread t4 = new TicketThread("售票点4");
            
            t1.start();
            t2.start();
            t3.start();
            t4.start();
            
        }
        
    }
    GaryTest.java

      可以看出,当把count设置成static之后,线程同步时还是会造成count票的数量不安全

      第二种方法:通过实现Runnable接口(实时共享数据,不需将count设置成静态)

       

    package com.Gary1;
    
    public class TicketRunnable implements Runnable{
        
        //设置有100张票 count不需要设置成static
        private int count = 100;
        
        public void run() {
            while(true) {
                //当票大于0张的时候卖票
                if(count>0) {
                    System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
                    count--;
                }else {
                    break;
                }
            }
        }
    
    }
    TicketRunnable.java
    package com.Gary1;
    
    public class GaryTest {
    
        public static void main(String[] args) {
            
            TicketRunnable t = new TicketRunnable();
            
            Thread t1 = new Thread(t,"售票点1");
            Thread t2 = new Thread(t,"售票点1");
            Thread t3 = new Thread(t,"售票点1");
            Thread t4 = new Thread(t,"售票点1");
            
            
            t1.start();
            t2.start();
            t3.start();
            t4.start();
            
        }
        
    }
    GaryTest.java

      发现此时不管用继承Thread类或者实现Runnable接口,都无法保证线程中变量的安全!!!

      (多个线程同时要修改一个变量的时候,引起冲突)

     

      解决线程不安全方法

      a)线程安全问题解决

      synchronized(对象){}//锁住某个对象,如果这个对象已经被锁定,那么等待。
    public void run() {
            while(true) {
                synchronized(lock) {//第一个线程来的时候会锁上,并拿走钥匙,第二个线程来的时候,发现被锁上,等待
                    //当票大于0张的时候卖票
                    if(count>0) {
                        System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
                        count--;
                    }else {
                        break;
                    }
                }//执行完,归还钥匙
            }
        }
    package com.Gary1;
    
    public class TicketRunnable implements Runnable{
        
        //设置有100张票 count不需要设置成static
        private int count = 100;
        
        private Object lock = new Object();
        
        public void run() {
            while(true) {
                synchronized(lock) {//第一个线程来的时候会锁上,并拿走钥匙,第二个线程来的时候,发现被锁上,等待
                    //当票大于0张的时候卖票
                    if(count>0) {
                        System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
                        count--;
                    }else {
                        break;
                    }
                }//执行完,归还钥匙
        
            }
        }
    
    }
    TicketRunnable.java

      通过继承Thread类解决线程不安全方法

    package com.Gary1;
    
    public class TicketThread extends Thread{
    
        //设置有100张票
        private static int count = 100;
    
        private static Object lock = new Object();
        
        public TicketThread(String name) {
            super(name);
        }
    
        @Override
        public void run() {
            synchronized(lock){
                while(true) {
                    //当票大于0张的时候卖票
                    if(count>0) {
                        System.out.println(getName() + "卖出第" + count + "卖票");
                        count--;
                    }else {
                        break;
                    }
                }
            }
            
        }
    
    }
    TicketThread.java

      优化,使用Thread.sleep()解决抢占式优先问题,避免同一个售票点一直抢占着线程的CPU

    package com.Gary1;
    
    public class TicketRunnable implements Runnable{
        
        //设置有100张票 count不需要设置成static
        private int count = 100;
        
        private Object lock = new Object();
        
        public void run() {
            while(true) {
                synchronized(lock) {//第一个线程来的时候会锁上,并拿走钥匙,第二个线程来的时候,发现被锁上,等待
                    //当票大于0张的时候卖票
                    if(count>0) {
                        System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
                        count--;
                    }else {
                        break;
                    }
                }//执行完,归还钥匙
        
                try {
                    Thread.sleep(100);
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
                
            }
        }
    
    }
    TicketRunnable.java

      b)出现线程安全问题的地方,要锁同一个对象(可以是当前对象,也可以单独创建一个对象)

      c)锁住某个对象,如果这个对象已经被锁定,那么停止当前线程的执行,一直等待,一直等到对象被解锁。

        (保证同一个时间,只有一个线程在使用这个对象,)

      d)创建同步方法

        同步方法锁的是哪个对象呢?锁定的是当前对象this

    public synchronized void sellTicket() {
            //synchronized(lock) {//第一个线程来的时候会锁上,并拿走钥匙,第二个线程来的时候,发现被锁上,等待
                //当票大于0张的时候卖票
                if(count>0) {
                    System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
                    count--;
                }
            }//执行完,归还钥匙
    package com.Gary1;
    
    public class TicketRunnable implements Runnable{
        
        //设置有100张票 count不需要设置成static
        private int count = 100;
        
        private Object lock = new Object();
        
        public void run() {
            while(count>0) {
                
                sellTicket();
        
                try {
                    Thread.sleep(100);
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
                
            }
        }
        
        
        public synchronized void sellTicket() {
            //synchronized(lock) {//第一个线程来的时候会锁上,并拿走钥匙,第二个线程来的时候,发现被锁上,等待
                //当票大于0张的时候卖票
                if(count>0) {
                    System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
                    count--;
                }
            }//执行完,归还钥匙
        
    
    }
    TicketRunnable.java

      题外话:因为StringBuffer类下的方法append()添加字符串被加了synchronized锁,所以线程安全!!!同理可查看Vector集合和ArrayList集合下的add()方法,可以发现Vector集合下的add()方法线程安全,ArrayList集合下的add()方法线程不安全。

      线程安全的类 

        安全: StringBuffer Vector 

        不安全:StringBuilder ArrayList

        @Override
        public synchronized StringBuffer append(String str) {
            toStringCache = null;
            super.append(str);
            return this;
        }

      同步锁的第二种使用方式

        a)创建锁对象 ReentrantLock lock = new ReentrantLock();

        b)加锁和解锁使用tryfinally、lock.lock()、lock.unlock()

      lock.lock();    //加锁
                try {
                    if(count>0) {
                        System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
                        count--;
                    }
                }finally {
                    //不管上边代码时不时出现异常,都能保证解锁
                    lock.unlock();  //解锁
                }
    package com.Gary1;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    public class TicketRunnable implements Runnable{
        
        //设置有100张票 count不需要设置成static
        private int count = 100;
        
        private ReentrantLock lock= new ReentrantLock(); 
        
        public void run() {
            while(count>0) {
                
                lock.lock();    //加锁
                try {
                    if(count>0) {
                        System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
                        count--;
                    }
                }finally {
                    //不管上边代码时不时出现异常,都能保证解锁
                    lock.unlock();  //解锁
                }
                
                
                try {
                    Thread.sleep(100);
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
                
            }
        }
        
    
    
    }
    TicketRunnable.java

      买票问题升级(两种卖票方式,一种通过电影院窗口,一种通过手机App)

      主要目的:保证两种方式使用的是同一把锁

    package com.Gary1;
    
    //管理不同方式,单都是卖同一种资源
    public class TicketMng {
    
        //表示票的剩余数量
        public static int count = 100;
        
    }
    TicketMng.java
    package com.Gary1;
    
    public class AppThread implements Runnable{
    
        //保证使用同一个lock锁
        public AppThread(Object lock) {
            this.lock = lock;
        }
        
        private Object lock;
        
        @Override
        public void run() {
        
            while(TicketMng.count>0) {
                synchronized(lock) {
                    if(TicketMng.count>0) {
                        System.out.println(Thread.currentThread().getName()+"售出第"+TicketMng.count+"票");
                        TicketMng.count--;
                    }
                }
            
            
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
        }
            
        }
    
    }
    AppThread.java
    package com.Gary1;
    
    public class WindowThread implements Runnable{
    
        //保证使用同一个lock锁
        public WindowThread(Object lock) {
            this.lock = lock;
        }
        
        private Object lock;
        
        @Override
        public void run() {
        
            while(TicketMng.count>0) {
                synchronized(lock) {
                    if(TicketMng.count>0) {
                        System.out.println(Thread.currentThread().getName()+"售出第"+TicketMng.count+"票");
                        TicketMng.count--;
                    }
                }
            
            
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
        }
            
        }
    
    }
    WindowThread.java
    package com.Gary1;
    
    public class GaryTest2 {
    
        public static void main(String[] args) {
            
            //windowThread和appThread共用同意一把锁
            Object lock = new Object();
            
            WindowThread windowThread = new WindowThread(lock);
            AppThread appThread = new AppThread(lock);
            
            new Thread(windowThread,"窗口").start();
            new Thread(appThread,"手机APP").start();
            
        }
        
    }
    GaryTest2.java
  • 相关阅读:
    机器学习-分类算法-决策树,随机森林10
    机器学习-分类算法-模型选择与调优09
    机器学习-分类算法-精确率和召回率08
    机器学习-分类算法-朴素贝叶斯算法07
    机器学习-分类算法-K-近邻算法06
    java读取自定义配置文件并引用
    kafka发布消息报错LEADER_NOT_AVAILABLE
    Kettle位置参数(Argument)、命名参数(Parameter)、变量(Variable)
    kettle里的两个参数和一个变量
    如何在命令行下运行kettle的作业(job)和转换(transform)
  • 原文地址:https://www.cnblogs.com/1138720556Gary/p/11944831.html
Copyright © 2020-2023  润新知