多线程读写文件利器-ReentrantReadWriteLock

本文详细解析Java线程的基本概念、状态转换、锁的作用及实现方式,包括Object的wait()、notify()方法,以及ObjectMonitor的类型与作用。重点介绍了线程同步原理、synchronized关键字的使用,包括同步方法、同步块与Reentrant同步锁的特性与应用,以及ReentrantReadWriteLock在多线程读写场景下的高效解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[size=11]理解线程,首先要明白线程的几种状态,以及状态之间的转换,具体参考下图:[/size]

[img]http://dl.iteye.com/upload/attachment/589769/8dac3eee-1aa4-3e21-a64f-5ab3e72c3e62.gif[/img]


[size=11]其次,必须理解线程中"锁"的作用,以下引用自sun公司文档Threads and Locks一章中关于Locks的描述:[/size]

[quote][size=11]The Java programming language provides multiple mechanisms for communicating between threads. The most basic of these methods is [i]synchronization[/i], which is implemented using [i]monitors[/i]. Each object in Java is associated with a monitor, which a thread can [i]lock[/i] or [i]unlock[/i]. Only one thread at a time may hold a lock on a monitor. Any other threads attempting to lock that monitor are blocked until they can obtain a lock on that monitor. A thread t may lock a particular monitor multiple times; each unlock reverses the effect of one lock operation.[/size][/quote]

[size=11]以上说明,对于多线程来说,正是依靠锁定Object的Monitor来保证同一时间只有一个线程持有某个特定的锁.

Java API中定义Object的wait(),notify()方法就是基于其Monitor来实现:[/size]

[quote][size=11]notify(): Wakes up a single thread that is waiting on this object's monitor.
wait(): Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
notifyAll(),wait(long timeout),wait(long timeout, int nanos)...[/size][/quote]

[size=11]对于Object Monitor来说,可参考Analyzing Stack Traces文档关于Monitor的部分说明及种类介绍:[/size]

[quote][size=11]A monitor can be thought of as a lock on an object, and every object has a monitor.
...
The following table describes the common registered monitors: [/size][/quote]

[table]
[size=9]|Monitor|Description|
|utf8 hash table|Locks the hashtable of defined i18N Strings that were loaded from the class constant pool.|
|JNI pinning lock|Protects block copies of arrays to native method code.|
|JNI global reference lock|Locks the global reference table which holds values that need to be explicitly freed, and will outlive the lifetime of the native method call.|
|BinClass lock|Locks access to the loaded and resolved classes list. The global table list of classes|
|Class linking lock|Protects a classes data when loading native libraries to resolve symbolic references|
|System class loader lock|Ensures that only one thread is loading a system class at a time.|
|Code rewrite lock|Protects code when an optimization is attempted.|
|Heap lock|Protects the Java heap during heap memory management|
|Monitor cache lock|Only one thread can have access to the monitor cache at a time this lock ensures the integrity of the monitor cache|
|Dynamic loading lock|Protects Unix green threads JVMs from loading the shared library stub libdl.so more than once at a time.|
|Monitor IO lock|Protects physical I/O for example, open and read.|
|User signal monitor|Controls access to the signal handler if a user signal USRSIG in green threads JVMs.|
|Child death monitor|Controls access to the process wait information when using the runtime system calls to run locals commands in a green threads JVM.|
|I/O Monitor|Controls access to the threads file descriptors for poll/select events|
|Alarm Monitor|Controls access to a clock handler used in green threads JVMs to handle timeouts|
|Thread queue lock|Protects the queue of active threads|
|Monitor registry|Only one thread can have access to the monitor registry at a time this lock ensures the integrity of that registry|
|Has finalization queue lock *|Protects the list of queue lock objects that have been garbage-collected, and deemed to need finalization. They are copied to the Finalize me queue|
|Finalize me queue lock *|Protects a list of objects that can be finalized at leisure|
|Name and type hash table lock *|Protects the JVM hash tables of constants and their types|
|String intern lock *|Locks the hashtable of defined Strings that were loaded from the class constant pool|
|Class loading lock *|Ensures only one thread loads a class at a time|
|Java stack lock *|Protects the free stack segments list|[/size]
[/table]


[size=11]多线程下的数据操作需要保证其数据的可靠一致性,为此必须实现线程的同步.以下引用自Intrinsic Locks and Synchronization:[/size]

[quote][size=11]Synchronization is built around an internal entity known as the [i]intrinsic lock[/i] or [i]monitor lock[/i]. (The API specification often refers to this entity simply as a "monitor.") Intrinsic locks play a role in both aspects of synchronization: enforcing exclusive access to an object's state and establishing happens-before relationships that are essential to visibility.

