首页 > 编程语言 >Java多线程​(三)线程安全:同步及锁

Java多线程​(三)线程安全:同步及锁

时间:2023-12-24 11:00:54浏览次数:62  
标签:Java Thread 张票 ticketCount 线程 new 多线程 窗口

线程安全问题

考虑如下情景:

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

public class TicketSeller extends Thread{

 // 定义票的数量
 static int ticket = 0; // 取值范围: 0~99

 @Override
 public void run() {
 while (true){
 if (ticket < 100){
 try {
 // 创建延时效果
 Thread.sleep(100);
 } catch (InterruptedException e) {
 throw new RuntimeException(e);
 }
 ticket++;
 System.out.println(getName() + ":正在卖第" + ticket + "张票!");
 }else {
 break;
 }
 }
 }
}


public class TicketWindows {
 public static void main(String[] args) {
 // 创建线程对象
 TicketSeller ts1 = new TicketSeller();
 TicketSeller ts2 = new TicketSeller();
 TicketSeller ts3 = new TicketSeller();
 // 命名线程
 ts1.setName("窗口1");
 ts2.setName("窗口2");
 ts3.setName("窗口3");
 // 开启线程
 ts1.start();
 ts2.start();
 ts3.start();
 }
}

一次运行结果如下:

窗口1:正在卖第2张票!
窗口2:正在卖第2张票!
窗口3:正在卖第2张票!
窗口1:正在卖第3张票!
窗口2:正在卖第3张票!
窗口3:正在卖第3张票!
。。。。。。
窗口2:正在卖第96张票!
窗口1:正在卖第97张票!
窗口3:正在卖第98张票!
窗口2:正在卖第99张票!
窗口1:正在卖第100张票!
窗口3:正在卖第101张票!
窗口2:正在卖第102张票!

我们发现,这个程序并不能达到预期的效果,

①相同的票出现了多次

②出现了超出范围的票

原因是线程执行有随机性。

同步代码块

解决的方式之一是使用同步代码块,即把操作共享数据的代码锁起来。格式如下:

synchronized (锁){
 操作共享数据的代码
}

特点1:锁默认打开,有一个线程进去了,锁自动关闭。

特点2:里面的代码全部执行完毕,线程出来,锁自动打开。

利用同步代码块改写后:

public class MyThreadSync extends Thread{
 // 表示这个类所有的对象都共享ticketCount数据
 static int ticketCount = 0; // 0-99
 // 锁对象,可以是任意的对象,但是必须要是唯一的
 static Object obj = new Object();

 @Override
 public void run() {
 while (true){
 // 同步代码块
 synchronized (obj){
 if (ticketCount < 100){
 try {
 Thread.sleep(50);
 } catch (InterruptedException e) {
 throw new RuntimeException(e);
 }
 ticketCount++;
 System.out.println(getName() + "正在卖第" + ticketCount + "张票!");
 }else {
 break;
 }
 }
 }
 }
}

这里要注意2点,第一是synchronized代码块要写在while(true)循环的里面,否则这100张票就只能由一个线程卖完为止。第二个注意点是,锁对象要是唯一,这里的对象是obj,需要使用static关键字修饰,在实际开发中一般写当前类的class对象名,如本例中可以写成MyThreadSync.class。


同步方法

同步方法,就是把synchronized关键字加到方法上。格式:

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

  • 特点1:同步方法是锁住方法里面所有的代码
  • 特点2:锁对象不能自己指定。如果当前方法是非静态的,那么锁对象就是this;如果是静态方法,那么锁对象就是当前类的字节码文件对象
public class MyThreadSyncFunc implements Runnable {

 // 表示电影票的数量
 // 注意这里与前面的继承Thread类的方式不一样
 // 这里的ticketCount不需要被static修饰
 // 因为实现Runnable接口的对象只会创建一个,
 // 然后作为参数传给Thread对象
 int ticketCount = 0;

 @Override
 public void run() {


// 4.判断共享数据是否到了末尾,如果没有到末尾


 //1.循环
 while (true) {
 //2.同步代码块(同步方法)
 if (method())
 break;
 }
 }

 private synchronized boolean method(){
 //3.判断共享数据是否到达末尾,则退出
 if (ticketCount == 100) {
 return true;
 } else {
 try {
 // 睡眠10毫秒
 Thread.sleep(10);
 } catch (InterruptedException e) {
 throw new RuntimeException(e);
 }
 ticketCount++;
 System.out.println(Thread.currentThread().getName() + "正在卖第" + ticketCount + "张票!");
 }
 return false;
 }
}

使用代码:

public class MyThreadSyncFuncTest {
 public static void main(String[] args) {
 MyThreadSyncFunc mtsf = new MyThreadSyncFunc();

 // 创建线程对象
 Thread t1 = new Thread(mtsf);
 Thread t2 = new Thread(mtsf);
 Thread t3 = new Thread(mtsf);

 // 线程命名
 t1.setName("窗口1");
 t2.setName("窗口2");
 t3.setName("窗口3");

 // 启动线程
 t1.start();
 t2.start();
 t3.start();
 }
}

补充知识点:StringBuilder与StringBuffer是两个相似的类,前者不是线程安全的,而StringBuffer是线程安全的。

Lock锁

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

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作Lock中提供了获得锁和释放锁的方法:

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

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

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

手动上锁/解锁的线程实现类:

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

public class MyThreadLock extends Thread {
 // 表示电影票的数量
 static int ticketCount = 0;
 // 锁,必须共享一个锁,所以要用static修饰
 static Lock lock = new ReentrantLock();

