教材学习内容总结

学习目标

  • 理解流与IO
  • 理解InputStream/OutPutStream的继承架构
  • 理解Reader/Writer继承架构
  • 会使用装饰类
  • 会使用多线程进行并发程序设计

第十章 输入、输出

首先掌握父类中方法,核心类如下:

InputStream与OutputStream

串流设计的概念
  1. java将输入\输出抽象化为串流,数据有来源及目的地,衔接两者的是串流对象

  2. 从应用程序角度来看,如果要将数据从来源取出,可以使用输入串流(java.io.InputStream 实例),如果要将数据写入目的地,可以使用输出串流(java.io.OutputStream 实例)

  1. * 通用的 dump() 方法:
  1. package week06;
  2. import java.io.*;
  3. public class IO {
  4. public static void dump(InputStream src, OutputStream dest)
  5. throws IOException {
  6. try (InputStream input = src; OutputStream output = dest) {
  7. byte[] data = new byte[1024];
  8. int length;
  9. while ((length = input.read(data)) != -1) {
  10. output.write(data, 0, length);
  11. }
  12. }
  13. }
  14. }

•read():InputStream 的 read() 方法,每次会尝试读入 byte 数组的数据,并返回实际读入的字节,只要不是 -1,就表示读取到数据

•write():OutputStream 的 write() 方法,指定要写出的 byte 数组、初始索引与数据长度

•将某个文档读入并另存为另一个文档:

  1. package week06;
  2. import java.io.*;
  3. public class Copy {
  4. public static void main(String[] args) throws IOException {
  5. IO.dump(
  6. new FileInputStream(args[0]),
  7. new FileOutputStream(args[1])
  8. );
  9. }
  10. }

•dump 方法另存文档:

  1. package week06;
  2. import java.io.*;
  3. import java.net.URL;
  4. public class Download {
  5. public static void main(String[] args) throws IOException {
  6. URL url = new URL(args[0]);
  7. InputStream src = url.openStream();
  8. OutputStream dest = new FileOutputStream(args[1]);
  9. IO.dump(src, dest);
  10. }
  11. }
串流继承架构
  1. System.in:文本模式下取得整行用户输入

  2. System.err:标准错误输出串流,用来立即显示错误信息

  3. System.setErr:重新指定标准错误输出串流

  4. setIn():使用 setIn() 方法指定 InputStream 实例,重新指定标准输入来源

  5. setOut():使用 setOut() 方法指定 PrintStream 实例,将结果输出至指定的目的地

  6. FileInputStream:可以指定文件名创建实例,一旦创建文档就开启,接着就可用来读取数据,主要操作 InputStream 的 read() 抽象方法,使之可以从文档中读取数据

  7. FileOutputStream:可以指定文件名创建实例,一旦创建文档就开启,接着就可用来写出数据,主要操作 InputStream 的 write() 抽象方法,使之可以写出数据至文档

  8. ByteArrayInputStream:可以指定 byte 数组创建实例,一旦创建就可将 byte 数组当作数据源进行读取,主要操作了 InputStream 的 read() 抽象方法,使之可从 byte 数组中读取数据

  9. ByteArrayOutputStream:可以指定 byte 数组创建实例,一旦创建就可将 byte 数组当作目的地写出数据,主要操作了 OutputStream 的 write() 抽象方法,使之可写出数据至 byte 数组

串流处理装饰器
  • BufferedInputStreamBufferedOutputStream:主要在内部提供缓冲区功能,例:
  1. package week06;
  2. import java.io.*;
  3. public class BufferedIO {
  4. public static void dump(InputStream src, OutputStream dest)
  5. throws IOException {
  6. try(InputStream input = new BufferedInputStream(src);
  7. OutputStream output = new BufferedOutputStream(dest)) {
  8. byte[] data = new byte[1024];
  9. int length;
  10. while ((length = input.read(data)) != -1) {
  11. output.write(data, 0, length);
  12. }
  13. }
  14. }
  15. }

DataInputStreamDataOutputStream:装饰InputStreamOutputStreamDataInputStreamDataOutputStream 提供读取、写入 java 基本数据类型的方法,例:

  1. package week06;
  2. import java.io.*;
  3. public class Member {
  4. private String number;
  5. private String name;
  6. private int age;
  7. public Member(String number, String name, int age) {
  8. this.number = number;
  9. this.name = name;
  10. this.age = age;
  11. }
  12. public String getNumber() {
  13. return number;
  14. }
  15. public void setNumber(String number) {
  16. this.number = number;
  17. }
  18. public String getName() {
  19. return name;
  20. }
  21. public void setName(String name) {
  22. this.name = name;
  23. }
  24. public int getAge() {
  25. return age;
  26. }
  27. public void setAge(int age) {
  28. this.age = age;
  29. }
  30. @Override
  31. public String toString() {
  32. return String.format("(%s, %s, %d)", number, name, age);
  33. }
  34. public void save() throws IOException {
  35. try(DataOutputStream output =
  36. new DataOutputStream(new FileOutputStream(number))) {
  37. output.writeUTF(number);
  38. output.writeUTF(name);
  39. output.writeInt(age);
  40. }
  41. }
  42. public static Member load(String number) throws IOException {
  43. Member member;
  44. try(DataInputStream input =
  45. new DataInputStream(new FileInputStream(number))) {
  46. member = new Member(
  47. input.readUTF(), input.readUTF(), input.readInt());
  48. }
  49. return member;
  50. }
  51. }