Every object has an intrinsic lock associated with it. By convention, a thread that needs exclusive and consistent access to an object's fields has to [i]acquire[/i] the object's intrinsic lock before accessing them, and then [i]release[/i] the intrinsic lock when it's done with them. A thread is said to [i]own[/i] the intrinsic lock between the time it has acquired the lock and released the lock. As long as a thread owns an intrinsic lock, no other thread can acquire the same lock. The other thread will block when it attempts to acquire the lock.

When a thread releases an intrinsic lock, a happens-before relationship is established between that action and any subsequent acquistion of the same lock.

[color=red][size=12]Locks In Synchronized Methods[/size][/color]
...
[color=red][size=12]Synchronized Statements[/size][/color]
...
[color=red][size=12]Reentrant Synchronization[/size][/color]
...[/size]
[/quote]

[size=11]对于以上部分,需要注意以下几点:

[b]1. 线程同步的部分为整个Synchronized同步方法,整个Synchronized同步块,或是Reentrant同步中上锁(lock())与开锁(unlock())之间的部分.[/b]

[b]2. 对于多线程synchronized同步块的锁定, 应该限制为锁定同一个Object对象: [/b]

synchronized(Object obj){} 注: 如果每个线程锁定的对象不同,则线程之间互不影响,无法达到同步效果.

synchronized同步块还可以直接锁定某个类的所有对象:

synchronized(XXX.class){} 注意没有static synchronized(XXX){}的用法.

锁定对象的线程与锁定类的线程之间互不影响.

[b]3. 对于synchronized同步方法: [/b]

①public synchronized void method(){...} 锁定当前对象
②public static synchronized void method(){...} 锁定当前类的所有对象

3.① 等价于public void method(){ synchronized(this){} }
3.② 等价于public void method(){ synchronized(this.class){} }

[b]4. Reentrant同步锁: [/b]

线程锁定靠的是Reentrant锁, 依靠调用其lock(),unlock()方法来决定何时何地执行同步.

(1) 与synchronized相比, 使用ReentrantLock(public class ReentrantLock extends Object implements Lock, Serializable),可更加精准的控制线程.

①可以判断线程持有(isHeldByCurrentThread(),isLocked())
②可以用于中断线程(lockInterruptibly())
③设置线程排队的公平性(构造方法)

(2) 专门读写文件, 可使用ReentrantReadWriteLock(public class ReentrantReadWriteLock extends Object implements ReadWriteLock, Serializable)

①该锁专门为多用户读取文件设计,因此封装了读和写之间的关系:允许多用户读(读-读)以及写文件时读(写-读),禁止读文件时写(读-写)以及写文件时再次写(写-写).
②包含两个属性readLock和writeLock, 这两个属性封装好了上述功能, 调用时直接lock(),unlock()即可.
③readLock和writeLock是通过升级(readLock -> writeLock)以及降级(writeLock -> readLock)来完成上述功能, 以下为Java API中的例子:[/size]

class CachedData {
Object data;
volatile boolean cacheValid;
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
// Recheck state because another thread might have acquired
// write lock and changed state before we did.
if (!cacheValid) {
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
rwl.writeLock().unlock(); // Unlock write, still hold read
}

use(data);
rwl.readLock().unlock();
}
}



[size=11]以下代码为自己练习操作ReentrantReadWriteLock的代码:[/size]

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* 对原始数据进行操作的类
*
* @author Vincent.zheng
*/
class Oper {

private Map<String, String> sourceMap = new HashMap<String, String>();
private ReadWriteLock lock = new ReentrantReadWriteLock(true);
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();

protected Oper() {
setSourceMap();
}

// 读取单个值
public String read(String key) {

try {
readLock.lock();
launchPeriod();
return key + ":" + sourceMap.get(key);
} finally {
readLock.unlock();
}
}

//
public String read() {
try {
readLock.lock();
launchPeriod();

StringBuffer results = new StringBuffer();
String[] str = new String[sourceMap.size()];
String[] keys = sourceMap.keySet().toArray(str);
for (String key : keys) {
results.append(key + ":" + sourceMap.get(key) + "; ");
}
return new String(results);
} finally {
readLock.unlock();
}
}

public String write(String key, String value) {

try {
writeLock.lock();
launchPeriod();
sourceMap.put(key, value);
return key + ":" + value;
} finally {
writeLock.unlock();
}
}

public String write(Map<String, String> map) {
try {
writeLock.lock();
launchPeriod();
sourceMap.putAll(map);

StringBuffer results = new StringBuffer();
String[] str = new String[map.size()];
String[] keys = map.keySet().toArray(str);
for (String key : keys) {
results.append(key + ":" + map.get(key) + "; ");
}
return new String(results);
} finally {
writeLock.unlock();
}
}

// 保证每个线程运行时间在5秒以上
private void launchPeriod() {
long currentTime = System.currentTimeMillis();
for (;;) {
if (System.currentTimeMillis() - currentTime > 5000) {
break;
}
}
}

// 原始数据
private void setSourceMap() {
for (int i = 0; i < 1000; i++) {
sourceMap.put("SourceKey" + i, "SourceValue" + i);
}
}
}

