20145231第六周学习笔记

20145231 《Java程序设计》第6周学习总结

教材学习内容总结

第十章:输入/输出

InputStream与Outputstream

• 串流设计的概念

从应用程序角度看,将数据从来源取出,可以使用输入串流,将数据写入目的地,可以使用输出串流;在Java中,输入串流代表对象为java.io.InputStream实例,输出串流代表对象为java.io.OutputStream实例;

• 串流继承框架

System.inSystem.out分别代表标准输入和标准输出;

可以使用System的setIn()方法指定InputStream实例,用setOut()方法指定printStream;代码如下:

System.err为printStream实例,称为标准输出串流,用于立即显示错误信息;

FileInputStream:是InputStream的子类,可以指定文件名创建实例,一旦创建文档就开启,接着就可以用来写出数据,主要操作了InputStream的read()抽象方法,从而读取文档中的数据;

FileOutputStream:是OutputStream的子类,可以指定文件名创建实例,一旦创建文档就开启,接着就可以用来写出数据,主要操作了OutputStream中的write抽象方法,使之可写出数据到文档;

不使用,时都要用close()关闭文档;

ByteStream是InputStream的子类,可以指定byte数组创建实例,一旦创建就可以将byte数组当做数据源进行读取。ByteArrayOutputStream是OutputStream的子类,可以指定byte数组创建实例,一旦创建就可以将byte数组当做目的地写出数据;

• 串流装饰处理器

若想要为输入输出的数据作加工处理,可以使用打包器类(如:scanner);

InputStream和OutputStream的一些子类也具有打包器的作用,这些子类创建时,可以接受InputStream和OutputStream实例;

常用打包器:BufferedInputStream、BufferOutputSream(具备缓冲区作用),DataInputStream、DataOutputStream(具备数据转换处理作用),ObjectInputStream、ObjectOutputStream(具备对象串行化能力)等;代码如下:

package cc.openhome;

import java.io.*;

public class BufferedIO {
    public static void dump(InputStream src, OutputStream dest)
                              throws IOException {
        try(InputStream input = new BufferedInputStream(src);
             OutputStream output = new BufferedOutputStream(dest)) {
            byte[] data = new byte[1024];
            int length;
            while ((length = input.read(data)) != -1) {
                output.write(data, 0, length);
            }
        }
    }
}

package cc.openhome;

import java.io.*;

public class Member {
    private String number;
    private String name;
    private int age;