ObjectInputStreamObjectOutputStreamObjectInputStream 提供 readObject() 方法将数据读入为对象,ObjectOutputStream提供writeObject() 方法将对象写至目的地,例:

  1. package week06;
  2. import java.io.*;
  3. public class Member2 implements Serializable {
  4. private String number;
  5. private String name;
  6. private int age;
  7. public Member2(String number, String name, int age) {
  8. this.number = number;
  9. this.name = name;
  10. this.age = age;
  11. }
  12. public String getNumber() {
  13. return number;
  14. }
  15. public void setNumber(String number) {
  16. this.number = number;
  17. }
  18. public String getName() {
  19. return name;
  20. }
  21. public void setName(String name) {
  22. this.name = name;
  23. }
  24. public int getAge() {
  25. return age;
  26. }
  27. public void setAge(int age) {
  28. this.age = age;
  29. }
  30. @Override
  31. public String toString() {
  32. return String.format("(%s, %s, %d)", number, name, age);
  33. }
  34. public void save() throws IOException {
  35. try(ObjectOutputStream output =
  36. new ObjectOutputStream(new FileOutputStream(number))) {
  37. output.writeObject(this);
  38. }
  39. }
  40. public static Member2 load(String number)
  41. throws IOException, ClassNotFoundException {
  42. Member2 member;
  43. try(ObjectInputStream input =
  44. new ObjectInputStream(new FileInputStream(number))) {
  45. member = (Member2) input.readObject();
  46. }
  47. return member;
  48. }
  49. }

字符处理类

  • Reader与Writer 继承架构

  • 针对字符数据的读取,Java SE 提供了 java.io.Reader 类,其抽象化了字符数据读入的来源

  • 针对字符数据的写入,Java SE 提供了 java.io.Writer 类,其抽象化了数据写出的目的地

  • 使用 CharUtil.dump() 方法从来源读入字符数据、将字符数据写至目的地:

  1. package week06;
  2. import java.io.*;
  3. public class CharUtil {
  4. public static void dump(Reader src, Writer dest) throws IOException {
  5. try(Reader input = src; Writer output = dest) {
  6. char[] data = new char[1024];
  7. int length;
  8. while((length = input.read(data)) != -1) {
  9. output.write(data, 0, length);
  10. }
  11. }
  12. }
  13. }

•read():每次从Reader 读入的数据,都会先置入 char 数组中,Reader 的 read() 方法,每次会尝试读入 char 数组长度的数据,并返回实际读入的字符数,只要不是-1,就表示读取到字符

•write():使用 write() 方法,指定要写出的 byte 数组、初始索引与数据长度

•使用CharUtil.dump() 读入文档、转为字符串并显示在文本模式中:

  1. package week06;
  2. import java.io.*;
  3. public class CharUtilDemo {
  4. public static void main(String[] args) throws IOException {
  5. FileReader reader = new FileReader(args[0]);
  6. StringWriter writer = new StringWriter();
  7. CharUtil.dump(reader, writer);
  8. System.out.println(writer.toString());
  9. }
  10. }

•字符处理装饰器

InputStreamReaderOutputStreamWriter:可对串流数据打包

BufferedReaderBufferedWriter:可对ReaderWriter 提供缓冲区作用,在处理字符输入\输出时,对效率也会有所帮助

PrintWriter:可对 OutputStreamWriter 进行打包,提供 print()println()format() 方法

第十一章 线程与并行API

线程

线程

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

•多线程程序:程序有多个流程

•在 java 中,从 main() 开始的流程会由主线程执行:

  1. package week06;
  2. public class TortoiseHareRace2 {
  3. public static void main(String[] args) {
  4. Tortoise tortoise = new Tortoise(10);
  5. Hare hare = new Hare(10);
  6. Thread tortoiseThread = new Thread(tortoise);
  7. Thread hareThread = new Thread(hare);
  8. tortoiseThread.start();
  9. hareThread.start();
  10. }
  11. }

结果如下:

•在以上程序中,主线程执行 main() 定义的流程,main() 定义的流程中建立了 tortoiseThread 与 hareThread 两个线程,这两个线程会分别执行 Tortoise 与 Hare() 的 run() 定义的流程,要启动线程执行指定流程,必须调用 Thread 实例的 start() 方法

•Thread 与 Runnable

•Thread:如果想要加装主线程,就要创建 Thread 实例,要启动额外的主线程就是调用 Thread 实例的 start() 方法

•额外线程执行流程的进入点,有两种方式:

  1. •可以定义在 Runnable run() 方法中
  2. •继承 Thread 类,重新定义 run() 方法

•在 java 中,任何线程可执行的流程都要定义在 Runnable 的 run() 方法,Thread 类本身也操作了 Runnable 接口,run() 方法的操作如下:

  1. @Override
  2. public void run(){
  3. if(target != null){
  4. target.run();
  5. }
  6. }

•线程生命周期

•Daemon 线程

  1. •主线程会从 main() 方法开始执行,直到 main() 方法结束后停止 JVM
  2. •如果主线程中启动了额外线程,默认会等待被启动的所有线程都执行完 run() 方法才中止 JVM
  3. •如果一个 Thread 被标示为 Daemon 线程,在所有的非 Daemon 线程都结束时,JVM 自动就会终止
  4. •从 main() 方法开始的就是一个非 Daemin 线程,可以使用 setDaemon() 方法来设定一个线程是否为 Daemon 线程,例:
  1. package week06;
  2. public class DaemonDemo {
  3. public static void main(String[] args) {
  4. Thread thread = new Thread(() -> {
  5. while (true) {
  6. System.out.println("Orz");
  7. }
  8. });
  9. // thread.setDaemon(true);
  10. thread.start();
  11. }
  12. }
  1. •使用 isDaemon() 方法可以判断线程是否为 Daemon 线程

