什么是线程安全?

多个线程同时访问了相同的资源,并对该资源进行写的操作,使得资源发生改变时就会产生线程安全问题。只有资源没有发生变化,多个资源同时进行读取操作的时候线程才是安全的。

例:

class SaleTicket implements Runnable(
    // 初始化当前剩余票数
    private Integer count = 100;

    @Override
    public void run() {
        while (ticketCount>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.sale();
        }
    }

    public void sale(){
        if(ticketCount > 0){
            System.out.println(Thread.currentThread().getName()+"正在出售第"+(100-ticketCount+1)+"张票");
            ticketCount--;
        }
    }
)

在测试方法中启动多个线程同时进行买票操作:

class TestSaleTicket{
    public static void main(String[] args){
        SaleTicket sale = new SaleTicket();

        Thread t1 = new Thread(sale,"售票窗口1");
        Thread t2 = new Thread(sale,"售票窗口2");

        t1.start();
        t2.start();
    }
}

该方法执行之后可以看到两个线程会同时卖一张票,此时这段代码就出现了线程安全问题。

解决线程安全问题的方法

  • 同步代码块:多个线程抢同一个锁,没有抢到锁的线程等待抢到锁的线程执行完毕之后释放锁。
synchronized(锁对象){
    /* 操作共享数据的代码
    *   ......
    */
}
  1. 代码块中的锁对象可以是任意对象。
  2. 所有线程使用的锁对象时同一个对象。
  3. 锁对象锁住同步代码块,只能让一个线程在代码块中执行。

如上代码中的sale()方法可以写成:

public void sale(){
    synchronized(this){
        if(count > 0){
            System.out.println(Thread.currentThread().getName()+"正在出售第"+(100-ticketCount+1)+"张票");
            ticketCount--;
        }
    }
}
  • 使用同步方法:将需要访问资源的代码封装成一个方法,并且加上synchronized修饰符。 例:
public synchronized void sale(){
    if(count > 0){
            System.out.println(Thread.currentThread().getName()+"正在出售第"+(100-ticketCount+1)+"张票");
            ticketCount--;
        }
}
  • 静态同步方法(静态方法+同步代码块):静态方法的锁对象是本类的class属性。 例:
class SaleTicket implements Runnable{
    // 初始化定义车票的数量
    private static Integer count = 100; 
    
    @Override
    public void run() {
        while (ticketCount>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sale();
        }
    }

    public static void sale(){
        synchronized(SaleTicket.class){
            if(count > 0){
                System.out.println(Thread.currentThread().getName()+"正在出售第"+(100-ticketCount+1)+"张票");
                ticketCount--;
            }
        }
    }
}
  • Lock锁:java.util.concurrent.locks包中的Lock接口
    两个方法:

    void lock() 获取锁
    void unlock() 释放锁

Lock接口实现类java.util.concurrent.locks.ReentrantLock
使用步骤:

在成员位置创建一个ReentrantLock对象 在可能出现安全问题的代码前调用lock方法获取锁 在可能出翔安全问题的代码后调用unlock方法释放锁

private Lock lock = new ReentrantLock();

 public void sale(){
        lock.lock();
        if(ticketCount > 0){
            System.out.println(Thread.currentThread().getName()+"正在出售第"+(100-ticketCount+1)+"张票");
            ticketCount--;
        }
        lock.unlock();
    }

非静态同步方法使用的是this锁。
使用synchroized时最好使代码块包裹可能会产生线程安全的代码,不要包裹无关线程安全问题的代码,否则会影响程序的效率。

版权声明:本文为kfw5264原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/kfw5264/p/12215825.html