Java----线程同步

Java----线程同步

案例:卖票

要求:某电影院正在上映国产大片,它有100张票,而该电影院有三个窗口卖票,请设计一个程序模拟该电影院卖票

思路:

① 定义一个类SellTicket来实现Runnable接口,里面定义个变量

② 重写Run()方法,步骤如下:

​ A:若票数大于0,就卖票,并说明是哪个窗口在卖

​ B:卖了票后,票数要减一

​ C:没票了,但肯定还有人来问,所以设置一个死循环

③ 定义一个测试类SellTicketDemo,里面有main()方法,步骤如下:

​ A:创建SellTicket对象

​ B:创建三个Thread对象,将SellTicket对象作为构造方法的参数,并给出名称

​ C:启动线程

卖票出现的问题:

  • 相同票出现了多次
  • 出现了负数的票

问题的原因:

  • 线程的随机性导致

卖票案例数据安全问题的解决

为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准)

  • 是否是多线程环境
  • 是否有共享数据
  • 是否有多条语句操作共享数据(切入点)

(如果这三条都是的话,那么肯定出现数据安全性问题)

如何解决多线程数据安全问题?

  • 基本思想:让程序没有安全问题的环境(破坏掉上面三个条件中的一个)

如何实现?

  • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
  • 利用java提供的同步代码块解决

同步代码块:

锁住多条语句(线程)操作共享数据,可以使用同步代码块来实现

  • 格式:

​ synchronized(任意对象){

​ 多条语句(线程)操作共享数据的代码

}

  • synchronized(任意对象) 相当于给代码块加锁了,任意对象相当于是锁

同步的好处和弊端

  • 好处:解决了多线程数据安全问题
  • 弊端:当线程很多时,因为每个线程都要去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

同步方法

同步普通方法:

概念:就是把synchronized关键字加到方法上

  • 格式:
    修饰符 synchronized 返回值类型 方法名(方法参数)

同步普通方法的锁对象是什么呢?

  • this

同步静态方法:

概念:就是把synchronized关键字加到静态方法上

  • 格式

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

同步静态方法的锁对象是什么呢?

  • 类名.class

线程安全的类

StringBuffer==StringBuilder

Vector==ArrayList

Hashtable<K,V>==Hashmap<K,V>

以上左边三者均为线程安全的类,区别是:

  • 左边可实现线程安全,右边不行
  • 右边在执行相同操作时,更快,因为它们不执行同步

lock锁

由于我们不理解同步代码块和同步方法在哪里上的锁(synchronized)和在哪里释放的锁,所以JDK5新增一个锁对象Lock

Lock比synchronized方法和语句有更多更广泛的锁定操作

Lock中提供了锁和释放锁的方法:

  • void lock( ) :获得锁
  • void unlock( ) :释放锁

Lock是接口,不能直接实例化,这里采用它的实现类ReentrantLock来实例化:

ReentrantLock的构造方法:

  • ReentrantLock():创建一个ReentrantLock的实例

注意:

  • 一般使用try{

    lock.lock();

    要锁的代码块

    }

    finally{

    lock.unlock();

    }

    这个方法来用lock锁

生产者与消费者模式

一个经典的多线程协作的模式。

  • 生产者线程用于生产数据
  • 消费者线程用于消费数据

为了解耦生产者与消费者之间的关系,通常建立一个共享的数据区域,就像是一个仓库

image-20220830210622748

为了体现生产与消费过程中的等待与唤醒,Java提供了几个方法供我们使用:

Object类的等待与唤醒方法:

方法名 作用
void wait() 将导致当前线程等待(执行一次),直到另一个线程调用该对象的notify()或者notifyAll()方法
void notify() 唤醒正在等待对象监视器的单个线程
void notifyAll() 唤醒正在等待对象监视器的所有线程

生产者与消费者案例

生产者与消费者案例包含的类:

  • 奶箱类(Box):定义一个成员变量,表示第X瓶奶,提供存储牛奶和获取牛奶的炒作
  • 生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶操作
  • 消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶操作
  • 测试类(BoxDemo):main()方法:

​ 步骤:

​ ①创建奶箱对象,共用区域

​ ②创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶操作

​ ③创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶操作

​ ④创建两个线程对象,分别把消费者和生产者对象作为构造参数传递

​ ⑤启动线程

代码实现:

posted @   青弦c  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示