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

Posted Cynical丶Gary

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

 

 

 

 

 

 

 

 

 

以上是关于Java基础_通过模拟售票情景解决线程不安全问题的主要内容,如果未能解决你的问题,请参考以下文章

线程模拟售票问题

java多线程——多线程的安全问题

java-利用多线程Runnable,公用一个参数问题

java多线程(线程安全,线程同步)

解决线程安全的三种方法

多线程安全问题产生&解决方案