Java 线程安全问题

Java 线程安全问题

首先我们引出一个卖票问题,假设一个电影现剩余10张票要卖,这个任务有三个人要抢着做,我们对任务中的票数的操作如何才能实现安全访问。

  • 首先看一下没加任何约束的情况。
package com.kkb.task4_5;

public class Test {
    public static void main(String[] args) {
        //线程不安全
        Runnable runnable = new Ticket();
        new Thread(runnable).start();//创建第一个线程并执行
        new Thread(runnable).start();//创建第二个线程并执行
        new Thread(runnable).start();//创建第三个线程并执行

    }
    static class Ticket implements Runnable{

        private  int count =10;//10张票
        @Override
        public void run() {
            while (count > 0) {
                System.out.println("正在准备卖票");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println("票已出售成功!余票:" + count);
            }
        }
    }
}

这时候的输出结果顺序比较乱,而且count值得改变也不合理。我们拿一种输出情况做解释。

这种情况下我们发现count的值还有-1和-2.我们在代码中是明确加入了while判断语句进行判断的。这种情况下问题出在哪里呢?

解释:当还有一张票剩下时,这时线程1,线程2,线程3同时进行了whlie判断,发现都能进入循环,这时线程1将count设置为了0,并进行了输出打印,二线程2在线程1改变count值的基础上又进行修改和输出,线程3在线程2修改的count值基础上也进行了修改和输出。这时我们发现不加任何约束是不安全的,我们如何能够实现安全的卖票呢?

1.解决方案1,同步代码块

格式:synchroized(锁对象){//任何对象都可以作为锁对象,但是同步的线程要共用一个锁对象

}

我们先创建一个suo类,里面无需声明任何属性。也可以直接Object s= new Object();创建一个

package com.kkb.task4_5;

public class Test {
    public static void main(String[] args) {
        //线程不安全
        Runnable runnable = new Ticket();
        new Thread(runnable).start();//创建第一个线程并执行
        new Thread(runnable).start();//创建第二个线程并执行
        new Thread(runnable).start();//创建第三个线程并执行

    }
    static class Ticket implements Runnable{

        private  int count =10;//10张票
        suo s =new suo();
        //Object object =new Object();
        @Override
        public void run() {

                while (true) {
                    synchronized (s) {//将下面代码锁住,一次只能一个线程操作
                        if (count > 0) {
                            System.out.println("正在准备卖票");
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            count--;
                            System.out.println(Thread.currentThread().getName() + "票已出售成功!余票:" + count);
                        }
                    }
            }
        }
    }
}

输出结果

这时我们发现三个线程是排队进行售票操作的,如果一个线程抢到锁就会将代码块锁住,直到执行完操作后释放锁,再另一个进程抢到锁,以此类推,直到count值为0.

2.线程安全2-同步方法

就是将要锁的代码放到一个方法里面。

package com.kkb.task4_5;

public class Test {
    public static void main(String[] args) {
        //线程不安全
        Runnable runnable = new Ticket();
        new Thread(runnable).start();//创建第一个线程并执行
        new Thread(runnable).start();//创建第二个线程并执行
        new Thread(runnable).start();//创建第三个线程并执行

    }
    static class Ticket implements Runnable{

        private  int count =10;//10张票
        //suo s =new suo();
        //Object object =new Object();
        @Override
        public void run() {

            while (true) {
                Boolean falg = sale();
                if (!falg){
                    break;
                }
            }
        }
        public synchronized Boolean sale() {
                if (count > 0) {
                    System.out.println("正在准备卖票");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName() + "票已出售成功!余票:" + count);
                    return true;
                }
                return false;
            }
    }
}

输出结果

注意:当锁的方法不是static修饰时,锁为对象this,就是上面创建的runnnable,否则为类.class字节码文件对象,就是Tecket.class字节码文件对象。当代码块和方法共用this锁对象时,代码块和方法区一定时间内(线程占用锁的时间)只能被这个拥有锁的线程访问。

3.线程安全3-显示锁Lock 

  • 同步代码块和方法区都是隐示锁

首先通过Lock l = new ReentrantLock();创建一把锁。

在需要同步执行的代码区进行上锁和解锁操作即可。

package com.kkb.task4_5;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockText {
    public static void main(String[] args) {
        Runnable runnable =new Ticket();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();

    }
    static class Ticket implements Runnable{
    private int count =10;
    private Lock l = new ReentrantLock();

        @Override
        public void run() {
            while (true){
                l.lock();//锁上
                if (count>0){

                    System.out.println("开始卖票---");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName()+"售票成功!剩余的票数为:"+count);
                }
                l.unlock();//解锁
            }

        }
    }
}

执行结果

显示锁Lock和隐示锁synchronized的区别:

https://blog.csdn.net/qq_43570075/article/details/106243873

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值