`

Java 多线程同步问题的探究(三、Lock来了,大家都让开【1. 认识重入锁】)

阅读更多

在上一节中,

我们已经了解了Java多线程编程中常用的关键字synchronized,以及与之相关的对象锁机制。这一节中,让 我们一起来认识JDK 5中新引入的并发框架中的锁机制

我想很多购买了《Java程序员面试宝典》之类图书的朋友一定对下面 这个面试题感到非常熟悉:

问:请对比synchronized与java.util.concurrent.locks.Lock 的异同。
答案:主要相同点:Lock能完成synchronized所实现的所有功能
     主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放 锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。

恩,让我们先鄙视一下应试教育。

言归正传,我们先来看一个多线程程序。它使用多个线程对一个Student对象进行访问,改变其中的变量值。 我们首先用传统的synchronized 机制来实现它:

package sky.cn.test4;

import java.util.Random;

public class ThreadDemo implements Runnable {
	
	Student student = new Student();
	int count = 0;
	
	public void accessStudent() {
		String currentThreadName = Thread.currentThread().getName();
		long startTime = System.currentTimeMillis();
		System.out.println(currentThreadName + " is running!");
		synchronized (this) {		//(1)使用同一个ThreadDemo对象作为同步锁
			System.out.println(currentThreadName + " got lock1@Step1!");
			try {
				count++;
				Thread.sleep(5000);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				System.out.println(currentThreadName + " first Reading count: " + count);
			}
			System.out.println(currentThreadName + " release lock1@Step1!");
		}
		
		synchronized (this) { 		//(2)使用同一个ThreadDemo对象作为同步锁
			System.out.println(currentThreadName + " got lock2@Step2!");
			try {
				Random random = new Random();
				int age = random.nextInt(100);
				System.out.println("thread " + currentThreadName + " set age to: " + age);
				this.student.setAge(age);
				System.out.println("thread " + currentThreadName + " first read age is: " 
              + this.student.getAge());
				Thread.sleep(5000);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				System.out.println("thread " + currentThreadName + " second read age is: "
               + this.student.getAge());
			}
			System.out.println(currentThreadName + " release lock2@step2!");
			long endTime = System.currentTimeMillis();
			System.out.println("thread " + currentThreadName + " cost " 
             + (endTime - startTime)/1000 + " seconds!");
		}
	}
	
	public void run() {
		accessStudent();
	}
	
	public static void main(String[] args) {
		ThreadDemo td = new ThreadDemo();
		Thread t1 = new Thread(td, "a");
		Thread t2 = new Thread(td, "b");
		Thread t3 = new Thread(td, "c");
		t1.start();
		t2.start();
		t3.start();
	}

	class Student {
		private int age = 0;
		
		public int getAge() {
			return age;
		}
		
		public void setAge(int age) {
			this.age = age;
		}
	}
}
 结果:
a is running!
a got lock1@Step1!
b is running!
c is running!
a first Reading count: 1
a release lock1@Step1!
c got lock1@Step1!
c first Reading count: 2
c release lock1@Step1!
c got lock2@Step2!
thread c set age to: 80
thread c first read age is: 80
thread c second read age is: 80
c release lock2@step2!
thread c cost 15 seconds!
b got lock1@Step1!
b first Reading count: 3
b release lock1@Step1!
b got lock2@Step2!
thread b set age to: 73
thread b first read age is: 73
thread b second read age is: 73
b release lock2@step2!
thread b cost 25 seconds!
a got lock2@Step2!
thread a set age to: 80
thread a first read age is: 80
thread a second read age is: 80
a release lock2@step2!
thread a cost 30 seconds!
 
显然,在这个程序中,由于两段synchronized块使用了同样的对象做为对象锁 ,所以JVM优先使刚刚释放该锁的线程重新获得该 锁。这样,每个线程执行的时间是10秒钟,并且要彻底把两个同步块的动作执行完毕,才能释放对象锁。这样,加起来一共是 30秒。

我想一定有人会说:如果两段synchronized块采用两个不同的对象锁,就可以提高程序的并发性,并且,这 两个对象锁应该选择那些被所有线程所共享的对象。

那么好。我们把第二个同步块中的对象锁改为student (此处略去代码,读 者自己修改),程序运行结果为:
a is running!
a got lock1@Step1!
b is running!
c is running!
a first Reading count: 1
a release lock1@Step1!
a got lock2@Step2!
c got lock1@Step1!
thread a set age to: 32
thread a first read age is: 32
c first Reading count: 2
c release lock1@Step1!
b got lock1@Step1!
thread a second read age is: 32
a release lock2@step2!
thread a cost 10 seconds!
c got lock2@Step2!
thread c set age to: 86
thread c first read age is: 86
b first Reading count: 3
b release lock1@Step1!
thread c second read age is: 86
c release lock2@step2!
thread c cost 15 seconds!
b got lock2@Step2!
thread b set age to: 40
thread b first read age is: 40
thread b second read age is: 40
b release lock2@step2!
thread b cost 20 seconds!
 从 修改后的运行结果来看,显然,由于同步块的对象锁不同了,
三个线程的执行顺序也发生了变化。在一个线程释放第一个同步块的同步锁之 后,第二个线程就可以进入第一个同步块,而此时,第一个线程可以继续执行第二个同步块。这样,整个执行过程中,有10秒钟 的时间是两个线程同时工作 的。另外十秒钟分别是第一个线程执行第一个同步块的动作和最后一个线 程执行第二个同步块的动作 。相比较第一 个例程,整个程序的运行时间节省了1/3。细心的读者不难总结出优化前后的执行时间比例公式:(n+1)/ 2n ,其中n为 线程数 。如果线程数趋近于正无穷,则程序执行效率的提高会接近50%。而如果一个线程的执行阶段被分割成m个 synchronized ,并且每个同步块使用不同的对象锁,而同步块的执行时间恒定,则执行时间比例公式可以写作:((m- 1)n+1)/ mn 那么当m趋于无穷大时,线程数n趋近于无穷大,则程序执行效率的提升几乎可以达到100%。(显然,我 们不能按照理想情况下的数学推导来给BOSS发报告,不过通过这样的数学推导,至少我们看到了提高多线程程序并发性的一种方案,而 这种方案至少具备数学上的可行性理论支持。)

可见,使用不同的对象锁,在不同的同步块中完成任务,
可以使性能大大提升。

很多人看到这不禁要问:这和新的Lock框 架有什么关系?


别着急。我们这就来看一看。

synchronized块 的确不错,但是他有一些功能性的限制

1. 它无法中断一个正在等候获得锁的线程,也无法通过投票得到锁,如果不想等下去,也就没法得到锁。

2.synchronized 块对于锁的获得和释放是在相同的堆栈帧中进行的。多数情况下,这没问题(而且与异常处理交互得很    好),但是,确实存在一些更适合使用 非块结构锁定的情况。

java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。

JDK 官方文档中提到:
ReentrantLock是“一个可重入的互斥锁 Lock ,它具有与使用 synchronized  方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
ReentrantLock 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查 此情况是否发生。 ”

简单来说,ReentrantLock有一个与锁相关的获取计 数器 ,如果拥有锁的某个线程再次得到锁 ,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放 。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。

ReentrantLock  类(重入锁)实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性 。此外,它还提供了在激烈争用情况下更佳的性 能 。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)

我们把 上面的例程改造一下:

package sky.cn.test4;

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

public class ThreadDemo implements Runnable {
	
	Student student = new Student();
	int count = 0;
	Lock lock1 = new ReentrantLock(false);
	Lock lock2 = new ReentrantLock(false);
	
	public void accessStudent() {
		String currentThreadName = Thread.currentThread().getName();
		long startTime = System.currentTimeMillis();
		System.out.println(currentThreadName + " is running!");
		lock1.lock();	//使用重入锁
		System.out.println(currentThreadName + " got lock1@Step1!");
		try {
			count++;
			Thread.sleep(5000);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			System.out.println(currentThreadName + " first Reading count: " + count);
			lock1.unlock();
			System.out.println(currentThreadName + " release lock1@Step1!");
		}
		
		lock2.lock();	//使用另外一个不同的重入锁
		System.out.println(currentThreadName + " got lock2@Step2!");
//		/*
//		 * 注:如果在此处抛出异常,将不会释放lock2的锁,需要确定开启锁和try之前不会出现异常,
//		 * 否则就全包到try中去
//		 */
//		if ("a".equals(currentThreadName)) {
//			throw new RuntimeException("thread a throw an exception!");
//		}
		try {
			Random random = new Random();
			int age = random.nextInt(100);
			System.out.println("thread " + currentThreadName + " set age to: " + age);
			this.student.setAge(age);
			System.out.println("thread " + currentThreadName + " first read age is: " 
            + this.student.getAge());
			Thread.sleep(5000);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			System.out.println("thread " + currentThreadName + " second read age is: " 
            + this.student.getAge());
			lock2.unlock();
			System.out.println(currentThreadName + " release lock2@step2!");
			long endTime = System.currentTimeMillis();
			System.out.println("thread " + currentThreadName + " cost " 
            + (endTime - startTime)/1000 + " seconds!");
		}
	}
	
	public void run() {
		accessStudent();
	}
	
	public static void main(String[] args) {
		ThreadDemo td = new ThreadDemo();
		Thread t1 = new Thread(td, "a");
		Thread t2 = new Thread(td, "b");
		Thread t3 = new Thread(td, "c");
		t1.start();
		t2.start();
		t3.start();
	}

	class Student {
		private int age = 0;
		
		public int getAge() {
			return age;
		}
		
		public void setAge(int age) {
			this.age = age;
		}
	}
}

 从上面这个 程序我们看到:

对象锁的获得和释放是由手工编码完成的,
所以获得锁和释放锁的时机 比使用同步块具有更好的可定制性 。并 且通过程序的运行结果(运行结果忽略,请读者根据例程自行观察),我们可以发现,和使用同步块的版本相比,结果是相同的

这说明两点问题:
1. 新的ReentrantLock的确实现了和同步块相同的语义功能。而对象锁的获得和释放 都可以由编码 人员自行掌握
2. 使用新的ReentrantLock,免去 了为同步块放置合适的对象锁 所要进行的考量。
3. 使用新的ReentrantLock,最佳的实践就是结合try/finally块来进行。在try块之前使用lock方法 ,而 在finally中使用unlock方法

细心的读者又发现了:

在我们的例程中,创建ReentrantLock实例的时候,
我们的构造函数里面传递的参数是false。那么如果传递 true又回是什么结果呢?这里面又有什么奥秘呢?

请看本节的续 ———— Fair or Unfair? It is a question...

分享到:
评论

相关推荐

    pendulum-2.1.2-cp39-cp39-win32.whl.rar

    python whl离线安装包 pip安装失败可以尝试使用whl离线安装包安装 第一步 下载whl文件,注意需要与python版本配套 python版本号、32位64位、arm或amd64均有区别 第二步 使用pip install XXXXX.whl 命令安装,如果whl路径不在cmd窗口当前目录下,需要带上路径 WHL文件是以Wheel格式保存的Python安装包, Wheel是Python发行版的标准内置包格式。 在本质上是一个压缩包,WHL文件中包含了Python安装的py文件和元数据,以及经过编译的pyd文件, 这样就使得它可以在不具备编译环境的条件下,安装适合自己python版本的库文件。 如果要查看WHL文件的内容,可以把.whl后缀名改成.zip,使用解压软件(如WinRAR、WinZIP)解压打开即可查看。 为什么会用到whl文件来安装python库文件呢? 在python的使用过程中,我们免不了要经常通过pip来安装自己所需要的包, 大部分的包基本都能正常安装,但是总会遇到有那么一些包因为各种各样的问题导致安装不了的。 这时我们就可以通过尝试去Python安装包大全中(whl包下载)下载whl包来安装解决问题。

    Nginx配置文件中FastCGI相关参数理解

    Nginx配置文件中FastCGI相关参数理解

    Pillow-8.4.0-cp310-cp310-win32.whl.rar

    python whl离线安装包 pip安装失败可以尝试使用whl离线安装包安装 第一步 下载whl文件,注意需要与python版本配套 python版本号、32位64位、arm或amd64均有区别 第二步 使用pip install XXXXX.whl 命令安装,如果whl路径不在cmd窗口当前目录下,需要带上路径 WHL文件是以Wheel格式保存的Python安装包, Wheel是Python发行版的标准内置包格式。 在本质上是一个压缩包,WHL文件中包含了Python安装的py文件和元数据,以及经过编译的pyd文件, 这样就使得它可以在不具备编译环境的条件下,安装适合自己python版本的库文件。 如果要查看WHL文件的内容,可以把.whl后缀名改成.zip,使用解压软件(如WinRAR、WinZIP)解压打开即可查看。 为什么会用到whl文件来安装python库文件呢? 在python的使用过程中,我们免不了要经常通过pip来安装自己所需要的包, 大部分的包基本都能正常安装,但是总会遇到有那么一些包因为各种各样的问题导致安装不了的。 这时我们就可以通过尝试去Python安装包大全中(whl包下载)下载whl包来安装解决问题。

    yolo算法-刹车灯探测器数据集-1070张图像带标签-交通信号灯.zip

    yolo系列算法目标检测数据集,包含标签,可以直接训练模型和验证测试,数据集已经划分好,包含数据集配置文件data.yaml,适用yolov5,yolov8,yolov9,yolov7,yolov10,yolo11算法; 包含两种标签格:yolo格式(txt文件)和voc格式(xml文件),分别保存在两个文件夹中; yolo格式:<class> <x_center> <y_center> <width> <height>, 其中: <class> 是目标的类别索引(从0开始)。 <x_center> 和 <y_center> 是目标框中心点的x和y坐标,这些坐标是相对于图像宽度和高度的比例值,范围在0到1之间。 <width> 和 <height> 是目标框的宽度和高度,也是相对于图像宽度和高度的比例值

    pocketsphinx-0.1.15-cp36-cp36m-win32.whl.rar

    python whl离线安装包 pip安装失败可以尝试使用whl离线安装包安装 第一步 下载whl文件,注意需要与python版本配套 python版本号、32位64位、arm或amd64均有区别 第二步 使用pip install XXXXX.whl 命令安装,如果whl路径不在cmd窗口当前目录下,需要带上路径 WHL文件是以Wheel格式保存的Python安装包, Wheel是Python发行版的标准内置包格式。 在本质上是一个压缩包,WHL文件中包含了Python安装的py文件和元数据,以及经过编译的pyd文件, 这样就使得它可以在不具备编译环境的条件下,安装适合自己python版本的库文件。 如果要查看WHL文件的内容,可以把.whl后缀名改成.zip,使用解压软件(如WinRAR、WinZIP)解压打开即可查看。 为什么会用到whl文件来安装python库文件呢? 在python的使用过程中,我们免不了要经常通过pip来安装自己所需要的包, 大部分的包基本都能正常安装,但是总会遇到有那么一些包因为各种各样的问题导致安装不了的。 这时我们就可以通过尝试去Python安装包大全中(whl包下载)下载whl包来安装解决问题。

    NI-VISA资源安装包

    Windows下2024Q4版本

    【java毕业设计】网上电子书店源码(ssm+mysql+说明文档+LW).zip

    功能说明: (a) 管理员;管理员使用本系统涉到的功能主要有主页、个人中心、用户管理、一级分类管理、二级分类管理、电子书管理、下单购买管理、我的书籍管理、留言反馈、系统管理等功能。 (b) 用户;用户进入系统可以实现首页、电子书、通知公告、留言反馈、个人中心、后台管理、在线客服等,登录注册后可以对主页、个人中心、下单购买管理、我的书籍管理、留言反馈等功能进行详细操作。 环境说明: 开发语言:Java 框架:ssm,mybatis JDK版本:JDK1.8 数据库:mysql 5.7及以上 数据库工具:Navicat11及以上 开发软件:eclipse/idea Maven包:Maven3.3及以上 服务器:tomcat7及以上

    【java毕业设计】网上点餐系统源码(ssm+mysql+说明文档).zip

    环境说明: 开发语言:Java 框架:ssm,mybatis JDK版本:JDK1.8 数据库:mysql 5.7及以上 数据库工具:Navicat11及以上 开发软件:eclipse/idea Maven包:Maven3.3及以上 服务器:tomcat7及以上

    pocketsphinx-0.1.15-cp39-cp39-win_amd64.whl.rar

    python whl离线安装包 pip安装失败可以尝试使用whl离线安装包安装 第一步 下载whl文件,注意需要与python版本配套 python版本号、32位64位、arm或amd64均有区别 第二步 使用pip install XXXXX.whl 命令安装,如果whl路径不在cmd窗口当前目录下,需要带上路径 WHL文件是以Wheel格式保存的Python安装包, Wheel是Python发行版的标准内置包格式。 在本质上是一个压缩包,WHL文件中包含了Python安装的py文件和元数据,以及经过编译的pyd文件, 这样就使得它可以在不具备编译环境的条件下,安装适合自己python版本的库文件。 如果要查看WHL文件的内容,可以把.whl后缀名改成.zip,使用解压软件(如WinRAR、WinZIP)解压打开即可查看。 为什么会用到whl文件来安装python库文件呢? 在python的使用过程中,我们免不了要经常通过pip来安装自己所需要的包, 大部分的包基本都能正常安装,但是总会遇到有那么一些包因为各种各样的问题导致安装不了的。 这时我们就可以通过尝试去Python安装包大全中(whl包下载)下载whl包来安装解决问题。

    cn-visual-studio-2010-ultimate-x86-dvd-532347.z01

    cn-visual-studio-2010-ultimate-x86-dvd-532347.z01

    智慧城市照明智能管理系统解决方案PPT(27页).pptx

    城市运行管理的重要性与挑战 城市运行体系是以人为本的服务和经济发展体系,涉及决策、管理和执行三个层次。当前城市运行管理面临城市化快速发展、资源环境制约和社会矛盾突出等挑战。信息技术的发展为城市运行管理提供了重要手段,城市信息化经历了数字化、智能化到智慧化的发展过程。我国城市信息化虽取得进展,但仍处于初级阶段,存在缺乏整体规划、资源浪费和协作效率不高等问题。 智慧城市综合运行管理解决方案 智慧城市运行管理中心(SCOC)是支撑城市运行综合管理的神经中枢,旨在掌控城市运行综合体征,促进服务型政府转型。该中心通过全面整合运行资源,服务城市未来发展,提升城市运行水平和突发事件处置效率。中心纵向提升综合职能,横向贯通专业分工,包括综合管理平台、专业管理平台和业务操作平台,覆盖城市交通、公共安全、生态环境等多个领域。 智慧城市综合运行管理平台的结构与功能 智慧城市综合运行管理平台包括决策支持系统、处置系统、基础设施和监测系统。平台通过综合展现系统、综合应急指挥系统、综合运行业务联动系统等,实现城市运行的综合监测和管理。物联网数据采集系统利用网络通讯技术,实现城市物联网设备的高效运行。平台还包含云计算业务支撑系统、城市基础数据库、视频图像云平台等,以支持城市运行管理的各个方面。 智慧城市综合运行管理解决方案的优势 该解决方案具有三个核心优势:首先,它提供了完整的智慧城市视角,不仅仅是指挥中心或数据中心,而是智慧城市的实际载体。其次,它建立了完整的城市运行联动体系,打通业务部门壁垒,形成有机融合的业务联动平台,提升业务处理效率和服务水平。最后,方案凝聚了多年智慧城市建设咨询经验,为城市运行管理提供了成熟的解决方案。 项目实施建议 智慧城市运行管理中心的建设思路和项目实施建议是方案的重要组成部分,旨在指导城市如何有效实施智慧城市运行管理解决方案,以应对城市运行管理的挑战,提升城市管理的智能化和效率。通过这些建议,城市能够更好地规划和实施智慧城市项目,实现可持续发展。

    persistent-4.9.0-cp39-cp39-win32.whl.rar

    python whl离线安装包 pip安装失败可以尝试使用whl离线安装包安装 第一步 下载whl文件,注意需要与python版本配套 python版本号、32位64位、arm或amd64均有区别 第二步 使用pip install XXXXX.whl 命令安装,如果whl路径不在cmd窗口当前目录下,需要带上路径 WHL文件是以Wheel格式保存的Python安装包, Wheel是Python发行版的标准内置包格式。 在本质上是一个压缩包,WHL文件中包含了Python安装的py文件和元数据,以及经过编译的pyd文件, 这样就使得它可以在不具备编译环境的条件下,安装适合自己python版本的库文件。 如果要查看WHL文件的内容,可以把.whl后缀名改成.zip,使用解压软件(如WinRAR、WinZIP)解压打开即可查看。 为什么会用到whl文件来安装python库文件呢? 在python的使用过程中,我们免不了要经常通过pip来安装自己所需要的包, 大部分的包基本都能正常安装,但是总会遇到有那么一些包因为各种各样的问题导致安装不了的。 这时我们就可以通过尝试去Python安装包大全中(whl包下载)下载whl包来安装解决问题。

    pocketsphinx-0.1.15-cp27-cp27m-win_amd64.whl.rar

    python whl离线安装包 pip安装失败可以尝试使用whl离线安装包安装 第一步 下载whl文件,注意需要与python版本配套 python版本号、32位64位、arm或amd64均有区别 第二步 使用pip install XXXXX.whl 命令安装,如果whl路径不在cmd窗口当前目录下,需要带上路径 WHL文件是以Wheel格式保存的Python安装包, Wheel是Python发行版的标准内置包格式。 在本质上是一个压缩包,WHL文件中包含了Python安装的py文件和元数据,以及经过编译的pyd文件, 这样就使得它可以在不具备编译环境的条件下,安装适合自己python版本的库文件。 如果要查看WHL文件的内容,可以把.whl后缀名改成.zip,使用解压软件(如WinRAR、WinZIP)解压打开即可查看。 为什么会用到whl文件来安装python库文件呢? 在python的使用过程中,我们免不了要经常通过pip来安装自己所需要的包, 大部分的包基本都能正常安装,但是总会遇到有那么一些包因为各种各样的问题导致安装不了的。 这时我们就可以通过尝试去Python安装包大全中(whl包下载)下载whl包来安装解决问题。

    域外渗透域内思路:使用工具与技术进行域内侦察与暴力破解

    内容概要:本文介绍了一种通过域外渗透手段进入域内网络的技术思路。主要内容涵盖了使用VPN拨入内网,利用nbt.exe、ladon.exe、nmap等工具进行网络扫描,查找域控制器,以及使用bash和PowerShell脚本进行域用户口令暴力破解的方法。同时介绍了几种常用工具如ldapsearch、PowerView和PingCastle的使用方法,以及它们在获取域内信息方面的具体应用场景。 适合人群:网络安全专业人员、红队成员、渗透测试工程师等从事信息安全相关工作的技术人员。 使用场景及目标:帮助安全专家在进行渗透测试时有效地获取域内网络的关键信息,评估域的安全性,识别潜在的安全漏洞,并提出改进建议。 其他说明:文章提供了详细的命令示例和配置指南,适用于Windows和Linux环境,同时也提到了一些需要注意的安全事项,如防止触发安全警报等。

    Vue搭建AudioPlaySation(三)

    Vue搭建AudioPlaySation(三)

    yolo算法-石头剪刀数据集-7331张图像带标签.zip

    yolo系列算法目标检测数据集,包含标签,可以直接训练模型和验证测试,数据集已经划分好,包含数据集配置文件data.yaml,适用yolov5,yolov8,yolov9,yolov7,yolov10,yolo11算法; 包含两种标签格:yolo格式(txt文件)和voc格式(xml文件),分别保存在两个文件夹中; yolo格式:<class> <x_center> <y_center> <width> <height>, 其中: <class> 是目标的类别索引(从0开始)。 <x_center> 和 <y_center> 是目标框中心点的x和y坐标,这些坐标是相对于图像宽度和高度的比例值,范围在0到1之间。 <width> 和 <height> 是目标框的宽度和高度,也是相对于图像宽度和高度的比例值

    psf-2021.6.6-cp37-cp37m-win32.whl.rar

    python whl离线安装包 pip安装失败可以尝试使用whl离线安装包安装 第一步 下载whl文件,注意需要与python版本配套 python版本号、32位64位、arm或amd64均有区别 第二步 使用pip install XXXXX.whl 命令安装,如果whl路径不在cmd窗口当前目录下,需要带上路径 WHL文件是以Wheel格式保存的Python安装包, Wheel是Python发行版的标准内置包格式。 在本质上是一个压缩包,WHL文件中包含了Python安装的py文件和元数据,以及经过编译的pyd文件, 这样就使得它可以在不具备编译环境的条件下,安装适合自己python版本的库文件。 如果要查看WHL文件的内容,可以把.whl后缀名改成.zip,使用解压软件(如WinRAR、WinZIP)解压打开即可查看。 为什么会用到whl文件来安装python库文件呢? 在python的使用过程中,我们免不了要经常通过pip来安装自己所需要的包, 大部分的包基本都能正常安装,但是总会遇到有那么一些包因为各种各样的问题导致安装不了的。 这时我们就可以通过尝试去Python安装包大全中(whl包下载)下载whl包来安装解决问题。

    【java毕业设计】古诗词数字化平台源码(ssm+mysql+说明文档+LW).zip

    功能说明: 古诗词数字化平台的功能已基本实现,主要实现主页、个人中心、用户管理、诗词信息管理、分类管理、诗人信息管理、个人分享管理、系统管理等功能的操作系统。 环境说明: 开发语言:Java 框架:ssm,mybatis JDK版本:JDK1.8 数据库:mysql 5.7及以上 数据库工具:Navicat11及以上 开发软件:eclipse/idea Maven包:Maven3.3及以上 服务器:tomcat7及以上

    YOLO格式下的行人识别数据集

    这个文档中包含了行人数据集约四千张,在train文件中就包含了三千多张数据集。工具是使用了Labelimg进行标注。

    中介与调节效应分析素材-精心整理资料.zip

    中介与调节效应分析素材-精心整理资料.zip

Global site tag (gtag.js) - Google Analytics