理解Java的线程安全问题

news2025/1/22 9:13:38

目录

目录

举例:三个窗口卖票

运行结果:出现重票

如何解决?

方式一:同步代码块,第一个例子

 运行结果:

改进:

 运行结果:

方法一:同步代码块的第二个例子。把锁和ticket都改为静态

运行结果:

方法二:同步方法,第一个例子

 方式二:同步方法第二个例子

 错误示范:show方法的锁是this,this指的是Window4的对象,即t1,t2,t3,3个线程用的不是同一个锁,所以线程不安全

正确示范:把show方法改为静态的,private static synchronized void show(),此时同步监视器(锁)是Window4.class

方式三:Lock锁方式解决线程安全问题

运行结果:

synchronized与Lock的异同?


举例:三个窗口卖票

 class Window1 implements Runnable{

	private int ticket=100;
	@Override
	public void run() {
		while(true) {
			if(ticket>0) {
				
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
				ticket--;
			}else {
				break;
			}
		}
		
	}

}


public class WindowTest1 {

	public static void main(String[] args) {
		Window1 w=new Window1();
		
		Thread t1=new Thread(w);
		Thread t2=new Thread(w);
		Thread t3=new Thread(w);
		
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		
		t1.start();
		t2.start();
		t3.start();
		
		
	}
}

运行结果:出现重票

问题:卖票的过程中,出现了重票、错票---->出现了线程安全问题

问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票

窗口2:卖票,票号为:100
窗口1:卖票,票号为:100
窗口3:卖票,票号为:100
窗口1:卖票,票号为:97
窗口2:卖票,票号为:97
窗口3:卖票,票号为:97

.......
窗口3:卖票,票号为:7
窗口3:卖票,票号为:4
窗口1:卖票,票号为:4
窗口2:卖票,票号为:4
窗口3:卖票,票号为:1
窗口1:卖票,票号为:1
窗口2:卖票,票号为:0

如何解决?

当线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以操作ticket。这种情况即使是线程a出现了阻塞,也不能被改变

在Java中,程序使用同步机制来解决

方式1:同步代码块

synchronized (同步监视器){

        //需要被同步的代码

}

说明:1.操作共享数据的代码,即为需要被同步的代码

2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据

3. 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。

4.多个线程公用同一个锁。

方式2:同步方法

如果操作共享数据的代码完整声明在一个方法中,我们不妨将此方法声明为同步方法。

方式一:同步代码块,第一个例子

同步代码块解决Runnable接口创建的线程的安全问题

 class Window1 implements Runnable{

	private int ticket=100;
	Object obj=new Object();
	
	@Override
	public void run() {
		while(true) {
			
			synchronized(obj) {
			
			if(ticket>0) {
				
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
				ticket--;
			}else {
				break;
			}
			
			}
		}
		
	}

}

public class WindowTest1 {

	public static void main(String[] args) {
		Window1 w=new Window1();
		
		Thread t1=new Thread(w);
		Thread t2=new Thread(w);
		Thread t3=new Thread(w);
		
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		
		t1.start();
		t2.start();
		t3.start();
		
		
	}
}

 运行结果:

没有重票问题。但是,全是窗口3在卖票。没有3个窗口交替出现。理论上讲,没问题。

窗口3:卖票,票号为:100
窗口3:卖票,票号为:99
窗口3:卖票,票号为:98
窗口3:卖票,票号为:97
窗口3:卖票,票号为:96
.......

改进:

把sleep放到synchronized()上面。

class Window1 implements Runnable{

	private int ticket=100;
	Object obj=new Object();
	
	@Override
	public void run() {
		while(true) {
			
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				
				e.printStackTrace();
			}
			
			
			synchronized(obj) {
			
				if(ticket>0) {
					
					
					
					System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
					ticket--;
				}else {
					break;
				}
			
			}
		}
		
	}

}


public class WindowTest1 {

	public static void main(String[] args) {
		Window1 w=new Window1();
		
		Thread t1=new Thread(w);
		Thread t2=new Thread(w);
		Thread t3=new Thread(w);
		
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		
		t1.start();
		t2.start();
		t3.start();
		
		
	}
}

 运行结果:

没有重票问题,3个窗口交替卖票

窗口1:卖票,票号为:100
窗口2:卖票,票号为:99
窗口3:卖票,票号为:98
窗口1:卖票,票号为:97
窗口2:卖票,票号为:96
窗口3:卖票,票号为:95
......

