线程安全问题解决方法

线程安全问题

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。(单线程程序是不会出现线程安全问题的;多线程访问了共享的数据,会产生线程安全问题)

通过卖票案例演示线程的安全问题:
我们来模拟电影院的售票窗口,实现多个窗口同时卖一场电影票(多个窗口一起卖这100张票)需要窗口,采用线程对象来模拟﹔需要票,Runnable接口子类来模拟

模拟票︰

public class safe implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while(true){
            if(ticket>0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
                ticket--;
            }
        }
    }
}

模拟卖票案例:

public class safemain {
    public static void main(String[] args) {
        safe s1=new safe();
        Thread t0=new Thread(s1);
        Thread t1=new Thread(s1);
        Thread t2=new Thread(s1);
        t0.start();
        t1.start();
        t2.start();

    }

}

结果会出现卖重复票和0票,-1票的情况,出现这种情况的原因:

解决方法:线程同步

完成同步操作有三种方法:

1. 同步代码块

同步代码块︰synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式:
synchronized(同步锁){
       需要同步操作的代码
}

同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
1.锁对象可以是任意类型。
2.多个线程对象要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

使用同步代码块解决代码︰

public class safe implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while(true){
            synchronized (this){
                if(ticket>0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
                    ticket--;
                }
            }
        }
    }
}
/*或
public class safe implements Runnable{
    private int ticket=100;
    Object obj=new Object();//放在run方法外部,否则多个线程会产生多个对象,不能保证唯一
    @Override
    public void run() {
        while(true){
            synchronized (obj){
                if(ticket>0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
                    ticket--;
                }
            }
        }
    }
}*/

同步技术的原理:

2.同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2 .在方法上添加synchronized修饰符
格式︰
public synchronized void method( ){
      可能会改生线程安全问题的代码
}
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

/*定义一个同步方法
同步方法也会把方法内部的代码锁住只让一个线程执行
同步方法的锁对象就是实现类对象 new RunnableimpL(),也是就是this
 */
public class safe implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while(true){
            payTacket();
        }
    }
    public synchronized void payTacket(){
        if(ticket>0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
            ticket--;
        }
    }
}
//静态同步方法
public class safe implements Runnable{
    private static int ticket=100;
    @Override
    public void run() {
        while(true){
            payTacket();
        }
    }
    public static synchronized void payTacket(){
        if(ticket>0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
            ticket--;
        }
    }
}

3.锁机制

使用Lock锁:java.util.concurrent.Locks.Lock接口
Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。

Lock接口中的方法:
      void lock()获取锁。
      void unLock( )释放锁。
java.util.concurrent.Locks.ReentrantLock implements Lock接口

使用步骤:
      1.在成员位置创建一个ReentrantLock对象
      2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
      3.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁

public class safe implements Runnable{
    private int ticket=100;
    Lock l=new ReentrantLock();
    @Override
    public void run() {
        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();//无论程序是否异常,都会释放锁
                }
            }
        }
    }
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值