线程安全问题

线程卖票案例分析

有线程安全问题代码

java 复制代码
public class Ticket implements Runnable {
    //成员变量
    private int ticketCount = 100;
    @Override
    public void run() {
        while (true) {
            if (ticketCount > 0) {
                //模拟出票时间,让当前线程休息100毫秒
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "售出票号:" + ticketCount);
                ticketCount--;//票数减一
            }
            if(ticketCount==0){
                break;//票数为0时退出循环
            }
        }
    }
}


public class TicketDemo {
    public static void main(String[] args) {
        //创建线程任务对象
        Ticket ticket = new Ticket();

        //创建三个买票窗口(三个线程在执行同一个任务)
        Thread t1 = new Thread(ticket, "窗口1 - ");
        Thread t2 = new Thread(ticket, "窗口2 - ");
        Thread t3 = new Thread(ticket, "窗口3 - ");

        t1.start();
        t2.start();
        t3.start();
    }
}

线程安全问题:卖票案例中重复票出现的原因,负数票出现的原因

发生线程安全问题的原因: 多个线程对同一个数据,进行读写操作,造成数据错乱


线程安全问题的解决

基本思想:

让共享数据存在安全的环境中 , 当某一个线程访问共享数据时其他线程是无法操作的

怎么实现呢?

  • 把多条线程操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
  • java语言基于线程安全问题,提供了:同步机制

线程的同步

java允许多线程并发执行,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证该变量的唯一性和准确性。

三种实现同步的方式:【同步机制之所以能够实现,是因为其加了同步锁】

  1. 同步代码块

  2. 同步方法

  3. 锁机制。Lock锁

1. 同步代码块

格式:

java 复制代码
synchronized(任意对象) { 
               多条语句操作共享数据的代码 
       }
synchronized:关键字,表示同步
任意对象:锁对象

a.默认情况锁是打开的,只要有一个线程进去执行代码了,锁就会关闭
b.当线程执行完出来了,锁才会自动打开
c.锁对象可以是任意对象 , 但是多个线程必须使用同一把锁
解决上面的售票安全问题:将对共享数据操作的代码都放到同步代码块中
java 复制代码
public class Ticket implements Runnable {
    //成员变量
    private int ticketCount = 100;
    @Override
    public void run() {
        while (true) {
            //使用同步代码块解决,线程安全问题
//            this:锁对象,是任意类型的对象,也可以是"锁对象"在括号中
            synchronized (this) {
                if (ticketCount > 0) {
                    //模拟出票时间,让当前线程休息100毫秒
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "售出票号:" + ticketCount);
                    ticketCount--;//票数减一
                }
                if (ticketCount == 0) {
                    break;//票数为0时退出循环
                }
            }
        }
    }
}

public class TicketDemo {
    public static void main(String[] args) {
        //创建线程任务对象
        Ticket ticket = new Ticket();

        //创建三个买票窗口(三个线程在执行同一个任务)
        Thread t1 = new Thread(ticket, "窗口1 - ");
        Thread t2 = new Thread(ticket, "窗口2 - ");
        Thread t3 = new Thread(ticket, "窗口3 - ");

        t1.start();
        t2.start();
        t3.start();
    }
}
同步的好处和弊端

好处:解决了多线程的数据安全问题

弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

2. 同步方法

同步方法:就是把synchronized关键字加到方法上,保证线程执行该方法的时候,其他线程只能在方法外等着【该方法多个线程并发访问时只能唯一访问】

格式:

java 复制代码
格式:
       修饰符 synchronized 返回值类型 方法名(方法参数) {    }

 同步方法也有对象锁,不需要我们自己写:
    1. this锁  (当前对象锁)
    2. 类名.class (静态方法上的对象锁)

注意 : 同步方法时不能指定锁对象的 , 但是有默认存在的锁对象的。

  • 对于非static方法,同步锁就是this。
  • 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。 Class类型的对象