•Thread 基本状态图

  1. •在调用 Thread 实例 start() 方法后,基本状态为可执行(Runnable)、被阻断(Blocked)、执行中(Running
  2. •状态间的转移如下图:

  1. •线程看起来像是同时执行,但事实上同一时间点上,一个 CPU 只能执行一个线程,只是 CPU 会不断切换线程,且切换动作很快,所以看起来像是同时执行
  2. setPriority():线程有其优先权,可使用 Thread setPriority() 方法设定优先权,可设定值为110,默认是5,超出110外的设定值会抛出 IllegalArgumentException
  3. •数字越大优先权越高,排版器越优先排入 CPU,如果优先权相同,则输流执行
  4. •改进效能的方式:运用多线程,当某线程进入 Blocked 时,让另一线程排入 CPU 执行,避免 CPU 空闲下来
  5. interrupt():一个进入 Blocked 状态的线程,可以由另一个线程调用,该线程的 interrupt() 方法,让它离开 Blocked 状态,例:
  1. package week06;
  2. public class InterruptedDemo {
  3. public static void main(String[] args) {
  4. Thread thread = new Thread(() -> {
  5. try {
  6. Thread.sleep(99999);
  7. } catch (InterruptedException ex) {
  8. System.out.println("我醒了XD");
  9. }
  10. });
  11. thread.start();
  12. thread.interrupt(); // 主线程调用thread的interrupt()
  13. }
  14. }

•安插线程

  1. join():如果A线程正在运行,流程中允许B线程加入,等到B线程执行完毕后再继续A线程流程,则可以使用 join() 方法完成这个需求,例:
  1. package cc.openhome;
  2. import static java.lang.System.out;
  3. public class JoinDemo {
  4. public static void main(String[] args) throws InterruptedException {
  5. out.println("Main thread 开始...");
  6. Thread threadB = new Thread(() -> {
  7. out.println("Thread B 开始...");
  8. for (int i = 0; i < 5; i++) {
  9. out.println("Thread B 执行...");
  10. }
  11. out.println("Thread B 将结束...");
  12. });
  13. threadB.start();
  14. threadB.join(); // Thread B 加入 Main thread 流程
  15. out.println("Main thread将结束...");
  16. }
  17. }

运行结果如下:

•停止线程

  1. •线程完成 run() 方法后,就会进入 Dead ,进入 Dead 的线程不可以再次调用 start() 方法,否则会抛出 IllegalThreadStateException
  2. •如果要停止线程,最好自行操作,让线程跑完应有的流程,而非调用 Thread stop() 方法
  3. stop():直接调用 Thread stop() 方法,将不理会所设定的释放、取得锁定流程,线程会直接释放所有已锁定对象,这有可能使对象陷入无法预期状态

•ThreadGroup

•每个线程都属于某个线程群组

•每个线程产生时,都会归入某个线程群组,这视线程在那个群组中产生,如果没有指定,则归入产生该子线程的线程群组,也可以自行指定线程群组,线程一旦归入某个群组,就无法再更换

•java.lang.ThreadGroup:管理群组中的线程,可以使用以下方式产生群组,并在产生线程时指定所属群组:

  1. ThreadGroup group1 = new ThreadGroup("group1");
  2. ThreadGroup group2 = new ThreadGroup("group2");
  3. Thread thread1 = new Thread(group1,"group1's member");
  4. Thread thread2 = new Thread(group2,"group2's member");

•interrupt():中断群组中所有线程

•setMaxpriority():设定群组中所有线程最大优先权(本来就有更高优先权的线程不受影响)

•enumerate():一次取得群组中所有线程:

  1. Thread[] threads = new Thread[threadGroup1.activeCount()];
  2. threadGroup1.enumerate(threads);

•activeCount():取得群组的线程数量,enumerate() 方法要传入 Thread 数组,这会将线程对象设定至每个数组索引

•uncaughtException():群组中某个线程发生异常而未捕捉时,JVM 会调用此方法进行处理。如果 ThreadGroup 有父 ThreadGroup,就会调用父 ThreadGroup 的 uncaughtException() 方法,否则看看异常是否为 ThreadDeath 实例,若是则什么都不做,若不是则调用异常的 printStrackTrace(),如果必须定义 ThreadGroup 中的线程异常处理行为,可重新定义此方法,例:

  1. package week06;
  2. public class ThreadGroupDemo {
  3. public static void main(String[] args) {
  4. ThreadGroup group = new ThreadGroup("group") {
  5. @Override
  6. public void uncaughtException(Thread thread, Throwable throwable) {
  7. System.out.printf("%s: %s%n",
  8. thread.getName(), throwable.getMessage());
  9. }
  10. };
  11. Thread thread = new Thread(group, () -> {
  12. throw new RuntimeException("测试异常");
  13. });
  14. thread.start();
  15. }
  16. }

•uncaughtException() 方法第一个参数可取得发生异常的线程实例,第二个参数可取得异常对象

•在JDK5 之后,如果 ThreadGroup 中的线程发生异常,uncaughtException() 方法处理顺序为:

  1. •如果 ThreadGroup 有父 ThreadGroup,就会调用父 ThreadGroup uncaughtException() 方法
  2. •否则,看看 Thread 是否使用 setUncaughtExceptionHandler() 方法设定 Thread.Uncaught-ExceptionHandler 实例,有的话就会调用其 uncaughtException() 方法
  3. •否则,看看异常是否为 ThreadDeath 实例,若“是”则什么都不做,若“否”则调用异常的 printfStractTrace()

•对于线程本身未捕捉的异常,可以自行指定处理方式,例:

  1. package week06;
  2. public class ThreadGroupDemo2 {
  3. public static void main(String[] args) {
  4. ThreadGroup group = new ThreadGroup("group");
  5. Thread thread1 = new Thread(group, () -> {
  6. throw new RuntimeException("thread1 测试例外");
  7. });
  8. thread1.setUncaughtExceptionHandler((thread, throwable) -> {
  9. System.out.printf("%s: %s%n",
  10. thread.getName(), throwable.getMessage());
  11. });
  12. Thread thread2 = new Thread(group, () -> {
  13. throw new RuntimeException("thread2 测试异常");
  14. });
  15. thread1.start();
  16. thread2.start();
  17. }
  18. }

•synchronized 与 volatile

•synchronized

  1. •每个对象都会有个内部锁定,或称为监控锁定。被标示为 synchronized 的区块将会被监控,任何线程要执行 synchronized 区块都必须先取得指定的对象锁定
  2. •如果在方法上标示 synchronized,则执行方法必须取得该实例的锁定
  3. •线程若因尝试执行 synchronized 区块而进入 Blocked,在取得锁定之后,会先回到 Runnable 状态,等待 CPU 排版器排入 Running 状态
  4. java synchronized 提供的是可重入同步,也就是线程取得某对象锁定后,若执行过程中又要执行 synchronized,尝试取得锁定的对象来源又是同一个,则可以直接执行
  5. •有些资源在多线程下彼此交叉取用,有可能造成死结,例:
  1. package week06;
  2. class Resource {
  3. private String name;
  4. private int resource;
  5. Resource(String name, int resource) {
  6. this.name = name;
  7. this.resource = resource;
  8. }
  9. String getName() {
  10. return name;
  11. }
  12. synchronized int doSome() {
  13. return ++resource;
  14. }
  15. synchronized void cooperate(Resource resource) {
  16. resource.doSome();
  17. System.out.printf("%s 整合 %s 的资源%n",
  18. this.name, resource.getName());
  19. }
  20. }
  21. public class DeadLockDemo {
  22. public static void main(String[] args) {
  23. Resource resource1 = new Resource("resource1", 10);
  24. Resource resource2 = new Resource("resource2", 20);
  25. Thread thread1 = new Thread(() -> {
  26. for (int i = 0; i < 10; i++) {
  27. resource1.cooperate(resource2);
  28. }
  29. });
  30. Thread thread2 = new Thread(() -> {
  31. for (int i = 0; i < 10; i++) {
  32. resource2.cooperate(resource1);
  33. }
  34. });
  35. thread1.start();
  36. thread2.start();
  37. }
  38. }

•volatile

  1. synchronized 要求达到的所标示区块的互斥性与可见性,互斥性是指 synchronized 区块同时间只能有一个线程,可见性是指线程离开 synchronized 区块后,另一线程接触到的就是上一线程改变后的对象状态
  2. •在java中对于可见性的要求,可以使用 volatile 达到变量范围,例:
  1. package cc.openhome;
  2. class Variable1 {
  3. static int i = 0, j = 0;
  4. static void one() {
  5. i++;
  6. j++;
  7. }
  8. static void two() {
  9. System.out.printf("i = %d, j = %d%n", i, j);
  10. }
  11. }
  12. public class Variable1Test {
  13. public static void main(String[] args) {
  14. Thread thread1 = new Thread(() -> {
  15. while (true) {
  16. Variable1.one();
  17. }
  18. });
  19. Thread thread2 = new Thread(() -> {
  20. while (true) {
  21. Variable1.two();
  22. }
  23. });
  24. thread1.start();
  25. thread2.start();
  26. }
  27. }
  1. •可以在变量上声明 volatile,表示变量是不稳定的、易变的,也就是可能在多线程下存取,这保证变量的可见性,也就是若有线程变动了变量值,另一线程一定可以看到变更。被标示为 volatile 的变量,不允许线程快取,变量值的存取一定是在共享内存中进行,如:
  1. package week06;
  2. class Variable3 {
  3. volatile static int i = 0, j = 0;
  4. static void one() {
  5. i++;
  6. j++;
  7. }
  8. static void two() {
  9. System.out.printf("i = %d, j = %d%n", i, j);
  10. }
  11. }
  12. public class Variable3Test {
  13. public static void main(String[] args) {
  14. Thread thread1 = new Thread(() -> {
  15. while (true) {
  16. Variable3.one();
  17. }
  18. });
  19. Thread thread2 = new Thread(() -> {
  20. while (true) {
  21. Variable3.two();
  22. }
  23. });
  24. thread1.start();
  25. thread2.start();
  26. }
  27. }
  1. volatile 保证的是单一变数的可见性,线程对变量的存取一定是在共享内存中,不会在自己的内存空间中快取变量,线程对共享内存中变量的存取,另一线程一定看得到

•等待与通知

•wait()、notify()、notifyAll() 是 Object 定义的方法,可以通过这三个方法控制线程释放对象的锁定,或者通知线程参与锁定竞争

•wait():执行 synchronized 范围的程序代码期间,若要调用锁定对象的 wait() 方法,线程会释放对象锁定,并进入对象等待集合而处于阻断状态,其他线程可以竞争对象锁定,取得锁定的线程可以执行 synchronized 范围的程序代码。wait() 可以指定等待时间,时间到之后线程会再次加入排班,如果指定时间0或不指定,则线程会持续等待,只到被中断或是告知可以参与排班

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

•notifyAll():如果调用 notifyAll(),所有等待集合中的线程都会被通知参与排班,这些线程会与其他线程共同竞争对象锁定

并行API

•Lock、ReadWriteLock 与 Condition

•Lock

  1. Lock 接口主要操作类之一为 ReentrantLock,可以达到synchronized 的作用,也提供额外的功能,例:
  1. package week06;
  2. import java.util.Arrays;
  3. import java.util.concurrent.locks.*;
  4. public class ArrayList<E> {
  5. private Lock lock = new ReentrantLock();
  6. private Object[] elems;
  7. private int next;
  8. public ArrayList(int capacity) {
  9. elems = new Object[capacity];
  10. }
  11. public ArrayList() {
  12. this(16);
  13. }
  14. public void add(E elem) {
  15. lock.lock();
  16. try {
  17. if (next == elems.length) {
  18. elems = Arrays.copyOf(elems, elems.length * 2);
  19. }
  20. elems[next++] = elem;
  21. } finally {
  22. lock.unlock();
  23. }
  24. }
  25. public E get(int index) {
  26. lock.lock();
  27. try {
  28. return (E) elems[index];
  29. } finally {
  30. lock.unlock();
  31. }
  32. }
  33. public int size() {
  34. lock.lock();
  35. try {
  36. return next;
  37. } finally {
  38. lock.unlock();
  39. }
  40. }
  41. }
  1. •想要锁定 Lock 对象,可以调用其 lock 方法,只有取得 Lock 对象锁定的线程,才可以继续往后执行程序代码,要接触锁定,可以调用 Lock 对象的 unlock()
  2. Lock 接口还定义了tryLock() 方法,如果线程调用 tryLock() 可以取得锁定会返回 true,若无法取得锁定并不会发生阻断,而是返回 false,例:
  1. package week06;
  2. import java.util.concurrent.locks.*;
  3. class Resource {
  4. private ReentrantLock lock = new ReentrantLock();
  5. private String name;
  6. Resource(String name) {
  7. this.name = name;
  8. }
  9. void cooperate(Resource res) {
  10. while (true) {
  11. try {
  12. if (lockMeAnd(res)) {
  13. System.out.printf("%s 整合 %s 的资源%n", this.name, res.name);
  14. break;
  15. }
  16. } finally {
  17. unLockMeAnd(res);
  18. }
  19. }
  20. }
  21. private boolean lockMeAnd(Resource res) {
  22. return this.lock.tryLock() && res.lock.tryLock();
  23. }
  24. private void unLockMeAnd(Resource res) {
  25. if (this.lock.isHeldByCurrentThread()) {
  26. this.lock.unlock();
  27. }
  28. if (res.lock.isHeldByCurrentThread()) {
  29. res.lock.unlock();
  30. }
  31. }
  32. }
  33. public class NoDeadLockDemo {
  34. public static void main(String[] args) {
  35. Resource res1 = new Resource("resource1");
  36. Resource res2 = new Resource("resource2");
  37. Thread thread1 = new Thread(() -> {
  38. for (int i = 0; i < 10; i++) {
  39. res1.cooperate(res2);
  40. }
  41. });
  42. Thread thread2 = new Thread(() -> {
  43. for (int i = 0; i < 10; i++) {
  44. res2.cooperate(res1);
  45. }
  46. });
  47. thread1.start();
  48. thread2.start();
  49. }
  50. }

•ReadWriteLock

  1. ReadWriteLock 接口定义了读取锁定与写入锁定行为,可以使用 readLock()、writeLock() 方法返回 Lock 操作对象
  2. ReentrantReadWriteLock.ReadLock 操作了Lock 接口,调用其 lock() 方法时,若没有任何 ReentrantReadWriteLock.WriteLock 调用过 lock() 方法,也就是没有任何写入锁定时,就可以取得读取锁定
  3. ReentrantReadWriteLock.WriteLock 操作了 Lock 接口,调用其 lock() 方法时,若没有任何 ReentrantReadWriteLock.ReadLock ReentrantReadWriteLock.WriteLock 调用过 lock() 方法,也就是没有任何读取或写入锁定时,才可以取得写入锁定

•StampedLock

  1. StampedLock:支持乐观读取操作,例:
  1. package week06;
  2. import java.util.Arrays;
  3. import java.util.concurrent.locks.*;
  4. public class ArrayList3<E> {
  5. private StampedLock lock = new StampedLock();
  6. private Object[] elems;
  7. private int next;
  8. public ArrayList3(int capacity) {
  9. elems = new Object[capacity];
  10. }
  11. public ArrayList3() {
  12. this(16);
  13. }
  14. public void add(E elem) {
  15. long stamp = lock.writeLock();
  16. try {
  17. if (next == elems.length) {
  18. elems = Arrays.copyOf(elems, elems.length * 2);
  19. }
  20. elems[next++] = elem;
  21. } finally {
  22. lock.unlockWrite(stamp);
  23. }
  24. }
  25. public E get(int index) {
  26. long stamp = lock.tryOptimisticRead();
  27. Object elem = elems[index];
  28. if (!lock.validate(stamp)) {
  29. stamp = lock.readLock();
  30. try {
  31. elem = elems[index];
  32. } finally {
  33. lock.unlockRead(stamp);
  34. }
  35. }
  36. return (E) elem;
  37. }
  38. public int size() {
  39. long stamp = lock.tryOptimisticRead();
  40. int size = next;
  41. if (!lock.validate(stamp)) {
  42. stamp = lock.readLock();
  43. try {
  44. size = next;
  45. } finally {
  46. lock.unlockRead(stamp);
  47. }
  48. }
  49. return size;
  50. }
  51. }
  1. validate():验证戳记是不是被其他排他性锁定取得了,如果是的话返回 false,如果戳记是 0 也会返回 false

•Condition

  1. Condition 接口用来搭配 Lock,最基本的用法就是达到 Object wait()、notify()、notifyAll() 方法的作用
  2. Condition 使用样例:
  1. package week06;
  2. import java.util.concurrent.locks.Condition;
  3. import java.util.concurrent.locks.Lock;
  4. import java.util.concurrent.locks.ReentrantLock;
  5. public class Clerk {
  6. private int product = -1;
  7. private Lock lock = new ReentrantLock();
  8. private Condition condition = lock.newCondition();
  9. public void setProduct(int product) throws InterruptedException {
  10. lock.lock();
  11. try {
  12. waitIfFull();
  13. this.product = product;
  14. System.out.printf("生产者设定 (%d)%n", this.product);
  15. condition.signal();
  16. } finally {
  17. lock.unlock();
  18. }
  19. }
  20. private void waitIfFull() throws InterruptedException {
  21. while (this.product != -1) {
  22. condition.await();
  23. }
  24. }
  25. public int getProduct() throws InterruptedException {
  26. lock.lock();
  27. try {
  28. waitIfEmpty();
  29. int p = this.product;
  30. this.product = -1;
  31. System.out.printf("消费者取走 (%d)%n", p);
  32. condition.signal();
  33. return p;
  34. } finally {
  35. lock.unlock();
  36. }
  37. }
  38. private void waitIfEmpty() throws InterruptedException {
  39. while (this.product == -1) {
  40. condition.await();
  41. }
  42. }
  43. }
  1. signal():要通知等待集合中的一个线程,则可以调用 signal() 方法
  2. signalAll():如果要通知所有等待集合中的线程,可以调用 signalAll()
  3. •一个Condition 对象可代表有一个等待集合,可以重复调用 Lock newCondition(),取得多个Condition 实例,这代表了可以有多个等待集合

•Executor

•从JDK5 开始,定义了 java.util.concurrent.Executor 接口,目的是将 Runnable 的指定与实际如何执行分离

•Executor 接口只定义了一个 execute() 方法:

  1. package java.util.concurrent;
  2. public interface Executor{
  3. void execute(Runnable command);
  4. }

•ThreadPoolExecutor

  1. •根据不同的线程池需求,ThreadPoolExecutor 拥有数种不同构造函数可供使用,不过通常会使用 java.util.concurrent.Executors newCachedThreadPool()、newFixedThreadPool() 静态方法来创建 ThreadPoolExecutor 实例,例:
  1. package week06;
  2. import java.net.URL;
  3. import java.util.concurrent.ExecutorService;
  4. import java.util.concurrent.Executors;
  5. public class Download3 {
  6. public static void main(String[] args) throws Exception {
  7. URL[] urls = {
  8. new URL("http://openhome.cc/Gossip/Encoding/"),
  9. new URL("http://openhome.cc/Gossip/Scala/"),
  10. new URL("http://openhome.cc/Gossip/JavaScript/"),
  11. new URL("http://openhome.cc/Gossip/Python/")
  12. };
  13. String[] fileNames = {
  14. "Encoding.html",
  15. "Scala.html",
  16. "JavaScript.html",
  17. "Python.html"
  18. };
  19. ExecutorService executorService = Executors.newCachedThreadPool();
  20. new Pages(urls, fileNames, executorService).download();
  21. executorService.shutdown();
  22. }
  23. }

•ScheduledThreadPoolExecutor

•ScheduledExecutorService 的操作类 ScheduledThreadPoolExecutor 为 ThreadPoolExecutor 的子类,具有线程池与排程功能,例:

  1. package cc.openhome;
  2. import java.util.concurrent.*;
  3. public class ScheduledExecutorServiceDemo {
  4. public static void main(String[] args) {
  5. ScheduledExecutorService service
  6. = Executors.newSingleThreadScheduledExecutor();
  7. service.scheduleWithFixedDelay(
  8. () -> {
  9. System.out.println(new java.util.Date());
  10. try {
  11. Thread.sleep(2000); // 假设这个工作会进行两秒
  12. } catch (InterruptedException ex) {
  13. throw new RuntimeException(ex);
  14. }
  15. }, 2000, 1000, TimeUnit.MILLISECONDS);
  16. }
  17. }

•ForkJoinPool

•java.util.ForkJoinPool:解决分而治之的问题

•在分而治之需要结合并行的情况下,可以使用 ForkJoinTask,其操作了 Future 接口,可以让你在未来取得耗时工作的执行结果

•ForkJoinPool 与其他的 ExecutorService 操作不同的地方在于,它实现了工作窃取演算,其建立的线程如果完成手边任务,会尝试寻找并执行其他任务建立的子任务,让线程保持忙碌状态,有效利用处理器的能力

•并行Collection

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

•BllockingQueu 是 Queue 的子接口,新定义了 put() 与 take() 等方法,线程若调用 put() 方法,在队列已满的情况下会被阻断,线程若调用 take() 方法,在队列为空的情况下会被阻断,例:

  1. package week06;
  2. import java.util.concurrent.BlockingQueue;
  3. public class Producer3 implements Runnable {
  4. private BlockingQueue<Integer> productQueue;
  5. public Producer3(BlockingQueue<Integer> productQueue) {
  6. this.productQueue = productQueue;
  7. }
  8. public void run() {
  9. System.out.println("生产者开始生产整数......");
  10. for(int product = 1; product <= 10; product++) {
  11. try {
  12. productQueue.put(product);
  13. System.out.printf("生产者提供整数 (%d)%n", product);
  14. } catch (InterruptedException ex) {
  15. throw new RuntimeException(ex);
  16. }
  17. }
  18. }
  19. }

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

问题

P325的代码每次运行的结果为什么是不一样的?

解决

教材上该段代码如下:

  1. package week06;
  2. import static java.lang.System.out;
  3. public class TortoiseHareRace {
  4. public static void main(String[] args) {
  5. boolean[] flags = {true, false};
  6. int totalStep = 10;
  7. int tortoiseStep = 0;
  8. int hareStep = 0;
  9. out.println("龟兔赛跑开始...");
  10. while(tortoiseStep < totalStep && hareStep < totalStep) {
  11. tortoiseStep++;
  12. //乌龟走一步
  13. out.printf("乌龟跑了 %d 步...%n", tortoiseStep);
  14. boolean isHareSleep = flags[((int) (Math.random() * 10)) % 2];
  15. //兔子随机睡觉
  16. if(isHareSleep) {
  17. out.println("兔子睡着了zzzz");
  18. } else {
  19. hareStep += 2;
  20. //如果兔子不睡觉,就走两步
  21. out.printf("兔子跑了 %d 步...%n", hareStep);
  22. }
  23. }
  24. }
  25. }

因为程序中设置了兔子随机睡觉,如果兔子不睡觉就将 hareStep 递增2,表示兔子走两步,只要兔子或乌龟其中一个走完10步就离开循环,根据兔子睡觉的随机性,结果不同。

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

书上的代码没有什么问题~

关于输入输出流的应用自己写了一些小程序^ _ ^

NO.1

  1. import java.io.*;
  2. /**
  3. * Created by XiaYihua on 2017/1/30.
  4. */
  5. public class Test0130 {
  6. public static void main(String[] args) {
  7. File file = new File("text.txt");
  8. if (file.exists()) {
  9. System.err.println("exist");
  10. try {
  11. FileInputStream fis = new FileInputStream(file);
  12. InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
  13. BufferedReader br = new BufferedReader(isr);
  14. String line;
  15. while ((line = br.readLine()) != null) {
  16. System.out.println(line);
  17. }
  18. br.close();
  19. isr.close();
  20. fis.close();
  21. } catch (FileNotFoundException e) {
  22. e.printStackTrace();
  23. } catch (UnsupportedEncodingException e) {
  24. e.printStackTrace();
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. try {
  30. File newfile = new File("newtext.txt");
  31. FileOutputStream fos = new FileOutputStream(newfile);
  32. OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
  33. BufferedWriter bw = new BufferedWriter(osw);
  34. bw.write("ride\n");
  35. bw.write("like\n");
  36. bw.write("funk\n");
  37. bw.write("queue\n");
  38. bw.write("stack\n");
  39. bw.close();
  40. osw.close();
  41. fos.close();
  42. System.out.println("Write Complete!");
  43. } catch (FileNotFoundException e) {
  44. e.printStackTrace();
  45. } catch (UnsupportedEncodingException e) {
  46. e.printStackTrace();
  47. } catch (IOException e) {
  48. e.printStackTrace();
  49. }
  50. }
  51. }

运行结果:



newtext.txt文件中变为:



这里感受了一波System.err.println("exist");的作用~

两者都是输出流,没有本质区别,err是运行期异常和错误反馈的输出流的方向。

但是,需要注意的是: (1)System.out.println 能重定向到别的输出流,这样的话你在屏幕上将看不到打印的东西了,如输出到 一个txt的log日志中. (2)而System.err.println只能在屏幕上实现打印,即使你重定向了也一样。

如果要说最直接的区别就是:如果使用err打印字符串,在IDEA的console中会显示成红色的~如上图所示。

NO.2按字节读取数据流

  1. /**
  2. * Created by XiaYihua on 2017/1/31.
  3. */
  4. import java.io.*;
  5. public class Test0131_2 {
  6. public static void main(String[] args) {
  7. try {
  8. FileInputStream fis = new FileInputStream("text.txt");
  9. byte[] input = new byte[40];
  10. fis.read(input);
  11. String inputString = new String(input, "UTF-8");
  12. System.out.println(inputString);
  13. fis.close();
  14. } catch (FileNotFoundException e) {
  15. e.printStackTrace();
  16. } catch (UnsupportedEncodingException e) {
  17. e.printStackTrace();
  18. } catch (IOException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }

运行结果截图:

  • 前一个程序是为了按行读取数据(文本文件),而这个程序是按字节读取~

本周代码托管截图

https://git.oschina.net/xyh20145226/java-besti-is-2015-2016-2-20145226-2/tree/master/src?dir=1&filepath=src&oid=521ad5dd7b419812d6b342bb970ba6c33cd6c7b4&sha=45fff2cdd5eba8ebe469473c18d6c37d17c87529

寒假已全部完成。

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 5000行 30篇 400小时
第二周 200/400 1/3 12/52
预备作业 0/400 1/4 15/67
第三周 500/900 1/5 10/77
第四周 500/1500 1/6 15/92
第五周 500/2000 1/7 20/112
第六周 500/2500 2/9 20/132

参考资料

20145226夏艺华 《Java程序设计》第6周学习总结的更多相关文章

  1. 20145226夏艺华 JAVA预备作业1

    博客阅读总结 关于师生关系: 学生和老师之间我觉得关系时多元化的,不能拘泥于单独的一种关系:灌输与被灌输,教授与被教授--我认为,在不同的课程阶段,师生之间的关系都可以发生变化.前期的老师更像是一个指 ...

  2. 20145226夏艺华 《Java程序设计》实验报告四

    实验四 Android开发基础 实验内容 基于Android Studio开发简单的Android应用并部署测试 了解Android组件.布局管理器的使用 掌握Android中事件处理机制 Andro ...

  3. 20145226夏艺华 网络对抗技术 EXP9 web安全基础实践

    20145226夏艺华 网络对抗技术 EXP9 web安全基础实践 !!!免考项目:wannacry病毒分析+防护 一.实验后回答问题 SQL注入攻击原理,如何防御 攻击原理 "SQL注入& ...

  4. 20145226夏艺华 网络对抗技术EXP8 WEB基础实践

    20145226夏艺华 网络对抗技术EXP8 WEB基础实践 实验问题回答 1.什么是表单? 表单在网页中主要负责数据采集功能.一个表单有三个基本组成部分: 表单标签:这里面包含了处理表单数据所用CG ...

  5. 20145226夏艺华 网络对抗技术 EXP7 网络欺诈技术防范

    20145226夏艺华 网络对抗技术 EXP7 网络欺诈技术防范 实践内容 本实践的目标理解常用网络欺诈背后的原理,以提高防范意识,并提出具体防范方法. · 简单应用SET工具建立冒名网站 · ett ...

  6. 20145226夏艺华 Exp6 信息搜集与漏洞扫描

    20145226夏艺华 Exp6 信息搜集与漏洞扫描 基础问题回答 哪些组织负责DNS,IP的管理? · 全球根服务器均由美国政府授权的ICANN统一管理,负责全球的域名根服务器.DNS和IP地址管理 ...

  7. 20145226夏艺华 网络对抗技术EXP4 恶意代码分析

    20145226夏艺华 网络对抗技术EXP4 恶意代码分析(未完成版) 回答问题 (1)如果在工作中怀疑一台主机上有恶意代码,但只是猜想,所有想监控下系统一天天的到底在干些什么.请设计下你想监控的操作 ...

  8. 20145226夏艺华《网络对抗》第一次实验拓展:shellcode注入+return-to-libc

    20145226夏艺华<网络对抗>第一次实验拓展:shellcode注入+return-to-libc shellcode注入实践 编写shellcode 编写shellcode已经在之前 ...

  9. 20155312 2016-2017-2 《Java程序设计》第九周学习总结

    20155312 2016-2017-2 <Java程序设计>第九周学习总结 课堂内容总结 两个类有公用的东西放在父类里. 面向对象的三要素 封装 继承 多态:用父类声明引用,子类生成对象 ...

  10. 20155213 2016-2017-2 《Java程序设计》第九周学习总结

    20155213 2016-2017-2 <Java程序设计>第九周学习总结 教材学习内容总结 第十六章 JDBC(Java DataBase Connectivity)即java数据库连 ...

随机推荐

  1. java杂项

    简单介绍==和equals区别==是判断两个变量或实例是不是指向同一个内存空间equals是判断两个变量或实例所指向的内存空间的值是不是相同 final, finally, finalize的区别fi ...

  2. mysql按日期分组统计数据

    最近在做一个招聘网时,需要显示一个月内企业招聘信息的发布数量,按日期分组统计,刚开始是直接从源数据库表里面进行group by,但这样子就出现日期不连续的问题了,我想要的效果是,若当天没有数据,则显示 ...

  3. app流畅度测试--使用FPS Meter

    1.FFPS Meter是一款非常实用的小软件,能够用数字实时显示安卓界面的每秒帧数,非常直观.此外,FPS Meter还可以显示最大帧数.最小帧数以及平均帧数,用来评价安卓流畅度极具价值.由于涉及到 ...

  4. Codeforces 893F(主席树+dfs序)

    在子树内和距离不超过k是一个二维限制,容易想到主席树,但主席树显然没法查最小值,因为不满足区间可减.kdtree和二维线段树可以干这事,但肯定会T飞.但事实上我们的问题有一个特殊性:对某个点x,查询其 ...

  5. MySQL 大表备份、改表

    0.背景: 需要对一个千万行数据的表新增字段,具体操作: a.dump 数据 b.delete 数据 c.alter 表 MySQL  版本为5.5,alter表时MySQL会锁表:表行数虽多,当数据 ...

  6. C 函数——Day04

    C 函数 函数是一组一起执行一个任务的语句.每个 C 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数. 您可以把代码划分到不同的函数中.如何划分代码到不同的函数 ...

  7. MT【120】保三角函数

    评:1.这里处理第三个函数时用到$ab-a-b=(a-1)(b-1)-1$是处理$ab,a+b$之间加减的常见变形. 2.第二个函数$g(x)=sinx,x\in(0,\frac{5\pi}{6})$ ...

  8. 【刷题】洛谷 P4234 最小差值生成树

    题目描述 给定一个标号为从 \(1\) 到 \(n\) 的.有 \(m\) 条边的无向图,求边权最大值与最小值的差值最小的生成树. 输入输出格式 输入格式: 第一行两个数 \(n, m\) ,表示图的 ...

  9. BZOJ4870 [Shoi2017]组合数问题 【组合数 + 矩乘】

    题目链接 BZOJ4870 题解 \[ans = \sum\limits_{i = 0}^{\infty}{nk \choose ik + r} \pmod p\] 发现实际是求 \[ans = \s ...

  10. C#线程篇---线程池如何管理线程(6完结篇)

    C#线程基础在前几篇博文中都介绍了,现在最后来挖掘一下线程池的管理机制,也算为这个线程基础做个完结. 我们现在都知道了,线程池线程分为工作者线程和I/O线程,他们是怎么管理的? 对于Microsoft ...