class Reader extends Thread {

public Oper oper;

public Reader(String name, Oper oper) {
super(name);
this.oper = oper;
}

public void run() {

String name = Thread.currentThread().getName();
System.out.println(name + " Start Reading");
// 读全部数据
// String results = oper.read();
// System.out.println(name + " Read=======" + results);
// 读单个随机值
String result = oper.read("SourceKey" + new Random().nextInt(1000));
System.out.println(name + " Read=======" + result);
}
}

class Writer extends Thread {

public Oper oper;

public Writer(String str, Oper oper) {
super(str);
this.oper = oper;
}

public void run() {

String name = Thread.currentThread().getName();
System.out.println(name + " Start Writing");

// 写全部数据
// String results = oper.write(getWriteData());
// System.out.println(name + " Write=======" + results);

// 写单个值
String result = oper.write("WriteSoloKeyIn" + name, "WriteSoloValueIn"
+ name);
System.out.println(name + " Write=======" + result);
System.out.println(name + " Read=======" + oper.read());
}

// 写入数据
private Map<String, String> getWriteData() {
Map<String, String> writeMap = new HashMap<String, String>();
for (int i = 0; i < 10; i++) {
writeMap.put("WriteKey" + (i + 1), "WriteValue" + (i + 1));
}
return writeMap;
}

}

public class Test {

/**
* @param args
*/
public static void main(String[] args) {

final Oper oper = new Oper();
ArrayList<Thread> list = new ArrayList<Thread>();

for (int i = 0; i < 100; i++) {
Reader reader = new Reader("Reader" + i, oper);
list.add(reader);
}

for (int i = 0; i < 10; i++) {
Writer writer = new Writer("Writer" + i, oper);
list.add(writer);
}

ArrayList<Integer> data = new ArrayList<Integer>();
for (int i = 0; i < list.size(); i++) {
data.add(i);
}
for (int i = 0; i < list.size(); i++) {
Integer random = new Random(i).nextInt(list.size());
if (data.contains(random)) {
data.remove(random);
list.get(random).start();
}
}
}
}
### Java 多线程 文件读写 实现方法 #### 使用多线程提升文件读写的性能 通过引入多线程技术可以显著提高文件读写操作的效率。当涉及到大量文件或大容量单个文件时,利用并发机制能够充分利用CPU资源,减少I/O等待时间。 对于多线程环境下安全地访问共享资源——即文件对象而言,必须采取适当措施防止竞争条件的发生。这通常意味着要对临界区施加同步控制,比如借助于`ReentrantReadWriteLock`这样的工具类[^2]。 #### 示例代码展示 下面给出了一种基于线程池模式下的异步文件读取方案: ```java import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousFileChannel; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.concurrent.ExecutionException; public class AsyncFileReader { private final Path filePath; public AsyncFileReader(String fileName){ this.filePath = Paths.get(fileName); } /** * 异步方式打开文件并读取指定长度的内容. */ public String readFile(int length) throws IOException, InterruptedException, ExecutionException { StringBuilder contentBuilder = new StringBuilder(); try (AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(filePath, StandardOpenOption.READ)) { ByteBuffer buffer = ByteBuffer.allocate(length); Integer bytesRead = fileChannel.read(buffer, 0).get(); while(bytesRead != -1 && bytesLeftToRead > 0 ) { buffer.flip(); byte[] dataBytes = new byte[buffer.remaining()]; buffer.get(dataBytes); contentBuilder.append(new String(dataBytes)); if (bytesRead < length || bytesLeftToRead <= 0 ) break; buffer.clear(); bytesRead = fileChannel.read(buffer, bytesRead).get(); } } catch(IOException e){ throw e; } return contentBuilder.toString(); } } ``` 此段程序定义了一个名为 `AsyncFileReader` 的类,它接受一个字符串参数作为待读取的目标文件路径名,并提供了一个公共的方法用于执行实际的读取工作。这里采用了 NIO.2 中新增添的功能组件 —— `AsynchronousFileChannel` 来完成非阻塞式的磁盘 I/O 操作;同时配合使用了 `Future<V>` 接口来获取最终返回的结果集[^5]。 另外一种常见的做法是在主进程中启动若干个工作子进程分别负责不同的任务单元,例如某个特定范围内的记录解析或是某几个分片文档的整体搬运等。这些辅助性的计算节点之间互不干扰却又紧密协作,共同构成了整个应用程序的核心业务逻辑框架结构。 为了确保数据的一致性和完整性,在上述过程中应当考虑加入必要的异常处理流程以及合理的超时设置策略,从而有效规避潜在的风险隐患。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值