    public Member(String number, String name, int age) {
        this.number = number;
        this.name = name;
        this.age = age;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    @Override
    public String toString() {
        return String.format("(%s, %s, %d)", number, name, age);
    }
    
    public void save() throws IOException {
        try(DataOutputStream output = 
                new DataOutputStream(new FileOutputStream(number))) {
            output.writeUTF(number);
            output.writeUTF(name);
            output.writeInt(age);
        } 
    }
    
    public static Member load(String number) throws IOException {
        Member member;
        try(DataInputStream input = 
                new DataInputStream(new FileInputStream(number))) {
            member = new Member(
                    input.readUTF(), input.readUTF(), input.readInt());
        } 
        return member;
    }
} 

package cc.openhome;

import java.io.IOException;
import static java.lang.System.out;

public class MemberDemo {
    public static void main(String[] args) throws IOException {
        Member[] members = {
                    new Member("B1234", "Justin", 90), 
                    new Member("B5678", "Monica", 95), 
                    new Member("B9876", "Irene", 88)
        };
        for(Member member : members) {
            member.save();
        }
        out.println(Member.load("B1234"));
        out.println(Member.load("B5678"));
        out.println(Member.load("B9876"));
    }
}

import static java.lang.System.out;

public class Member2Demo {
    public static void main(String[] args) throws Exception {
        Member2[] members = {new Member2("B1234", "Justin", 90), 
                             new Member2("B5678", "Monica", 95), 
                             new Member2("B9876", "Irene", 88)};
        for(Member2 member : members) {
            member.save();
        }
        out.println(Member2.load("B1234"));
        out.println(Member2.load("B5678"));
        out.println(Member2.load("B9876"));
    }
}

字符处理类

• Reader与Writer继承架构

java.io.Reader类:抽象化了字符数据读入的来源;

java.io.Writer类:抽象化了数据写出目的地;代码如下:

FileReader:读取文档并将读到的数据转换成字符;StringWriter:将字符数据写至它最后使用toString()的方法取得字符串;代码如下:

import java.io.*;

public class CharUtil {
    public static void dump(Reader src, Writer dest) throws IOException {
        try(Reader input = src; Writer output = dest) {
            char[] data = new char[1024];
            int length;
            while((length = input.read(data)) != -1) {
                output.write(data, 0, length);
            }
        }
    }
}
package cc.openhome;

import java.io.*;

public class CharUtilDemo {
    public static void main(String[] args) throws IOException {
        FileReader reader = new FileReader(args[0]);
        StringWriter writer = new StringWriter();
        CharUtil.dump(reader, writer);
        System.out.println(writer.toString());
    }
}

• 字符处理装饰器

将字节数据转换成对应的编码字符,可以使用InputStreamReader、OutputStreamWriter对串流数据打包;代码如下:

import java.io.*;

public class CharUtil2 {
    public static void dump(Reader src, Writer dest) throws IOException {
        try(Reader input = src; Writer output = dest) {
            char[] data = new char[1024];
            int length;
            while((length = input.read(data)) != -1) {
                output.write(data, 0, length);
            }
        }
    }
    
    public static void dump(InputStream src, OutputStream dest, 
                             String charset) throws IOException {
        dump(
            new InputStreamReader(src, charset), 
            new OutputStreamWriter(dest, charset)
        );
    }

    // 采用预设编码
    public static void dump(InputStream src, OutputStream dest) 
                           throws IOException {
        dump(src, dest, System.getProperty("file.encoding"));
    }
}

提高字符输入输出效率,提供缓冲区作用:BufferedReader、BufferWriter;

printWriter:对OutStream打包,对writer打包;

第十一章:线程与并行API

线程

• 线程简介

单线程程序:启动的程序从main()程序进入点开始至结束只有一个流程;多线程程序:拥有多个流程;

java中从main()开始的流程会由主线程执行可以创建Thread实例来执行Runable实例定义的run()方法;代码如下:(龟兔赛跑)

package cc.openhome;

public class Tortoise implements Runnable {
    private int totalStep;
    private int step;

    public Tortoise(int totalStep) {
        this.totalStep = totalStep;
    }

    @Override
    public void run() {
        while (step < totalStep) {
            step++;
            System.out.printf("乌龟跑了 %d 步...%n", step);
        }
    }
}
import static java.lang.System.out;

public class TortoiseHareRace {
    public static void main(String[] args) {
        boolean[] flags = {true, false};
        int totalStep = 10;
        int tortoiseStep = 0;
        int hareStep = 0;
        out.println("龟兔赛跑开始...");
        while(tortoiseStep < totalStep && hareStep < totalStep) {
            tortoiseStep++;       
            out.printf("乌龟跑了 %d 步...%n", tortoiseStep);
            boolean isHareSleep = flags[((int) (Math.random() * 10)) % 2];
            if(isHareSleep) {
                out.println("兔子睡着了zzzz");
            } else {
                hareStep += 2;    
                out.printf("兔子跑了 %d 步...%n", hareStep);
            }
        }
    }
}
public class TortoiseHareRace2 {
    public static void main(String[] args) {
        Tortoise tortoise = new Tortoise(10);
        Hare hare = new Hare(10);
        Thread tortoiseThread = new Thread(tortoise);
        Thread hareThread = new Thread(hare);
        tortoiseThread.start();
        hareThread.start();
    }
}

• Thread与Runnable

创建Thread实例就是为JVM加装CPU,启动额外CPU就是调用实例的start()方法,额外CPU的进入点可以定义在Runable接口的run()方法中;

除了将流程这样定义,另一个撰写多线程程序的方式就就是继承Thread类,重新定义run()方法;

操作Runnable接口的好处就是较有弹性,你的类还有机会继承其他类;若继承了Thread类,通常是为了直接利用Thread中定义的一些方法;

• 线程生命周期

Daemon线程:如果一个Thread被标示为Deamon线程,在所有的非Deamon线程都结束时,JVM就会自动终止;代码如下:

public class DaemonDemo {

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true) {
                System.out.println("Orz");
            }
        });
        // thread.setDaemon(true);
        thread.start();
    }
}

