线程安全

线程安全

一、线程安全

  1. 多线程访问共享数据会产生线程安全问题
  2. 线程安全问题实例:

如果有多个线程在同时运行,而这些线程可能同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

模拟电影院买票,多个窗口卖100张票(采用实现Runnable接口的方法)
模拟买票:

//模拟卖电影票
public class RunnableImpl implements Runnable{
    //票数100张
    private int ticket=100;
    @Override
    public void run() {
        //判断票数是否大于0,如果大于0可以进行买票
        while (true){
            if(ticket>0){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在卖"+ticket+"张票");
                ticket--;
            }
        }
    }
}

测试类:

        //创建接口的实现类对象
        RunnableImpl run=new RunnableImpl();
        //创建线程对象,传递接口的实现类对象
        Thread t1=new Thread(run);
        Thread t2=new Thread(run);
        Thread t3=new Thread(run);
        t1.start();
        t2.start();
        t3.start();

运行结果:
在这里插入图片描述a
在这里插入图片描述

  • 出现相同票数的现象,比如同时在卖第100张票
  • 出现了不存在的票,第0张
    几个窗口(线程)票数不能同步,就出现了线程安全问题。

二、如何解决线程安全问题

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制
有三种方法可以完成同步操作:

1. 同步代码块

使用格式:

synchronized(同步锁){      
      可能会出现问题的代码块
}

同步锁:

  • 通过锁对象可以使用任意对象
  • 但必须保证多个线程使用的锁对象是同一个
  • 锁对象的作用:把同步代码块锁住,只让一个线程在同步代码中执行

代码实现:

   private int ticket=100;
    //锁对象
    Object obj=new Object();
    @Override
    public void run() {
        //判断票数是否大于0,如果大于0可以进行买票
        while (true){
        //使用同步代码块
            synchronized (obj){
                if(ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在卖"+ticket+"张票");
                    ticket--;
                }
            }

        }

同步中的线程,没有执行完毕不会释放锁
同步外的线程没有锁进不去同步
因为频繁的判断锁,所以程序的效率会降低

2. 同步方法

  • 同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。使用格式如下:
public synchronized void method(){   
         可能会产生线程安全问题的代码  
 }
  • 使用同步方法代码如下:
/*使用同步方法解决线程安全问题
 *  1.把访问了共享数据的代码块抽取出来,放到一个方法中
 *  2.方法加上synchronized修饰符*/
public class RunnableImpl implements Runnable{
    private int ticket=100;
    //同步方法
    public synchronized void ticket(){
        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+"正在卖"+ticket+"张票");
            ticket--;
        }
    }
    @Override
    public void run() {
        //判断票数是否大于0,如果大于0可以进行买票
        while (true){
           //payticket();
            ticket();
        }
    }
  • 静态同步方法的使用
//静态同步方法,其对象锁是本类的class属性
    public static void payticket(){
        synchronized (RunnableImpl.class){
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+"正在卖"+ticket+"张票");
                ticket--;
            }
        }

    }
  • 同步方法和静态同步方法中锁对象的区别
    ①同步方法的锁对象是实现类对象,即this
    ②静态同步方法的锁对象不能是this,this是创建对象之后产生的,静态方法优先于对象。它的锁对象是本类的class属性----->class文件对象(反射)

3. Lock锁

java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:

  • public void lock() :加同步锁。
  • public void unlock() :释放同步锁
    实现步骤:
  • 在成员位置创建一个ReentrantLock对象
  • 在可能出现安全问题的代码前调用Lock接口中的lock方法获取锁
  • 在可能出现安全问题的代码后调用Lock接口中的unlock方法释放锁
    代码实现如下:
public class RunnableImpl implements Runnable{
    private static int ticket=100;
    //使用多态创建一个ReentrantLock对象
    Lock l=new ReentrantLock();
    @Override
    public void run() {
        //判断票数是否大于0,如果大于0可以进行买票
        while (true){
            //获得锁
            l.lock();
            if(ticket>0){
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName()+"正在卖"+ticket+"张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //释放锁
                    l.unlock();//无论程序是否异常,都会把锁释放
                }
            }
        }
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值