Java ReentrantLock
目录
1 互斥性
2 公平性
3 可重入性
4 获取和释放锁
5 尝试获取锁
6 可中断的锁定
7 条件变量
8 性能
9 使用场景
ReentrantLock
是 Java 提供的一种可重入的互斥锁,位于 java.util.concurrent.locks
包中,它实现了 Lock
接口。这个锁提供了与内置监视器锁(通过 synchronized
关键字实现)类似的互斥性和内存可见性,但具有更强大的功能和灵活性。以下是 ReentrantLock
的一些基本概念:
1 互斥性
ReentrantLock
可以确保同一时间只有一个线程可以执行由该锁保护的代码块,从而避免了竞态条件。
2 公平性
- 非公平锁:默认情况下,
ReentrantLock
是非公平的。这意味着线程获取锁的顺序并不是按照它们请求锁的顺序来确定的。非公平锁通常提供更高的吞吐量。 - 公平锁:当创建
ReentrantLock
实例时,可以通过传入true
参数来指定为公平锁。公平锁保证线程将按照它们请求锁的顺序获得锁。这可能会降低吞吐量,但能减少饥饿现象。
示例代码:默认是非公平锁,如果需要公平锁,则在构造函数中传入 true
。
import java.util.concurrent.locks.ReentrantLock;// 默认非公平锁
ReentrantLock lock = new ReentrantLock();// 公平锁
ReentrantLock fairLock = new ReentrantLock(true);
3 可重入性
可重入意味着如果一个线程已经持有某个锁,那么它可以再次获取这个锁而不会被阻塞。这是非常有用的特性,因为它允许方法在调用其他可能也需要相同锁的方法时不会导致死锁。
示例代码:
public void reentrantExample() {lock.lock();try {// 第一次获取锁System.out.println("First time locked, thread: " + Thread.currentThread().getName());// 在已经持有锁的情况下再次获取锁lock.lock();try {System.out.println("Second time locked, thread: " + Thread.currentThread().getName());} finally {lock.unlock(); // 释放第二次获取的锁}} finally {lock.unlock(); // 释放第一次获取的锁}
}
输出结果:
First time locked, thread: main
Second time locked, thread: main
使用可重入锁的具体例子:假设有一个类,其中包含一个递归方法,且这个方法需要是线程安全的。由于方法会多次调用自身,因此需要一个可重入锁来确保同一个线程可以重复获取锁。
import java.util.concurrent.locks.ReentrantLock;public class FactorialCalculator {private final ReentrantLock lock = new ReentrantLock();public int factorial(int number) {lock.lock(); // 获取锁try {if (number <= 1) {return 1;} else {return number * factorial(number - 1); // 递归调用}} finally {lock.unlock(); // 释放锁}}
}
4 获取和释放锁
获取锁通过调用 lock()
方法,释放锁则通过 unlock()
方法。通常建议在 finally
块中释放锁,以确保即使发生异常也能正确释放锁。
public void doSomething() {lock.lock();try {// 执行同步代码} finally {lock.unlock();}
}
5 尝试获取锁
tryLock()
方法尝试获取锁,如果无法立即获得锁,则返回 false
,不会阻塞线程。
if (lock.tryLock()) { // 尝试获取锁,如果无法获取,则立即返回falsetry {// 执行同步代码} finally {lock.unlock();}
} else {// 锁未获得时的操作
}
tryLock()
方法有两个版本:
- 无参数的
tryLock()
:尝试获取锁,如果锁可用则立即获取并返回true
,否则立即返回false
。 - 带超时的
tryLock(long timeout, TimeUnit unit)
:尝试获取锁,如果在指定的时间内可以获取到锁,则返回true
;如果超过了指定时间仍无法获取到锁,则返回false
。
具体使用示例:
无参数的 tryLock():
import java.util.concurrent.locks.ReentrantLock;public class TryLockExample {private final ReentrantLock lock = new ReentrantLock();public void doWork() {if (lock.tryLock()) { // 尝试获取锁try {// 执行需要同步的操作System.out.println("Thread " + Thread.currentThread().getName() + " is doing work.");} finally {lock.unlock(); // 确保释放锁}} else {// 如果无法获取锁,执行其他操作System.out.println("Thread " + Thread.currentThread().getName() + " could not get the lock and will do something else.");}}public static void main(String[] args) {TryLockExample example = new TryLockExample();// 创建两个线程来调用 doWork 方法Thread t1 = new Thread(() -> example.doWork(), "Thread-1");Thread t2 = new Thread(() -> example.doWork(), "Thread-2");t1.start();t2.start();}
}
在这个例子中,如果 Thread-1
先获取到了锁,那么 Thread-2
将无法获取锁,并且会直接输出“无法获取锁”的信息。
带超时的 tryLock(long timeout, TimeUnit unit)
:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;public class TryLockWithTimeoutExample {private final ReentrantLock lock = new ReentrantLock();public void doWork() {boolean locked = false;try {// 尝试在5秒内获取锁locked = lock.tryLock(5, TimeUnit.SECONDS);if (locked) {// 执行需要同步的操作System.out.println("Thread " + Thread.currentThread().getName() + " is doing work.");} else {// 如果在5秒内无法获取锁,执行其他操作System.out.println("Thread " + Thread.currentThread().getName() + " could not get the lock within 5 seconds and will do something else.");}} catch (InterruptedException e) {// 处理中断异常Thread.currentThread().interrupt();System.out.println("Thread " + Thread.currentThread().getName() + " was interrupted while waiting for the lock.");} finally {if (locked) {lock.unlock(); // 确保释放锁}}}public static void main(String[] args) {TryLockWithTimeoutExample example = new TryLockWithTimeoutExample();// 创建两个线程来调用 doWork 方法Thread t1 = new Thread(() -> example.doWork(), "Thread-1");Thread t2 = new Thread(() -> example.doWork(), "Thread-2");t1.start();t2.start();}
}
在这个例子中,如果 Thread-1
持有锁超过5秒钟,那么 Thread-2
将会在等待5秒后放弃尝试获取锁,并执行相应的逻辑。
使用场景
- 避免死锁:当多个线程试图以不同的顺序获取多个锁时,可能会导致死锁。使用
tryLock()
可以帮助检测这种情况,并采取适当的措施。 - 提高响应性:在某些情况下,你可能不希望线程一直等待锁,而是希望线程能够快速响应其他任务。这时可以使用
tryLock()
来检查锁是否可用,如果不可用就立即执行其他工作。 - 资源竞争控制:在资源有限的情况下,可以使用
tryLock()
来尝试获取资源,如果获取不到则可以选择放弃或重试。
6 可中断的锁定
lockInterruptibly()
方法允许在等待获取锁的过程中响应中断。
try {lock.lockInterruptibly(); // 尝试获取锁,可以响应中断try {// 执行同步代码} finally {lock.unlock();}
} catch (InterruptedException e) {// 处理中断情况
}
示例代码:
import java.util.concurrent.locks.ReentrantLock;public class CancellableTask {private final ReentrantLock lock = new ReentrantLock();private boolean isCancelled = false;public void runTask() {Thread thread = new Thread(() -> {try {// 尝试获取锁,同时可以响应中断lock.lockInterruptibly();try {// 模拟任务执行while (!isCancelled) {System.out.println("Task is running...");// 假设任务需要一段时间完成Thread.sleep(1000);}} finally {lock.unlock();}} catch (InterruptedException e) {System.out.println("Task was interrupted, stopping execution.");// 通常在这里你会清理资源并退出// 重新设置中断状态,以便调用者知道该线程已被中断Thread.currentThread().interrupt();}});thread.start();// 在某个时刻决定取消任务try {Thread.sleep(3000); // 等待几秒钟} catch (InterruptedException e) {e.printStackTrace();}isCancelled = true;thread.interrupt(); // 中断线程}public static void main(String[] args) {CancellableTask task = new CancellableTask();task.runTask();}
}
当主线程决定取消任务时,它会中断工作线程。工作线程会在 lockInterruptibly()
调用处立即响应中断,并抛出 InterruptedException
,从而允许任务快速终止。
输出结果:
Task is running...
Task is running...
Task is running...
Task was interrupted, stopping execution.
除了使用 lockInterruptibly()
来实现中断锁,还可以使用带超时的 tryLock(long timeout, TimeUnit unit)来达成这一目的。以下是例子:
import java.util.concurrent.locks.ReentrantLock;public class ResourceService {private final ReentrantLock resourceLock = new ReentrantLock();public void useResource() {Thread thread = new Thread(() -> {try {if (resourceLock.tryLock()) {try {// 使用资源System.out.println("Using resource...");Thread.sleep(10000); // 模拟长时间操作} finally {resourceLock.unlock();}} else {System.out.println("Could not acquire the resource, operation is cancelled.");}} catch (InterruptedException e) {System.out.println("Resource usage was interrupted, releasing the resource.");resourceLock.unlock(); // 如果已经获得锁,则释放Thread.currentThread().interrupt(); // 保持中断状态}});thread.start();// 在某个时刻决定取消使用资源的操作try {Thread.sleep(5000); // 等待几秒钟} catch (InterruptedException e) {e.printStackTrace();}thread.interrupt(); // 中断线程}public static void main(String[] args) {ResourceService service = new ResourceService();service.useResource();}
}
是的,tryLock(long time, TimeUnit unit)
方法也可以响应中断。当一个线程调用 tryLock(long time, TimeUnit unit)
时,它会尝试在指定的时间内获取锁。如果在这段时间内没有获取到锁,方法将返回 false
。此外,如果在此期间线程被中断,该方法会立即抛出 InterruptedException
并且不会获取锁。
注意:ReentrantLock
的 lock()
方法确实不会响应中断。当一个线程调用 lock()
试图获取锁时,如果锁已经被其他线程持有,那么该线程将一直阻塞,直到它能够获取到锁为止。即使在此期间该线程被中断(例如通过调用 Thread.interrupt()
),它也不会抛出 InterruptedException
或者以其他方式退出等待状态。只有在获取到锁之后,中断状态才会被检查。
7 条件变量
ReentrantLock
提供了条件变量 Condition
,它可以替代传统的 Object.wait/notify
机制。这些对象类似于 Object
类中的 wait/notify
机制,但是更加灵活。每个 Condition
实例都可以独立地挂起和唤醒线程,这对于复杂的同步需求是非常有用的。
下面是一个具体的例子,展示了如何使用条件变量来实现生产者-消费者模式:
在这个模式中,有一个共享缓冲区,生产者向缓冲区添加元素,消费者从缓冲区移除元素。当缓冲区满时,生产者必须等待;当缓冲区空时,消费者必须等待。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.LinkedList;public class ProducerConsumerExample {private final int BUFFER_SIZE = 5; // 缓冲区大小private final LinkedList<Integer> buffer = new LinkedList<>();private final ReentrantLock lock = new ReentrantLock();private final Condition notFull = lock.newCondition(); // 缓冲区不满的条件private final Condition notEmpty = lock.newCondition(); // 缓冲区不空的条件public void put(int value) throws InterruptedException {lock.lock();try {// 如果缓冲区已满,则等待while (buffer.size() == BUFFER_SIZE) {notFull.await();}// 向缓冲区添加元素buffer.add(value);System.out.println("Produced: " + value);// 唤醒可能正在等待的消费者notEmpty.signal();} finally {lock.unlock();}}public int get() throws InterruptedException {lock.lock();try {// 如果缓冲区为空,则等待while (buffer.isEmpty()) {notEmpty.await();}// 从缓冲区移除元素int value = buffer.removeFirst();System.out.println("Consumed: " + value);// 唤醒可能正在等待的生产者notFull.signal();return value;} finally {lock.unlock();}}public static void main(String[] args) {final ProducerConsumerExample example = new ProducerConsumerExample();Thread producerThread = new Thread(() -> {for (int i = 0; i < 20; i++) {try {example.put(i);Thread.sleep(100); // 模拟生产时间} catch (InterruptedException e) {Thread.currentThread().interrupt();break;}}});Thread consumerThread = new Thread(() -> {for (int i = 0; i < 20; i++) {try {example.get();Thread.sleep(200); // 模拟消费时间} catch (InterruptedException e) {Thread.currentThread().interrupt();break;}}});producerThread.start();consumerThread.start();try {producerThread.join();consumerThread.join();} catch (InterruptedException e) {e.printStackTrace();}}
}
代码解释:
- ReentrantLock 和 Condition:我们使用
ReentrantLock
来保护对共享资源(即缓冲区)的访问,并且创建了两个条件变量notFull
和notEmpty
。 - put() 方法:生产者调用此方法向缓冲区添加数据。如果缓冲区已满,则生产者将调用
notFull.await()
等待直到有空间可用。一旦添加了数据,就通过notEmpty.signal()
通知等待中的消费者。 - get() 方法:消费者调用此方法从缓冲区获取数据。如果缓冲区为空,则消费者将调用
notEmpty.await()
等待直到有数据可用。一旦取出了数据,就通过notFull.signal()
通知等待中的生产者。 - 主线程:启动生产者和消费者线程,并等待它们完成。
8 性能
ReentrantLock
在某些情况下比 synchronized
更高效,尤其是在高度竞争的情况下。这是因为 ReentrantLock
可以使用自旋而不是完全阻塞来等待锁,这样可以减少上下文切换的成本。
9 使用场景
- 当你需要比
synchronized
更细粒度的控制时,例如尝试获取锁、响应中断或使用多个条件变量。 - 当你需要实现公平锁时。
- 当你希望在性能上有所提升,并且能够处理高级并发模式时。
示例代码:
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockExample {private final ReentrantLock lock = new ReentrantLock();public void doSomething() {// 获取锁lock.lock();try {// 执行需要同步的操作System.out.println("Thread " + Thread.currentThread().getName() + " is doing something.");} finally {// 确保锁最终会被释放lock.unlock();}}public static void main(String[] args) {ReentrantLockExample example = new ReentrantLockExample();// 创建两个线程来调用 doSomething 方法Thread t1 = new Thread(() -> example.doSomething(), "Thread-1");Thread t2 = new Thread(() -> example.doSomething(), "Thread-2");t1.start();t2.start();}
}
在这个例子中,doSomething
方法使用 ReentrantLock
来确保每次只有一个线程可以执行其中的代码。注意在 finally
块中释放锁,以防止因异常导致锁未被释放的情ss
相关文章:
Java ReentrantLock
目录 1 互斥性 2 公平性 3 可重入性 4 获取和释放锁 5 尝试获取锁 6 可中断的锁定 7 条件变量 8 性能 9 使用场景 ReentrantLock 是 Java 提供的一种可重入的互斥锁,位于 java.util.concurrent.locks 包中,它实现了 Lock 接口。这个锁提供了与内…...

【Linux系统编程】第二十六弹---彻底掌握文件I/O:C/C++文件接口与Linux系统调用实践
✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、回顾C语言文件接口 1.1、以写的方式打开文件 1.2、以追加的方式打开文件 2、初步理解文件 2.1、C文件接口 3、进一步理…...

数据分析-29-基于pandas的窗口操作和对JSON格式数据的处理
文章目录 1 窗口操作1.1 滑动窗口思想1.2 函数df.rolling2 JSON格式数据2.1 处理简单JSON对象和JSON列表2.1.1 处理简单的JSON结构2.1.2 处理空字段2.1.3 获取部分字段2.2 处理多级json2.2.1 展开所有级别(默认)2.2.2 自定义展开层级2.3 处理嵌套列表JSON3 参考附录1 窗口操作 …...

Ubuntu-WSL2一键设置代理操作
现状: Window11中拥有自己的代理软件 ,可以科学上网已在WSL2中安装Ubuntu22.04 需求: Ubuntu-WSL2实现科学上网 实现: 参考:为 WSL2 一键设置代理 Linux 子系统中的网关指向的是 Windows,DNS 服务器指…...
ubuntu命令行连接wifi
在Ubuntu上,你可以通过命令行连接到Wi-Fi网络。以下是详细步骤,主要使用 nmcli 和 nmtui 命令。 方法 1:使用 nmcli nmcli 是 NetworkManager 的命令行界面,用于管理网络连接。以下是使用 nmcli 连接到 Wi-Fi 网络的步骤&#x…...
日常工作第10天:
vim 批量编辑的命令是 移动光标到列首。按键 CtrlV 进入 Visual block 模式,标记你想要进行编辑的列(HJKL可以向左下上右移动光标)。按键 ShiftI 进入 Insert 模式,在列首输入文本;或者 Shift A,追加文本…...
CNN+Transformer解说
CNN(卷积神经网络)和Transformer是两种在深度学习领域广泛使用的模型架构,它们在处理不同类型的数据和任务时各有优势。 CNN擅长捕捉局部特征和空间层次结构,而Transformer擅长处理序列数据和长距离依赖关系。 将CNN与Transform…...

jmeter中token测试
案例: 网站:http://shop.duoceshi.com 讲解:用三个接口来讲解 第一个接口code:GET http://manage.duoceshi.com/auth/code 第二个登录接口:http://manage.duoceshi.com/auth/login 第三个接口:http://…...

基于解压缩模块的JPEG同步重压缩检测论文学习
一、论文基本信息: 论文题目:基于解压缩模块的JPEG同步重压缩检测 作者:王金伟1 ,胡冰涛1 ,张家伟1 ,马 宾2 ,罗向阳3 (1.南京信息工程大学计算机学院、网络空间安全学院…...

音视频入门基础:FLV专题(7)——Tag header简介
一、引言 从《音视频入门基础:FLV专题(3)——FLV header简介》中可以知道, 在FLV header之后,FLV文件剩下的部分应由PreviousTagSize和Tag组成。FLV文件 FLV header PreviousTagSize0 Tag1 PreviousTagSize1 Ta…...

【Linux 报错】“make: ‘xxxx‘ is up to date.” 解决办法
一、报错原因 我们使用 make 命令,想要将 text.c 文件编译形成 可执行文件 text 时,报错如下 make: test is up to date. 中文含义:test 文件已经达到最新状态 意思是: test.c 文件里面的 所有源代码都没有修改过,你…...

【FPGA开发】Xilinx FPGA差分输入时钟的使用方法
正文 以前在使用ZYNQ的领航者ZYNQ7020进行FPGA学习时,它们使用的单端50M的输入时钟,在verlog代码编写上比较简单,而现在使用Alinx的AXU3EG开发板时,发现它使用的是200M的差分输入时钟,哪这个时候,输入时钟要…...

面试扩展知识点
1.C语言中分为下面几个存储区 栈(stack): 由编译器自动分配释放堆(heap): 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收全局区(静态区): 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域&#…...
【经验分享】MyCAT 中间件
学习了一下数据量过大的解决方案,使用 MyCAT 中间件。 MyCAT 可以解决分布式事务、读写分离、主从、分片等一系列MySQL集群和分布式问题。 整体过程可以概括为拦截 - 分发 - 响应 例如设置 MyCAT 分片规则为每500万条数据就换一个数据库存储。 分库分表的中心思想都是将数据…...

Kotlin:1.8.0 的新特性
一、概述 Kotlin 1.8.0版本英语官方文档 Kotlin 1.8.0 中文官方文档 The Kotlin 1.8.0 release is out and here are some of its biggest highlights: Kotlin 1.8.0发布了,下面是它的一些亮点: JVM 平台新增实验性函数:递归复制或删除目录内容改进了 …...

深度学习之开发环境(CUDA、Conda、Pytorch)准备(4)
目录 1.CUDA 介绍 1.1 CUDA 的基本概念 1.2 CUDA 的工作原理 1.3 CUDA 的应用领域 2. 安装CUDA 2.1 查看GPU版本 2.2 升级驱动(可选) 2.3 查看CUDA版本驱动对应的支持的CUDA ToolKit工具包 2.4 下载Toolkit 2.5 安装(省略࿰…...

10月2日笔记(内网资源探测篇)
内网资源探测 在内网渗透中,测试人员往往需要通过各种内网扫描技术来探测内网资源的情况,为后续的横向渗透做准备,通常需要发现内网存活的主机,并探测主机的操作系统、主机开放了哪些端口、端口上运行了哪些服务、服务的当前版本…...

SpringCloud-基于Docker和Docker-Compose的项目部署
一、初始化环境 1. 卸载旧版本 首先,卸载可能已存在的旧版本 Docker。如果您不确定是否安装过,可以直接执行以下命令: sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logro…...

Linux下的基本指令/命令(一)
目录 基本命令 1. Is命令/指令: 罗列当前目录下指定的文件或者目录. 2. pwd命令: 查看当前工作的路径 3. cd命令: 切换到指定路径下。 只能切换到目录中 4. tree命令: 树状显式目录 使用前要输入命令 yum install -y tree ,用来安装一个…...

从零开始Ubuntu24.04上Docker构建自动化部署(四)Docker安装gitlab
你会发现所有gitlab都无法拉取到的情况下,经查找极狐下的gitlab还可以下载,具体如下: 建议保存地址: https://packages.gitlab.cn/#browse/searchkeyword%3Dgitlab 最新版本: sudo docker pull registry.gitlab.cn…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...