Java学习(1)——(Demo)多线程读取文件

Java学习(1)——(Demo)多线程读取文件

原文地址:http://blog.csdn.net/yzh201612/article/details/78301762

目标:
  1. 线程1:读取txt格式文件直到结束,每读取10个字符就通知线程2执行任务;
  2. 线程2:打印线程1刚读取到的10个字符,将其中的小写字母转换成大写字母并打印更改后的10个字符。结束后通知线程1执行任务。
代码及分析:
MyBufferCache.java
/*
 * MyBufferCache.java
 * 
 * MyBufferCache类
 * 包含读文件read()和转换字符transform()方法
*/
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;

public class MyBufferCache {
    private char[] tenCharacters = new char[10];
    private boolean isEnd = false;//判断是否读完文件
    private boolean isEndPre = false;//isEnd前一状态
    public boolean isSaved = false;//判断是否已经保存了一组字符

    public boolean isEnd() {
        return isEnd;
    }

    public synchronized void read() {
        try {
            // Read the file
            File myFile = new File("MyText.txt");
            FileReader fileReader = new FileReader(myFile);
            BufferedReader reader = new BufferedReader(fileReader);

            while(!isEnd) {             
                    if(isSaved) {                       
                        wait();
                    }
                    else {                              
                            for(int i = 0; i<10; i++) {
                                int readOneCharacter;

                                do{
                                    readOneCharacter = reader.read();   //读一个字符
                                }while('\r' == (char)(readOneCharacter) || '\n' == (char)(readOneCharacter));

                                char tmp = (char)(readOneCharacter);    
                                if(readOneCharacter == -1) {    //文档中无字符后结束保存
//                                  isEndPre = isEnd;
                                    isEnd = true;
                                    break;
                                }//end if
                                tenCharacters[i] = tmp; //保存在数组内
                            }//end for
                            isSaved = true;                     
                            notifyAll();
                    }//end else                 
            }//end while
            reader.close();
        }catch(Exception ex) {
            System.out.println("Here");
            ex.printStackTrace();       
        }
    }//end read method


    public synchronized void transform() {
        try {
            while(!isEndPre) {
                if(!isSaved) {
                    wait();
                }
                else {
                    System.out.println(tenCharacters);  //打印原始数组
                    for(int j = 0; j<10; j++) {
                        if(Character.isUpperCase(tenCharacters[j])) {
                            System.out.print(Character.toLowerCase(tenCharacters[j]));
                        }
                        else{
                            System.out.print(tenCharacters[j]);
                        }
                    }
                    System.out.println();

                    tenCharacters = null;               //清空数组
                    tenCharacters = new char[10];

                    isSaved = false;
                    notifyAll();
                    isEndPre = isEnd;//放在这里而不是read()中,保证最后一组转换能够仅再执行一次
                }               
            }
        }
        catch(InterruptedException ex) {
            ex.printStackTrace();
        }
    }   
}

MyBufferCache类中有公用的缓存数组tenCharacters[],以及针对该数组的两个方法:read()读文件并保存在数组中,transform()转换数组中字符。(Domain Driven Design 领域驱动设计:每个类都应该是完备的领域对象,MyBufferCache提供了缓存数组,那么就应该提供操作该数组的想关方法)
因为两个方法需要对同一个结构(数组)操作,将会有两个线程都要修改buffer对象(见下面的ReadFile.java)。所以将会有资源竞争的情况,于是引入同步监视器解决资源竞争(同步监视器的目的:组织两个线程对同一个共享资源进行并发访问)。

线程同步问题,处理办法是同步方法。将read()和transform()用synchronized修饰。此时默认同步监视器为调用该方法的对象,即下面main方法中新建的buffer对象。于是,对于这一个buffer对象,任意时刻只能有一个线程获得对buffer的锁定,然后进入read()或transform()方法执行对缓存数组的操作,保证了多线程并发对数组读写的数据安全。
线程通讯问题。因为两个线程需要交替循环执行,所以借用Object类提供的wait()/ notify()/ notifyAll()方法实现线程间通讯。
- wait():线程调用后会释放对同步监视器的锁定,线程暂停。可以加入时间参数,指定时间后自动唤醒等待其他线程释放锁定。
- notify():唤醒在此同步监视器上等待的(随机指定)单个线程。只有当前线程释放锁定时(调用wait()),其他被唤醒的线程才会被执行。
- notifyAll():唤醒在此监视器上等待的所有线程。其余同notify()。
线程结束问题,当线程执行完操作后会自动结束。因为设定的两个方法需要循环执行,所以需要设定结束标识。read()检测是否读到文档结尾,transform()检测是否读到文章结尾以及是否已经保存在缓存数组中。


ReadAFile.java
/*
 * ReadAFile.java
 * main方法,启动两个线程
*/
public class ReadAFile {
    public static void main(String[] args) {

        MyBufferCache buffer = new MyBufferCache();

        new MyFileReader(buffer).start();
        new MyFileTranslator(buffer).start();

    }
}

在main方法里新建一个MyBuffCache对象buffer,将此对象传入两个线程。

曾出现的错误:两个线程各自生成独立对象,出现无法唤醒对方线程问题。
分析:因为独立生成的两个对象各自只有一个线程在操作,相当于两个人分别用了两把锁,各自独立。这样问题不单单是无法相互唤醒,更重要的是读取和转换操作将会分别操作两个数组,无效操作。


MyFileReader.java
/*
 * MyFileReader.java
 * 线程1
*/
public class MyFileReader extends Thread {

    private MyBufferCache buffer;//引用变量
    public MyFileReader(MyBufferCache buffer) {
        this.buffer = buffer;
    }   
    public void run() {
//      System.out.println(Thread.currentThread().getName());
        buffer.read();
    }


}
MyFileTranslator.java
/*
 * MyFileTranslator.java
 * 线程2
*/
public class MyFileTranslator extends Thread {

    private MyBufferCache buffer;//引用变量
    public MyFileTranslator(MyBufferCache buffer) {
        this.buffer = buffer;
    }
    public void run() {
//      System.out.println(Thread.currentThread().getName());
        buffer.transform();
    }   
}

两个线程,建立方法是继承自Thread类。(在《Head First Java》中不推荐这种做法,因为违背了“继承”的理念。其推荐的是借助Runnable类建立任务,然后由Thread去执行Runnable任务)
线程中声明MyBufferCache引用变量buffer。在构造方法中引入对象buffer,并将其赋值给引用变量。


效果:

MyText.txt
MyText.txt

Console
Console


理论补充:

参考:

《Head First Java》. Kathy Sierra & Bert Bates.
《疯狂Java讲义(第3版)》. 李刚.

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值