java线程安全--快速入门

直接上案例

模拟多线程抢票

public class Site implements Runnable{

    private  int count = 10;  //记录剩余票数
    private  int num = 0;   //记录买到第几张票

    @SneakyThrows
    public void run() {
        while (true) {
            //没有余票时,跳出循环
            if (count <= 0) {
                break;
            }
            //第一步:修改数据
            num++;
            count--;
            TimeUnit.SECONDS.sleep( 1);// 模拟网络延时
            //第二步:显示信息
            System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余" + count + "张票!");
        }
    }
    //快速测试
    public static void main(String[] args) {
        Site site = new Site();
        Thread person1 = new Thread(site, "学明");
        Thread person2 = new Thread(site, "二明");
        Thread person3 = new Thread(site, "黄牛");
        System.out.println("********开始抢票********");
        person1.start();
        person2.start();
        person3.start();
    }
}

咱们可以看到 “学明” 明显抢不过 “黄牛” ,肯定是回不去家的!!!

 

  • 不是从第1张票开始

  • 存在多人抢到一张票的情况

  • 有些票号没有被抢到  

多个线程操作同一共享资源时,将引发数据不安全问题,线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。 

解决 

使用线程同步机制的使用

  1. 同步代码块。

  2. 同步方法。

  3. 锁机制。

1.同步代码块

synchronized表示同步,可以修饰代码块,也可以修饰方法。如果synchronized修饰代码块,那么这个代码块就是同步代码块。

语法:

synchronized (锁对象) {
            ...
}

 说明:

  • 锁对象就是一个普通的Java对象,锁对象可以是任何类型的,可以是Student,Object, ArrayList

  • 锁对象仅仅起到一个标记的作用,除此之外,就没有其他含义了。

  • 同步代码块的作用:

  • 只有持有锁的线程才能够进入到同步代码块。

  • 线程同步可以解决线程安全问题,会牺牲效率

public class Site implements Runnable {

    private int count = 10;  //记录剩余票数
    private int num = 0;   //记录买到第几张票
    //创建对象,当做锁对象,该对象可以是任何类型的,该对象仅仅起到一个标记作用。
    //锁对象一定要是唯一的。 多个线程使用的锁必须是同一个锁对象,否则也不能保证线程安全。
    Object lock = new Object();

    //在run方法中编写线程要执行的任务,线程要执行的任务是买票任务。
    @SneakyThrows
    public void run() {
        //定义死循环模拟售票窗口一直卖票的过程
        while (true) {
            //同步代码块
            //当线程执行到同步代码块时会看一下同步代码块上面有没有锁。
            //如果同步代码块上面有锁,那么线程会获取到锁,然后进入到同步代码块中。
            //如果同步代码块上面没有锁,那么线程会在同步代码块位置一直等着获取锁
            synchronized (lock) {
                // 没有余票时,跳出循环
                if (count <= 0) {
                    break;
                }
                // 第一步:修改数据
                num++;
                count--;
                TimeUnit.SECONDS.sleep( 1);// 模拟网络延时
                // 第二步:显示信息
                System.out.println(Thread.currentThread().getName() + "抢到第"
                        + num + "张票,剩余" + count + "张票!");
            }
        }
        //当线程离开同步代码块,线程会释放掉自己的锁。这样线程就又可以去竞争这个锁了,哪个线程能抢到,哪个线程去执行。
    }

    public static void main(String[] args) {
        Site site = new Site();
        Thread person1 = new Thread(site, "学明");
        Thread person2 = new Thread(site, "二明");
        Thread person3 = new Thread(site, "黄牛");
        System.out.println("********开始抢票********");
        person1.start();
        person2.start();
        person3.start();
    }
}

 学明运气不佳,还是抢不过黄牛,荒废了单身18年的手速,还是回不去家!!!

2.同步方法

如果synchronized修饰方法,那么该方法就是同步方法,同步方法同样可以解决线程安全问题。

同步方法格式

修饰符 synchronized 返回值类型 方法名(参数列表) {

    方法体;

    return 返回值;

}

优缺点

同步代码块:

优点:灵活,可以对任意代码进行同步。

缺点:语法不如同步方法简洁

同步方法:

优点:语法简洁。

缺点:不如同步代码块灵活。 是直接将整个的方法体都加了同步。  

public class Site implements Runnable {

    private int count = 10;  //记录剩余票数
    private int num = 0;   //记录买到第几张票