 @Override
 public void run() {
 // 1.循环
 while (true) {
 // 上锁
 lock.lock();
 try {
 if (ticketCount == 100) {
 break;
 } else {
 Thread.sleep(10);
 ticketCount++;
 System.out.println(Thread.currentThread().getName() + "在卖第" + ticketCount + "张票!");
 }
 } catch (InterruptedException e) {
 throw new RuntimeException(e);
 } finally {
 // 解锁
 lock.unlock();
 }
 }
 }
}

使用该线程:

public class MyThreadLockTest {
 public static void main(String[] args) {
 // 创建线程对象
 MyThreadLock mtl1 = new MyThreadLock();
 MyThreadLock mtl2 = new MyThreadLock();
 MyThreadLock mtl3 = new MyThreadLock();
 // 线程命名
 mtl1.setName("窗口1");
 mtl2.setName("窗口2");
 mtl3.setName("窗口3");
 // 线程启动
 mtl1.start();
 mtl2.start();
 mtl3.start();
 }
}

解锁的代码放在finally语句块中,表示无论如何都要执行解锁操作。


死锁

死锁是一种编程错误,要避免程序陷入死锁的局面从而让程序卡死。在编程实践中要避免锁嵌套。

多线程编程的一般套路

  1. 循环
  2. 同步代码块
  3. 判断共享数据是否到了末尾(到了末尾)
  4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)



标签:Java,Thread,张票,ticketCount,线程,new,多线程,窗口
From: https://blog.51cto.com/tangxiaohu/8954723

相关文章

  • java 数组想等
    实现"Java数组相等"作为一名经验丰富的开发者,我非常乐意教你如何实现"Java数组相等"的功能。在本文中,我将向你展示整个过程,并逐步指导你完成每一步所需的代码。流程概述下面是实现"Java数组相等"功能的整体流程:创建两个数组。检查两个数组的长度是否相等。逐个比较两个数组......
  • java 判断字符串a中包好几个字符串b
    Java判断字符串a中是否包含字符串b在Java编程中,我们经常需要判断一个字符串是否包含另一个字符串。这种需求在很多实际场景中都会遇到,比如搜索功能、数据过滤等。本文将介绍如何使用Java判断一个字符串中是否包含多个子字符串,并给出相关代码示例。方案一:使用String类的contains方......
  • java 判断一个值是否为null
    Java判断一个值是否为null作为一名经验丰富的开发者,我会在本文中教你如何在Java中判断一个值是否为null。首先,我将为你展示整个流程,并使用表格形式展示每个步骤。然后,我将详细说明每个步骤需要做什么,并提供相应的代码示例,并对代码进行注释解释。流程概述以下是判断一个值是否为......
  • java 判断一个集合是否包含
    Java判断一个集合是否包含1.整体流程下面是判断一个集合是否包含某个元素的整体流程:步骤描述步骤一创建一个集合对象步骤二向集合中添加元素步骤三判断集合是否包含指定的元素步骤四根据判断结果输出对应的信息2.详细步骤2.1步骤一:创建一个集合......
  • java 判断文件是否是视频
    Java判断文件是否是视频引言在开发中,我们经常需要判断一个文件是否是视频文件,这对于文件管理和处理来说非常重要。本文将指导你如何使用Java来实现判断文件是否是视频的功能。流程概述下面是整个流程的步骤概览:pietitle文件是否是视频"1.获取文件扩展名":20......
  • java 判断图片背景是深色的
    判断图片背景是深色的流程flowchartTDA[获取图片]-->B[将图片转换为灰度图]B-->C[获取灰度图像素点信息]C-->D[统计灰度图中像素点的颜色值]D-->E[根据颜色值判断背景色是深色还是浅色]E-->F[返回判断结果]具体步骤及代码实现1.获取图片首先,我们需要获取一张......
  • java 判断是否为Jason
    判断是否为JSON的实现方法介绍在开发过程中,我们经常会遇到需要判断一个字符串是否为JSON格式的需求。本文将向刚入行的小白介绍如何实现这个功能,通过一系列步骤和代码示例来帮助他理解。整体流程下面是判断字符串是否为JSON的整体流程,我们可以用一个表格来展示步骤:步骤描......
  • java 判断日期是否大于当前日期
    Java判断日期是否大于当前日期引言在Java开发中,经常会遇到需要判断日期是否大于当前日期的情况。本文将介绍如何使用Java来实现这个功能,并给出具体的代码示例。流程概述为了更好地理解整个过程,下面是一个流程图来展示我们将要完成的任务:graphLRA(开始)-->B(输入日期)B--......
  • java 判断某数是否落在区间内
    Java判断某数是否落在区间内本文将介绍如何使用Java编写代码来判断某个数是否在给定的区间内。我们将通过一个简单的示例来说明这个过程。引言在编程中,我们经常需要判断一个数是否在指定的区间内。这个过程对于数据处理、筛选和验证非常重要。在Java中,我们可以使用条件语句和逻......
  • java 判断某个英文字母在26个英文字母的哪个位置
    判断某个英文字母在26个英文字母的哪个位置介绍在Java中,我们可以通过一些简单的操作来判断一个英文字母在26个英文字母中的位置。在本文中,我们将详细介绍如何实现这一功能,并提供相应的代码示例。实现步骤以下是判断某个英文字母在26个英文字母中位置的步骤:将字母转换为小写字......