窗口2:卖票,票号为:9
窗口1:卖票,票号为:8
窗口3:卖票,票号为:7
窗口1:卖票,票号为:6
窗口2:卖票,票号为:5
窗口2:卖票,票号为:4
窗口1:卖票,票号为:3
窗口3:卖票,票号为:2
窗口1:卖票,票号为:1

方法一:同步代码块的第二个例子。把锁和ticket都改为静态

因为Window2是线程类,3个线程要共用同一种ticket和锁

同步代码块处理继承Thread类的线程安全问题

锁可以用synchronized(Window2.class),Window2.class只会加载一次,类也是对象

class Window2 extends Thread{

	private static int ticket=100;
	
//	静态锁,确保所有线程公用一个锁
	private static Object obj=new Object();
	@Override
	public void run() {
		while(true) {
			synchronized (obj) {
				if(ticket>0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					
					System.out.println(getName()+"卖票,票号为:"+ticket);
					ticket--;
				}
				else {
					break;
				}
			}
		}
	}
}


public class WindowTest2 {

	public static void main(String[] args) {
		Window2 t1=new Window2();
		Window2 t2=new Window2();
		Window2 t3=new Window2();
		
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

运行结果:

窗口2卖票,票号为:100
窗口2卖票,票号为:99
窗口2卖票,票号为:98
窗口2卖票,票号为:97
窗口2卖票,票号为:96

....................

窗口3卖票,票号为:6
窗口3卖票,票号为:5
窗口3卖票,票号为:4
窗口3卖票,票号为:3
窗口3卖票,票号为:2
窗口3卖票,票号为:1

方法二:同步方法,第一个例子

同步方法解决Runnable接口创建的线程的安全问题

class Window3 implements Runnable{

	private   int ticket=100;

	@Override
	public void run() {
			while (true) {
				show();
				if(ticket<=0)  break;
			}		
			
		
	}
	
	
	//同步方法同步监视器是this
//	在本例子中,this指的是当前类的对象,即wtc
	private  synchronized void show() {

			if(ticket>0) {
				
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+" ticket:"+ticket);
				--ticket;
			}

	}
}


public class WindowTest3 {

	public static void main(String[] args) {
		Window3 wtc=new Window3();
		Thread th1=new Thread(wtc);
		Thread th2=new Thread(wtc);
		Thread th3=new Thread(wtc);
		
		th1.setName("窗口1");
		th2.setName("窗口2");
		th3.setName("窗口3");
		
		th1.start();
		th2.start();
		th3.start();
	}
}

 方式二:同步方法第二个例子

解决继承Thread类创建的线程的线程安全问题

 错误示范:show方法的锁是this,this指的是Window4的对象,即t1,t2,t3,3个线程用的不是同一个锁,所以线程不安全

class Window4 extends Thread{

	private static int ticket=100;
	

	@Override
	public void run() {
		while(true) {
			show();
			if(ticket<=0) break;
		}
	}


	/**
	 * 线程不安全
	 * private synchronized void show()表示同步监视器(锁)是this
	 * 这里的this指的是3个Window4的对象,即t1,t2,t3
	 */
	private synchronized void show() {
		if(ticket>0) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			System.out.println(getName()+"卖票,票号为:"+ticket);
			ticket--;
		}
	}
}

public class WindowTest4 {

	public static void main(String[] args) {
//		window4是线程类
		Window4 t1=new Window4();
		Window4 t2=new Window4();
		Window4 t3=new Window4();
		
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

正确示范:把show方法改为静态的,private static synchronized void show(),此时同步监视器(锁)是Window4.class

class Window4 extends Thread{

	private static int ticket=100;
	

	@Override
	public void run() {
		while(true) {
			show();
			if(ticket<=0) break;
		}
	}


	/**
	 * 线程安全
	 * private staic synchronized void show()表示同步监视器(锁)是Window4.class
	 * Window4.class是唯一的
	 */
	private static synchronized void show() {
		if(ticket>0) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
			ticket--;
		}
	}
}


public class WindowTest4 {

