java并发读写⽂件
最近在看Brian Goetz 的<<Java并发实战>>,这本书有两个版本,电⼦⼯业出版社的译本很糟糕,建议使⽤机械⼯业出版社出版出版的书籍.
在看到第三四章的时候突然想到了多线程读写⽂件,同时遇到⼀些书中没有的问题
1, 如何保证组合对象的安全性?
2, 如何判断不变性的约束条件
远光灯图标3, 如何不通过synchronized关键字和锁进⾏同步处理?
下⾯是⼀段代码, ⽤来从source 读取数据,通过多线程写⼊target⽂件中
思路:
1, 如何read/write⽂件?
2, 如何设计Reader类?
3, Reader类的是否需要状态来描述? 状态变量如何同步?
体育课教学设计
4, 如何保证当前Thread可以准确地读取当前gment?
代码综述:
package org.mushroom.multithread;
import java.io.Cloable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Objects;
import urrent.atomic.AtomicLong;
/**
* 要点:
* 1, 将⼀个⽂件分为 ,⼤⼩为 {@link Reader#gmentLength} 的 {@link Reader#gments} 个⽂件
* ,每个线程通过读取pure block来读取数据,被访问的块会通过{@link Reader#gment}标记
* ,⾃增1.
* 2, 多个线程的全局变量是final {@link Reader#source}, final {@link Reader#target}和
* {@link Reader#gment},需要保证这些变量的安全性
*/
public class Reader implements Runnable {
private static final int BYTE = 1024;
private static final long NEGATIVE_ONE = -1L;
private static final long ZERO = 0L;
private static final long ONE = 1L;
/
/ 全局变量供多线程访问块
private final AtomicLong gment = new AtomicLong(NEGATIVE_ONE);
// 单个⽂件⼤⼩
private final int gmentLength = 30 * BYTE * BYTE;
// 原始⽂件
private final File source;
// 复制后的⽂件
private final File target;
// ⽂件被分割后的块数
private final long gments;
// 最后⼀块⽂件实际⼤⼩
private final long remains;
public Reader(String sourcePath, String targetPath) throws IOException {
public Reader(String sourcePath, String targetPath) throws IOException {
this.source = new File(sourcePath);
this.target = new File(targetPath);
if (!ists()) {
ateNewFile();
春天的古诗}
//如果余数不为0, 则需要多⼀个块来存储多余的bytes,否则会丢失
if (ains != ZERO) {
} el {
}
}
/**
* run:
* 1, while true: 当前块未被访问, 从{@link Reader#gment = 0}开始第⼀次访问
* 2, {@link Reader#readBlock(RandomAccessFile, long)}从⽂件中读取数据,并返回 byte[]
* 3, {@link Reader#writeBlock(RandomAccessFile, byte[], long)},设置position后将缓冲写⼊⽂件
*/
public void run() {
RandomAccessFile reader = null;
RandomAccessFile writer = null;
try {
reader = new RandomAccessFile(source, "r");
writer = new RandomAccessFile(target, "rw");
long position = -1L;
//循环计数当前gment, 多个线程均可修改
while ((position = gment.incrementAndGet()) < gments) {
final byte[] bytes = readBlock(reader, position);
writeBlock(writer, bytes, position);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
clo(writer);
clo(reader);
}
}
private void writeBlock(RandomAccessFile writer, byte[] bytes, long position) throws IOException {
writer.ek(position * gmentLength);
writer.write(bytes);
}
/**
* 1, reader设置position
咽有几个读音
* 2, 创建缓冲数组
* 3, 将数据写⼊byte[]
* 4, 返回缓冲数组
*
* @return position 供 {@link RandomAccessFile#write(byte[])}使⽤
*/
private byte[] readBlock(RandomAccessFile reader, long position) throws IOException {
reader.ek(position * gmentLength);
final byte[] bytes = new byte[getWriteLength(position)];
return bytes;
}
/**
* 获得当前byte[]实际可写⼊长度可能是{@link Reader#gmentLength} 或者 {@link Reader#remains} */
private int getWriteLength(long position) throws IOException {
private int getWriteLength(long position) throws IOException {
if (position == gments + NEGATIVE_ONE && remains > ZERO) {
return (int) remains;
}
return gmentLength;风吹幡动
}
/**
* 关闭流的通⽤接⼝⽅法
*上海铁路博物馆
* @param cloable
*/
private void clo(Cloable cloable) {
论语取名try {
if (Null(cloable)) {
cloable.clo();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
测试代码:注意junit对多线程测试⽆能为⼒,建议使⽤groboutils或者简单地main⽅法平板电脑手机
package org.mushroom.multithread;
import java.io.IOException;
public class ReaderTest {
public static void main(String[] args) throws IOException {
final String source = "";
final String target = "";
Reader reader = new Reader(source, target);
new Thread(reader).start();
new Thread(reader).start();
new Thread(reader).start();
new Thread(reader).start();
}
}
解决⽅式:
1, 多线程读取⽂件需要从不同的地⽅读取数据,所以普通的stream不符合要求,所以选择java.io.RandomAccessFile它可任意设置⽂件cursor.并且包含read&write⽅式
2, ①reader&writer设计
多线程执⾏的是reader类的run⽅法,所以每个线程都必须有⾃⼰独⽴的reader&writer,所以reader&writer必须是线程私有的,也就是说需要把reader&writer封装在线程中,线程之间的reader&writer不能共享.
②如何处理source&target?
每个线程处理⾃⼰所负责的块,最终的块合在⼀起就是⼀个完整的target.也就是将source分为⼤⼩为gment length的gment块,但是注意,最后⼀个gment块的实际有效数据长度并⾮gment length.
③其他域设计
需要两个File来描述source⌖
需要两个变量描述source被分成多少个gments以及gment的⼤⼩gmentLength;
需要⼀个变量描述最后⼀块remain的⼤⼩;
需要⼀个共享变量gment来描述source⽂件的⽂件状态;
3, source⽂件需要⼀个gment变量来描述它被多线程处理的状态,也就是当前线程处理到哪⾥了.还有那些尚未处理
4,Thread读取过的gment可以使⽤⼀个urrent.atomic.AtomicLong类型的变量来描述它(也就是主要描述了当前块是第⼏个块), AtomicLong类的增/减操作是线程安全的.所以每⼀次读取的块的位置都是安全的.