Thread基本状态图:可执行、被阻断、执行中;

线程看起来但事实是同一个时间点上,一个CPU还是只能执行一个线程,只是因其不断切换且很快,所以看起来像是同时执行;

线程有其优先权,setPriority()方法设定优先权,利用多线程改进效能;

当线程使用join()加入另一线程时,另一线程会等待被加入的线程工作完毕再继续它的动作;代码如下:

线程完成run()方法后,就会进入Dead,此时不可以再调用start()方法否则会抛出IlligleThreadException;

• 关于ThreadGroup

每个线程都属于某个线程群组,线程一旦归入某个群组,就无法再更换;可以使用以下程序片段取得当前线程所属线程群组名:Thread.currentThread().getThreadGroup().getname();

使用uncoughtException()方法处理群组中某个线程出现异常未被捕捉的情况,可以重新定义此方法;
代码如下:

package cc.openhome;

public class ThreadGroupDemo {

    public static void main(String[] args) {
        ThreadGroup group = new ThreadGroup("group") {
            @Override
            public void uncaughtException(Thread thread, Throwable throwable) {
                System.out.printf("%s: %s%n", 
                        thread.getName(), throwable.getMessage());
            }
        };

        Thread thread = new Thread(group, () -> {
            throw new RuntimeException("测试例外");
        });

        thread.start();
    }
}
public class ThreadGroupDemo2 {

    public static void main(String[] args) {
        ThreadGroup group = new ThreadGroup("group");
        
        Thread thread1 = new Thread(group, () -> {
            throw new RuntimeException("thread1 测试例外");
        });
        thread1.setUncaughtExceptionHandler((thread, throwable) -> {
            System.out.printf("%s: %s%n", 
                    thread.getName(), throwable.getMessage());
        });

        Thread thread2 = new Thread(group, () -> {
            throw new RuntimeException("thread2 测试例外");
        });

        thread1.start();
        thread2.start();
    }
}

• synchronized与volatile

如果在方法上标示synchronized,则执行方法必须取得该实例的锁定,才能执行该区块内容;

可重入同步:线程取得某对象锁定后,若执行过程中又要执行synchronized,尝试取得锁定的对象来源又是同一个,则可以直接执行;

synchronized:互斥性:该区块同时间只能有一个线程,可见性:线程离开该区块后,另一线程接触到的就是上一线程改变后的对象状态;

在java中对于可见性的要求,可以使用volatile达到变量范围,在变量上声明volatile,表示变量是不稳定、易变的,也就是可能在多线程下存取,其存取一定是在共享内存中进行,代码如下:

package cc.openhome;

class Variable1 {
    static int i = 0, j = 0;

    static void one() {
        i++;
        j++;
    }

    static void two() {
        System.out.printf("i = %d, j = %d%n", i, j);
    }
}

public class Variable1Test {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            while (true) {
                Variable1.one();
            }
        });
        Thread thread2 = new Thread(() -> {
            while (true) {
                Variable1.two();
            }
        });
        
        thread1.start();
        thread2.start();
    }
}

class Variable2 {
    static int i = 0, j = 0;

    static synchronized void one() {
        i++;
        j++;
    }

    static synchronized void two() {
        System.out.printf("i = %d, j = %d%n", i, j);
    }
}

