多线程同步问题及解决

多线程安全问题案例

线程在执行的时候,cpu的执行权有可能随时被抢走;

1、所以如果是在多线程的环境下,会出现一些安全的问题。 以下面的需求为例:

某电影院目前正在上映国产大片,共有100张票,而它有3个窗口买票,请设计一个程序模拟该电影院买票。

这里面的安全保证得是:每一张票是由某个窗口卖出去的,而且卖了就没有了。这都得用同步机制才能实现。 如果不用锁机制实现线程同步,上面的安全问题就无法得到保障。

例如下面的代码:

java 复制代码
public class UnsafeTicketSale extends Thread {
    // 共享资源:100张票
    private static int ticket = 0;
​
    @Override
    public void run() {
        while (true) {
            if (ticket < 100) {
                try {
                    // 模拟网络延迟,放大线程安全问题
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket++;
                System.out.println(getName() + " 卖出第 " + ticket + " 张票");
            } else {
                break;
            }
        }
    }
​
    public static void main(String[] args) {
        UnsafeTicketSale window1 = new UnsafeTicketSale();
        UnsafeTicketSale window2 = new UnsafeTicketSale();
        UnsafeTicketSale window3 = new UnsafeTicketSale();
​
        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");
​
        window1.start();
        window2.start();
        window3.start();
    }
}

部分结果是:

复制代码
窗口1 卖出第 2 张票
窗口2 卖出第 2 张票
窗口3 卖出第 3 张票

可以看到,第 2 张票被两个窗口同时卖出,出现了线程安全问题。

使用同步代码块解决

我们可以 使用同步代码块解决线程安全问题 即可以使用一个锁对象 SafeTicketSaleWithBlock.class(类对象),保证三个线程共用同一把锁。 当一个线程进入同步代码块时,其他线程必须等待锁释放,确保 ticket++ 操作的原子性。 从而运行结果中不会出现重复卖票或超卖的情况。

java 复制代码
public class SafeTicketSaleWithBlock extends Thread {
    private static int ticket = 0;
​
    @Override
    public void run() {
        while (true) {
            // 同步代码块:锁对象为类对象,保证所有线程共用同一把锁
            synchronized (SafeTicketSaleWithBlock.class) {
                if (ticket < 100) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket++;
                    System.out.println(getName() + " 卖出第 " + ticket + " 张票");
                } else {
                    break;
                }
            }
        }
    }

使用同步方法解决

同步方法的核心就是在方法声明中加上 synchronized 关键字,它会自动对整个方法体进行加锁。

  1. 非静态同步方法

    java 复制代码
    修饰符 synchronized 返回值类型 方法名(参数列表) {
     // 线程安全的代码
    }
    ​

    锁对象:默认是 this,也就是调用该方法的对象本身。

  2. 静态同步方法

    java 复制代码
    修饰符 static synchronized 返回值类型 方法名(参数列表) {
     // 线程安全的代码
    }

    锁对象:默认是当前类的字节码对象(类名.class),这个对象在全局是唯一的。

解决示例:

java 复制代码
public class SafeTicketSaleWithMethod implements Runnable {
    private int ticket = 0;
​
    // 同步方法:锁对象为 this(当前对象)
    private synchronized void sellTicket() {
        if (ticket < 100) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticket + " 张票");
        }
    }
​
    @Override
    public void run() {
        while (ticket < 100) {
            sellTicket();
        }
    }
​
    public static void main(String[] args) {
        SafeTicketSaleWithMethod task = new SafeTicketSaleWithMethod();
​
        Thread window1 = new Thread(task, "窗口1");
        Thread window2 = new Thread(task, "窗口2");
        Thread window3 = new Thread(task, "窗口3");
​
        window1.start();
        window2.start();
        window3.start();
    }
}

讨论

1、可以看到synchronized (SafeTicketSaleWithBlock.class) 使用的锁对象是字节码文件;这个类可以是任意的类;因为无论哪个类的字节码文件都是唯一的,而我们只需要保证锁这个资源是唯一的就行,无其他要求

2、为什么类的字节码对象(XXX.class)是唯一的?

当一个类被 JVM 加载完成后,会在内存中生成一个对应的「Class 对象」(也就是你说的字节码文件对象),这个对象全局唯一,无论你创建多少个该类的实例,对应的 Class 对象只有一个。

3、 锁的范围

同步方法的锁范围是整个方法体,无法像同步代码块那样精确控制锁的范围。

如果方法体中只有一小部分代码需要同步,使用同步代码块性能更高。

特性 同步方法 同步代码块
语法 简单,只需加 synchronized 关键字 需显式指定锁对象 synchronized(锁对象){...}
锁对象 固定(this类名.class 可以自定义,更灵活
锁范围 整个方法体 可以精确控制到代码块
性能 略低(锁范围大) 更高(锁范围小)
可读性 高(一眼看出是同步方法) 稍低(需看锁对象)

s

相关推荐
wfsm2 小时前
工厂模式创建动态代理实现类
java·开发语言
好好研究2 小时前
总结SSM设置欢迎页的方式
xml·java·后端·mvc
Hui Baby2 小时前
java -jar 启动原理
java·pycharm·jar
weixin_511255212 小时前
更新jar内资源和代码
java·jar
m0_706653232 小时前
模板编译期排序算法
开发语言·c++·算法
历程里程碑2 小时前
Linxu14 进程一
linux·c语言·开发语言·数据结构·c++·笔记·算法
木井巳2 小时前
【递归算法】验证二叉搜索树
java·算法·leetcode·深度优先·剪枝
不当菜虚困2 小时前
windows下HSDB导出class文件报错【java.io.IOException : 系统找不到指定的路径。】
java·开发语言
lsx2024062 小时前
Vue.js 循环语句
开发语言