解决上面的售票安全问题:使用同步方法解决问题
java 复制代码
//线程任务类
public class Ticket implements Runnable {
    //成员变量
    private int ticketCount = 100;
    @Override
    public void run() {
        //模拟卖票
        while(true){
            if(ticketCount <= 0){
                break;
            }
            method();
        }
    }
    //使用同步方法解决线程安全问题(方法中的代码都是同步代码)
    public synchronized void method(){
        if(ticketCount > 0){
            //模拟出票时间
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "出售票号:" + ticketCount);
            ticketCount--;
        }
    }
}

public class TicketDemo {
    public static void main(String[] args) {
        //创建线程任务对象
        Ticket ticket = new Ticket();

        //创建三个买票窗口(三个线程在执行同一个任务)
        Thread t1 = new Thread(ticket, "窗口1 - ");
        Thread t2 = new Thread(ticket, "窗口2 - ");
        Thread t3 = new Thread(ticket, "窗口3 - ");

        t1.start();
        t2.start();
        t3.start();
    }
}
同步代码块和同步方法的区别:

同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码

同步代码块可以指定锁对象,同步方法不能指定锁对象

3. 锁机制。Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock中提供了获得锁和释放锁的方法
java 复制代码
void lock():获得锁
void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
java 复制代码
ReentrantLock的构造方法
    ReentrantLock():创建一个ReentrantLock的实例
注意:多个线程使用相同的Lock锁对象,需要多线程操作数据的代码放在lock()和unLock()方法之间。一定要确保unlock最后能够调用
解决上面的售票安全问题:使用Lock锁解决问题
java 复制代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Ticket implements Runnable {
    //成员变量
    private int ticketCount = 100;
    //锁对象
    Lock l = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            //使用Lock锁解决,线程安全问题
            //获取锁
            l.lock();
                if (ticketCount > 0) {
                    //模拟出票时间,让当前线程休息100毫秒
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "售出票号:" + ticketCount);
                    ticketCount--;//票数减一
                }
                if (ticketCount == 0) {
                    break;//票数为0时退出循环
                }
            //释放锁
            l.unlock();
        }
    }
}

public class TicketDemo {
    public static void main(String[] args) {
        //创建线程任务对象
        Ticket ticket = new Ticket();

        //创建三个买票窗口(三个线程在执行同一个任务)
        Thread t1 = new Thread(ticket, "窗口1 - ");
        Thread t2 = new Thread(ticket, "窗口2 - ");
        Thread t3 = new Thread(ticket, "窗口3 - ");

        t1.start();
        t2.start();
        t3.start();
    }
}
使用Lock锁实现同步安全的步骤

1.创建Lock锁对象

2.调用lock()方法获得锁

3.调用unLock()释放锁

相关推荐
CC.GG1 小时前
【C++】C++11----智能指针
开发语言·c++
沛沛老爹2 小时前
Web开发者转型AI安全实战:Agent Skills敏感数据脱敏架构设计
java·开发语言·人工智能·安全·rag·skills
曹轲恒2 小时前
Java并发包atomic原子操作类
java·开发语言
cyforkk2 小时前
03、Java 基础硬核复习:流程控制语句的核心逻辑与面试考点
java·开发语言·面试
星火开发设计2 小时前
const 指针与指针 const:分清常量指针与指针常量
开发语言·c++·学习·算法·指针·const·知识
xixixi777772 小时前
RAG越权检索与变形指令/隐写规避常态化:攻击者通过Base64、TokenBreak、字符插入与多轮引导,诱导模型泄露知识库或训练集中的敏感信息
网络·安全·大模型·网络攻击模型·攻击·rag·越权检索
0x532 小时前
JAVA|智能无人机平台(一)
java·开发语言·无人机
雨季6662 小时前
构建 OpenHarmony 文本高亮关键词标记器:用纯字符串操作实现智能标注
开发语言·javascript·flutter·ui·ecmascript·dart
2501_948120152 小时前
Java实现的SSL/TLS协议通信系统
java·开发语言·ssl