public class Variable2Test {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            while (true) {
                Variable2.one();
            }
        });
        Thread thread2 = new Thread(() -> {
            while (true) {
                    Variable2.two();
                }
        }); 
        
        thread1.start();
        thread2.start();
    }
}

class Variable3 {
    volatile static int i = 0, j = 0;

    static void one() {
        i++;
        j++;
    }

    static void two() {
        System.out.printf("i = %d, j = %d%n", i, j);
    }
}

public class Variable3Test {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            while (true) {
                Variable3.one();
            }
        });
        Thread thread2 = new Thread(() -> {
            while (true) {
                Variable3.two();
            }
        });
        thread1.start();
        thread2.start();
    }
}

• 等待与通知

调用锁定对象的wait()方法,线程会释放对象锁定,并进入对象等待集合而处于阻断状态,其他线程可以竞争对象锁定,取得锁定的线程可以执行synchronized范围的代码;

被竞争的对象调用notify()方法时,会从对象等待集合中随机通知一个线程加入排班,再次执行synchronized前,被通知的线程会与其他线程共同竞争对象锁定;代码如下:

public class Consumer implements Runnable {
    private Clerk clerk; 
    
    public Consumer(Clerk clerk) { 
        this.clerk = clerk; 
    } 
    
    public void run() { 
        System.out.println("消费者开始消耗整数......"); 
        for(int i = 1; i <= 10; i++) { 
            try {
                clerk.getProduct(); 
            } catch (InterruptedException ex) {
                throw new RuntimeException(ex);
            }
        } 
    } 
 }
public class Clerk {
    private int product = -1;

    public synchronized void setProduct(int product) throws InterruptedException {
        waitIfFull();
        this.product = product;
        System.out.printf("生产者设定 (%d)%n", this.product);
        notify();
    }

    private synchronized void waitIfFull() throws InterruptedException {
        while (this.product != -1) {
            wait();
        }
    }

    public synchronized int getProduct() throws InterruptedException {
        waitIfEmpty();
        int p = this.product;
        this.product = -1;
        System.out.printf("消费者取走 (%d)%n", p);
        notify();
        return p;
    }

    private synchronized void waitIfEmpty() throws InterruptedException {
        while (this.product == -1) {
            wait();
        }
    }
}
public class ProducerConsumerDemo {
    public static void main(String[] args) {
        Clerk clerk = new Clerk(); 
        new Thread(new Producer(clerk)).start(); 
        new Thread(new Consumer(clerk)).start(); 
    }    
}

并行API

• lock、ReadWriteLock与Condition

java.util.concurrent.locks包中提供Lock、ReadWriteLock、Condition接口以及相关操作类,可以提供类似synchronized、wait()notify()notifyall()的作用,以及更多高级功能;

Lock接口主要操作类之一为ReentrantLook,可以达到synchronized的作用,也提供额外功能;代码如下:

ReadWriteLock:如果已经有线程取得Lock对象锁定,尝试再次锁定同一Lock对象是可以的。想要锁定Lock对象,可以调用Lock()方法;

Condition接口用来搭配Lock,一个Condition对象可代表一个等待集合,可以重复调用Lock的newCondition(),取得多个Condition实例,这代表了有多个等待集合;

• 使用Executor

java.util.concurrent.Executor接口,目的是将Runnable的指定与实际如何执行分离,Executor接口只定义了一个execute();

像线程池这样类服务的行为,实际上是定义在Executor的子接口java.util.concurrent.ExecutorService当中,通用的ExecutorService由抽象类AbstractExecutorService操作,如果需要线程池的功能,则可以使用其子类java.util.concurrent.ThreadPoolExecutor;

ExecutorService还定义了submit()invokeAll()invokeAny()等方法,这些方法中出现了java.util.concurrent.Future、java.util.concurrent.Callable接口;

ScheduledExecutorService为ExecutorService的子接口,可以进行工作排程,schedule()方法用来排定Runnable或Callable实例延迟多久执行一次,并返回Future子接口ScheduledFuture的实例;

