浅谈多线程安全问题

下面我们还是用看电影卖票的案例来谈一谈多线程安全的问题。

方案一:使用同步代码块

//实现卖票案例
/*
卖票案例出现线程安全问题
解决方案一:使用同步代码块
格式:
        synchronized(锁对象){
            可能会出现线程安全问题的代码(访问了共享数据的代码)
        }
注意:
        1.通过代码块中的锁对象,可以使用任意的对象
        2.但是必须保证多个线程使用的锁对象是同一个
        3.锁对象作用:
                把同步代码块锁住,只让一个线程在同步代码块中执行
 */
/*
总结:同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步
        同步保证了只能有一个线程在同步中执行共享数据,保证了安全
        程序频繁的判断锁,获取锁,释放锁,程序的效率会降低
 */
public class RunnableImpl implements Runnable{
    //定义一个多线程共享的票源
    private int piao = 100;

    //创建一个锁对象
    Object obj = new Object();
    @Override//设置任务买票
    public void run() {
        while (true){//使用死循环让买票操作重复着执行
            //创建同步代码块
            synchronized (obj){
                if(piao>0){//判断票是否存在
                    //提高安全问题的出现概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //票存在,买票,piao--
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+piao+"张票");
                    piao--;
                }
            }
        }
    }
}

测试类:

/*
1.单线程程序是不会出现线程安全问题的
2.多线程程序,没有访问共享数据,不会产生问题
3.多线程访问了共享的数据,会产生线程安全问题
 */
//模拟买票案例,创建三个线程,同时开启,对共享的票进行出售
public class Test01 {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread thread1 = new Thread(run);
        Thread thread2 = new Thread(run);
        Thread thread3 = new Thread(run);
        thread1.start();//开启多线程
        thread2.start();
        thread3.start();
        /*
        出现了线程安全问题,卖票出现了重复的票和不存在的票
        注意:
            线程安全问题是不能产生的,我们可以让一个线程访问共享数据的时候,无论是否失去了cpu的执行权;
            让其他的线程只能等待,等待当前程序卖完票,其他线程在进行卖票
        保证:使用一个线程在卖票
         */
    }
}

方案二:使用同步方法

//实现卖票案例
/*
卖票案例出现了线程安全问题
解决线程安全问题方法二:使用同步方法
        使用步骤:
                1.把访问了共享数据的代码抽取出来,放到一个方法中
                2.在方法上添加synchronized修饰符
        格式:定义方法的格式
        修饰符 synchronized 返回值类型 方法名(参数列表){
                可能会出现线程问题的代码(访问了共享数据的代码)
        }
 */
public class RunnableImpl implements Runnable {
    //定义一个多线程共享的票源
    private int piao = 100;
    @Override//设置任务买票
    public void run() {
        while (true) {//使用死循环让买票操作重复着执行
            method();
        }
    }
/*
同步方法也会把方法内部的代码锁住,只让一个线程执行
    同步方法的锁对象是谁?
        就是实现类对象new RunnableImpl()
        也就是this
 */
    public synchronized void method() {//定义一个同步方法
        if (piao > 0) {//判断票是否存在
            //提高安全问题的出现概率,让程序睡眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //票存在,买票,piao--
            System.out.println(Thread.currentThread().getName() + "-->正在卖第" + piao + "张票");
            piao--;
        }
    }
}

测试类

public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread thread1 = new Thread(run);
        Thread thread2 = new Thread(run);
        Thread thread3 = new Thread(run);
        thread1.start();//开启多线程
        thread2.start();
        thread3.start();
        /*
        出现了线程安全问题,卖票出现了重复的票和不存在的票
        注意:
            线程安全问题是不能产生的,我们可以让一个线程访问共享数据的时候,无论是否失去了cpu的执行权;
            让其他的线程只能等待,等待当前程序卖完票,其他线程在进行卖票
        保证:使用一个线程在卖票
         */
    }
}

当然同步方法也可以使用静态方法,原理差不多,大家可以自行摸索

方案三:使用Lock锁

//实现卖票案例
/*
卖票案例出现了线程安全问题
解决线程安全问题方法三:使用Lock锁
        java.util.concurrent.locks.Lock接口
        Lock实现按提了比使用synchronized方法和 语法可获得的更广泛的锁定操作
        Lock接口中的方法:
                void lock()获取锁
                void unlock()释放锁
        java.util.concurrent.locks.Reentrantlock implements Losk接口

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

 */
public class RunnableImpl implements Runnable {
    //定义一个多线程共享的票源
    private int piao = 100;
    //1.在成员位置创建一个Reentrantlock对象
    Lock lock = new ReentrantLock();

    @Override//设置任务买票
    public void run() {
        while (true) {//使用死循环让买票操作重复着执行
            method();
        }
    }

    public synchronized void method() {//定义一个同步方法
        //2.在可能出现安全问题的代码前调用Lock接口中的方法lock获取锁
        lock.lock();
        if (piao > 0) {//判断票是否存在
            //提高安全问题的出现概率,让程序睡眠
            try {
                Thread.sleep(10);
                //票存在,买票,piao--
                System.out.println(Thread.currentThread().getName() + "-->正在卖第" + piao + "张票");
                piao--;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //3.在可能出现安全问题的代码后调用Lock接口中的方法unlock释放锁
                lock.unlock();//无论是否出现异常,都会把锁释放,可以提高程序的效率
            }
        }
    }
}

测试类

/*
1.单线程程序是不会出现线程安全问题的
2.多线程程序,没有访问共享数据,不会产生问题
3.多线程访问了共享的数据,会产生线程安全问题
 */
//模拟买票案例,创建三个线程,同时开启,对共享的票进行出售
public class Test01 {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread thread1 = new Thread(run);
        Thread thread2 = new Thread(run);
        Thread thread3 = new Thread(run);
        thread1.start();//开启多线程
        thread2.start();
        thread3.start();
        /*
        出现了线程安全问题,卖票出现了重复的票和不存在的票
        注意:
            线程安全问题是不能产生的,我们可以让一个线程访问共享数据的时候,无论是否失去了cpu的执行权;
            让其他的线程只能等待,等待当前程序卖完票,其他线程在进行卖票
        保证:使用一个线程在卖票
         */
    }
}

以上就是我总结出来的三种线程安全问题的解决方案,希望对大家有所帮助,测试结果没有发出来,大家可以自行测试一下,学知识重点还是在于理解,学java就是要多敲敲代码,也许你不会,敲着敲着你就会了。

猜你喜欢

转载自blog.csdn.net/weixin_43908950/article/details/84896766