    public void run() {
        while (true) {
            if (!sale()) {
                break;
            }
        }
    }

    //定义同步方法,用来卖票
    //同步方法,相当于将整个的方法体都加了同步代码块。
    // 同步方法:卖票
    @SneakyThrows
    public synchronized boolean sale() {
        if (count <= 0) {
            return false;
        }
        // 第一步:修改数据
        num++;
        count--;
        TimeUnit.SECONDS.sleep( 1);// 模拟网络延时
        // 第二步:显示信息
        System.out.println(Thread.currentThread().getName() + "抢到第" + num
                + "张票,剩余" + count + "张票!");
        if (Thread.currentThread().getName().equals("黄牛")) {
            return false;
        }
        return true;

    }

    public static void main(String[] args) {
        Site site = new Site();
        Thread person1 = new Thread(site, "学明");
        Thread person2 = new Thread(site, "二明");
        Thread person3 = new Thread(site, "黄牛");
        System.out.println("********开始抢票********");
        person1.start();
        person2.start();
        person3.start();
    }
}

学明离家18年,已经泪流满面,为了让学明快点回家,十年都不用再买票了;

3.锁机制 

ReentrantLock :它是独占锁,是指只能被独自占领,即同一个时间点只能被一个线程锁获取到的锁。

ReentrantReadWriteLock :它包括子类ReadLock和WriteLock。ReadLock是共享锁,而WriteLock是独占锁。

LockSupport :它具备阻塞线程和解除阻塞线程的功能,并且不会引发死锁。

Lock是一个接口,如果要用,需要使用实现类,最常用的实现类是ReentrantLock

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

/*
使用锁实现多线程
 */
public class Site implements Runnable {

    private int count = 10;  //记录剩余票数
    private int num = 0;   //记录买到第几张票
    Lock lock = new ReentrantLock();

    @SneakyThrows
    public void run() {
        //定义死循环模拟售票窗口一直卖票的过程
        while (true) {
            lock.lock();//获取锁
            // 没有余票时,跳出循环
            if (count <= 0) {
                lock.unlock();
                break;
            }// 第一步:修改数据
            num++;
            count--;
            TimeUnit.SECONDS.sleep( 1);// 模拟网络延时
            // 第二步:显示信息
            System.out.println(Thread.currentThread().getName() + "抢到第"
                    + num + "张票,剩余" + count + "张票!");
            lock.unlock();//释放锁
        }
}

    public static void main(String[] args) {
        Site site = new Site();
        Thread person1 = new Thread(site, "学明");
        Thread person2 = new Thread(site, "二明");
        Thread person3 = new Thread(site, "黄牛");
        System.out.println("********开始抢票********");
        person1.start();
        person2.start();
        person3.start();
    }
}

CAS 

除了加锁以外,还有无锁的方式,就比如CAS

CAS(Compare and Swap)操作是一种并发编程中常用的原子操作,用于实现多线程环境下的无锁同步。CAS操作可以实现对共享变量的原子性读取和更新。

库存举例 

@Component
public class Inventory {
    private AtomicInteger count;

    public Inventory() {
        this.count = new AtomicInteger(10); // 初始化库存数量,实际开发中,可以从数据库或缓存获取数来源
    }

    public int getCount() {
        return count.get();
    }

    public boolean updateCount(int newValue) {
        // 获取余额的最新值
        int prev = count.get();
        if (prev <= 0) {
            return false;
        }
        // 要修改的余额
        int next = prev - newValue;
        if (next < 0) {
            return false;
        }
        return count.compareAndSet(prev, next);
    }
}
@RestController
public class testController {
    @Resource
    private Inventory inventory;

    private static Logger logger = Logger.getLogger(testController.class.getName());

    @RequestMapping(value = "testCAS", method = {RequestMethod.POST})
    public void simulateInventoryUpdate(@RequestParam Integer newValue) {
        logger.info("传入数量:" + newValue);
        // 获取库存数量
        int currentCount = inventory.getCount();
        logger.info("当前库存数量:" + currentCount);

        // 模拟更新库存
        boolean success = inventory.updateCount(newValue);
        if (success) {
            logger.info("库存更新成功");
        } else {
            logger.error("库存更新失败");
        }
        // 更新后的库存数量
        int updatedCount = inventory.getCount();
        logger.info("更新后的库存数量:" + updatedCount);
    }
}

 成功

失败 

 三克油

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值