	public static void main(String[] args) {
//		window4是线程类
		Window4 t1=new Window4();
		Window4 t2=new Window4();
		Window4 t3=new Window4();
		
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

同步方法总结:

1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明

2.非静态的同步方法,同步监视器是this

        静态方法的同步方法,同步监视器是:当前类本身,即xxx.class

方式三:Lock锁方式解决线程安全问题

JDK5.0新增

class Window5 implements Runnable{

	private int ticket=100;
	//true是公平锁,个进程排队获得锁,释放锁。
	private ReentrantLock  lock=new ReentrantLock(true);
	@Override
	public void run() {
		while (true) {
//			调用锁定方法
			lock.lock();
			try {
				
				if(ticket>0) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+":"+ticket);
					
					--ticket;				
				}else {
					break;
				}
			} finally {
//				调用解锁方法
				lock.unlock();
			}
			
		}
		
	}
	
}


public class WindowTest5 {

	public static void main(String[] args) {
		Window5 win=new Window5();
		
//		3个线程公用1个win对象
		Thread th1=new Thread(win);
		Thread th2=new Thread(win);
		Thread th3=new Thread(win);
		
		th1.setName("窗口1");
		th2.setName("窗口2");
		th3.setName("窗口3");
		th1.start();
		th2.start();
		th3.start();
	}
}

运行结果:

窗口1:100
窗口2:99
窗口3:98
窗口1:97
窗口2:96
窗口3:95
窗口1:94

................

窗口1:7
窗口2:6
窗口3:5
窗口1:4
窗口2:3
窗口3:2
窗口1:1

synchronized与Lock的异同?

相同:二者都可以解决线程安全问题

不同 :synchronized机制在执行完相应的同步代码后,自动的释放同步监视器。Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock() )

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/151680.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用opencv进行脸部识别

1.读取人脸部图片 引入需要的库&#xff0c;并读取人脸的图片 import cv2 import matplotlib.pyplot as plt import numpy as np# 定义t2s函数&#xff0c;方便查看是否对图片进行RGB通道转换 def t2s(img):return cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 读取图片 img cv2.…

10个PMP冷知识,项目经理看完都说长见识

早上好&#xff0c;我是老原。每次聊起考证啊&#xff0c;我内心总是充满遗憾——毕竟现在的市场情况&#xff0c;让很多项目经理对考证都不看好。但是啊&#xff0c;这群吹证书无用论的小友&#xff0c;总让我想起前几年吹学习无用论的时候&#xff0c;多么的异曲同工&#xf…

PageHelper分页规则

1.问题 1.1.PageHelper先开启分页&#xff0c;后对list数据操作 Overridepublic PageInfo<HdQueryVo> getRecordsByView(int pageNo, int pageSize) {PageHelper.startPage(pageNo,pageSize);List<HdQueryVo> hdQueryVosByView actionMapper.getActionByView();…

TP6(thinkphp6)队列与延时队列

安装 在此我就不再略过TP6的项目创建过程了&#xff0c;大致就是安装composer工具&#xff0c;安装成功以后&#xff0c;再使用composer去创建项目即可。 think-queue 安装 composer require topthink/think-queue 项目中添加驱动配置 我们需要在安装好的config下找到 queu…

SAP工作流任务

前言 灵活工作流沿用的传统工作流的活动节点&#xff0c;只是不再体现在流程图上&#xff0c;工作流的活动根据是否需要代理人可以分为两类&#xff0c;一类是自动后台处理的步骤&#xff0c;另外一类是需要代理人处理的步骤&#xff08;一般为决策节点&#xff09;&#xff0c…

线程通信例子

目录 线程通信的例子&#xff1a;使用两个线程打印1-100。线程1&#xff0c;线程2&#xff0c;交替打印 涉及的3个方法&#xff1a; 运行结果&#xff1a; 把锁改为其他对象 运行结果报错&#xff1a; 改进&#xff0c;把notify和wait的调用者改为obj sleep()和wait()的…

【数据结构】—— Java实现单链表

Java实现单链表一、一链表的概念及结构二、头节点与头指针的异同三、代码实现一、一链表的概念及结构 链表是一种物理存储结构上非连续存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。 注意&#xff1a; 从上图可以看出&#xff0c;链式结构在逻…

MobPush for SDK API

推送监听接口 (addPushReceiver) 描述&#xff1a;添加推送监听&#xff0c;可监听接收到的自定义消息(透传消息)、通知消息、通知栏点击事件、别名和标签变更操作。 /*** com.mob.pushsdk.MobPush.class* MobPush推送监听接口* param receiver 监听*/ public static void ad…