• 并行Collection简介

java.util.concurrent包中,提供一些支持并行操作的Collection子接口与操作类;

CopyOnWriteArrayList操作了List接口,这个类的实例在写入操作时,内部会建立新数组,并复制原有数组索引的参考,然后在新数组上进行写入操作,写入完成后,再将内部原参考旧数组的变量参考至新数组;

CopyOnWriteArraySet操作了Set接口,内部使用CopyOnWriteArrayList来完成Set的各种操作,因此一些特性与CopyOnWriteArrayList是相同的;

BlockingQueue是Queue的子接口,新定义了put()take()方法;代码如下:


import java.util.concurrent.BlockingQueue;

public class Producer3 implements Runnable {
    private BlockingQueue<Integer> productQueue; 
    
    public Producer3(BlockingQueue<Integer> productQueue) { 
        this.productQueue = productQueue; 
    } 
    
    public void run() { 
        System.out.println("生产者开始生产整数......"); 
        for(int product = 1; product <= 10; product++) { 
            try { 
                productQueue.put(product);
                System.out.printf("生产者提供整数 (%d)%n", product);
            } catch (InterruptedException ex) {
                throw new RuntimeException(ex);
            }
        }       
    } 
}
import java.util.concurrent.BlockingQueue;

public class Consumer3 implements Runnable {
    private BlockingQueue<Integer> productQueue; 
    
    public Consumer3(BlockingQueue<Integer> productQueue) { 
        this.productQueue = productQueue; 
    } 
    
    public void run() { 
        System.out.println("消费者开始消耗整数......"); 
        for(int i = 1; i <= 10; i++) { 
            try {
                int product = productQueue.take();
                System.out.printf("消费者消耗整数 (%d)%n", product);
            } catch (InterruptedException ex) {
                throw new RuntimeException(ex);
            }
        } 
    } 
 }
import java.util.concurrent.*;

public class ProducerConsumerDemo3 {
    public static void main(String[] args) {
        BlockingQueue queue = new ArrayBlockingQueue(1);
        new Thread(new Producer3(queue)).start(); 
        new Thread(new Consumer3(queue)).start(); 
    }    
}

教材学习中的问题和解决过程

问题一:p356页代码中的DeadLockDemo为什么会发生死结情况?

解决过程:因为两个线程在执行cooparate()方法取得目前Resource锁定后,尝试调用另一Resource的doSome(),因无法取得另一Resource的锁定而阻断。即:线程因无法同时取得两个Resource的锁定时,干脆释放已取得的锁定,就可以解决问题;

问题二:使用Condition提高效率?

解决过程:一个Condition对象可代表有一个等待集合,可以重复调用Lock的newCondition(),取得多个Condition实例,这代表了可以有多个等待集合。而p362中改写的Clerk类,因为使用了一个Condition,所以实际上也只有一个等待集合,作用将类似11.11.6节中的Clerk类。如果有两个等待集合:一个给生产者线程用,一个给消费者线程用,生产者只通知消费者等待集合,消费者只通知生产者等待集合,会比较有效率。

代码调试中的问题和解决过程

由于本周学习过程中需要理解的概念太多,对于代码的理解和掌握相较前面的学习有不足之处,只是直接输入了部分代码进行运行,所以没有太大问题。

其他(感悟、思考等,可选)

本周内容较上周更加抽象难懂,似乎开始对面向对象这个概念以及三要素之一的抽象有点感觉了,但是新的类、方法、接口太多,一时有点难以消化,只能通过代码帮助概念的理解。

代码托管截图

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 5000行 30篇 400小时
第一周 200/200 2/2 20/20
第二周 300/500 2/4 21/41
第三周 450/950 3/7 22/63
第四周 450/1365 2/9 20/83
第五周 450/1815 2/11 20/113
第六周 500/2315 2/13 20/133 了解了输入输出及线程基本概念

参考资料

原文地址:https://www.cnblogs.com/xzh20145231/p/5374554.html