Databend 开源周报 #74

Databend 是一款强大的云数仓。专为弹性和高效设计&#xff0c;自由且开源。 即刻体验云服务&#xff1a;https://app.databend.com。 What’s New 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 Features & Improvements Meta watch client …

【Word2021交叉引用参考文献格式】

Word2021交叉引用参考文献格式1 格式分类2 格式设置方法2.1 引用单个参考文献2.2 引用连续两个参考文献2.3 引用连续三个及三个以上的参考文献1 格式分类 按照引用参考文献的数量&#xff0c;格式一般分为三种&#xff1a; 1、引用单个参考文献&#xff0c;样例&#xff1a;我…

Leetcode回溯法题解

第一题 17. 电话号码的字母组合 题目描述&#xff1a;给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按任意顺序返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例1&#xff1a;…

Linux yum 使用手册,以及内网源搭建

Linux yum 内网源搭建 序 在 Linux 中&#xff0c;我们经常安装各个开源网站编写的代码、软件&#xff0c;在安装的时候各个软件之间有各种依赖关系&#xff0c;还可能出现版本问题&#xff0c;比如 Centos7 支持的软件&#xff0c; Centos6 就未必支持。如果是 Java 开发应该…

ArcGIS基础实验操作100例--实验73创建闭合线内部缓冲区

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验73 创建闭合线内部缓冲区 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff0…

K8S ReplicaSet 原理 示例 HPA扩容

K8S ReplicaSet 基本使用 K8S ReplicaSet 对象的作用是在任意时间点保持一组稳定的副本Pod运行&#xff0c;因此&#xff0c;它通常用于保证指定数量的相同Pod的可用性。乍一看&#xff0c; ReplicaSet 对象的定义跟 Replication Controller 并没有什么区别&#xff0c;都是维…

AI检测人员工衣工服着装不规范识别系统 yolo

AI检测人员工衣工服着装不规范识别系统基于opencvyolo网络深度学习模型对现场画面中人员着装穿戴实时监测分析。我们使用YOLO(你只看一次)算法进行对象检测。YOLO是一个聪明的卷积神经网络(CNN)&#xff0c;用于实时进行目标检测。该算法将单个神经网络应用于完整的图像&#x…

Vivado综合属性之SRL_STYLE

本文介绍综合属性SRL_STYLE取register、srl、srl_reg、reg_srl、reg_srl_reg和block中的值时&#xff0c;对Schematic的影响。 SRL_STYLE用于指导Vivado将SRL&#xff08;移位寄存器&#xff09;映射为何种形式。 目录 默认值 测试代码 原理图 SRL_STYLE配置为register …

【JavaSE】String类

目录 前言&#xff1a; 1.1、了解字符串构造方法 1.2、求字符串长度&#xff08;xxx.length()&#xff09; 1.3、isEmpty()方法 1.4、String对象的比较 1.4.1、通过&#xff08;str1 str2&#xff09;来比较&#xff0c;这样比较的是两个引用当中的地址 1.4.2、 boolea…

【ONE·C++ || vector (一)】

总言 学习笔记&#xff0c;慢慢补充。 文章目录总言1、整体介绍&#xff1a;2、常用各种接口介绍2.1、vector的基本结构&#xff1a;构造、析构、赋值2.1.1、总体情况预览2.1.2、各项函数使用演示2.2、vector增删查改相关2.2.1、增删查改总览2.2.2、如何在vector中插入、删除、…

Kotlin之使用协程编写高效的并发程序

文章目录1.协程的基本用法2.更多的作用域构建器3.使用协程简化回调的写法协程属于Kotlin中非常有特色的一项技术&#xff0c;因为大部分编程语言中是没有协程这个概念的。那么什么是协程呢&#xff1f;它其实和线程有点相似&#xff0c;可以简单地将它理解成一种轻量级的线程。…

Linux学习入门

1、Linux简介 操作系统分类&#xff1a;桌面操作系统、、服务器操作系统、移动端操作系统、嵌入式操作系统桌面操作系统主要针对个人电脑&#xff0c;Linux在桌面操作系统的应用主要有国产操作系统&#xff0c;Ubuntu。服务器操作系统有windows和linux以及苹果&#xff0c;主流…