OnJava进阶卷学习笔记

目录

第一章、枚举类型

1.13 新特性:switch中的箭头语法

1.15 新特性:将switch作为表达式

1.16 新特性:智能转型

1.17 新特性:模式匹配

1.17.1 违反里氏替换原则

第二章、对象传递和返回

 第三章、集合主题

3.9 可选操作

不支持操作

第四章、注解

4.1.2 元注解

4.2编写注解处理器

4.2.1 注解元素

 第五章、并发编程

5.7 并行流

5.7.1 parallel()并发灵丹妙药

5.8 创建和运行任务

5.8.3 生成结果

5.9 终止长时间运行的任务

5.10 CompletableFuture

5.10.1 基本用法

5.10.2 其他操作

5.10.3 合并多个CompletableFuture

5.10.5 异常

5.11 死锁

第六章 底层并发

6.1.1 最佳线程数

“工作窃取”线程池(WorkStealingPool)

6.2 捕获异常

6.3 共享资源

6.4 volatile关键字

6.4.1 字分裂

6.4.2 可见性

6.4.3 重排序和先行发生

6.5 原子性

6.6 临界区

第七章: Java IO 系统


第一章、枚举类型

1.13 新特性:switch中的箭头语法

        在JDK14时新增了这个新特性,使用这个新特性,不需要在每个case后都写一个break来防止case一直往下走。

public class Main {
    public static void main(String[] args) {
        newcase(1);
        newcase(2);
        newcase(3);
        newcase(4);
    }

    public static void newcase(int i) {
        switch (i) {
            case 1 -> System.out.println("one");
            case 2 -> System.out.println("two");
            case 3 -> System.out.println("three");
            default -> System.out.println("default");
        }
    }
}

1.15 新特性:将switch作为表达式

        在JDK14 switch语句可以有返回值。使用旧的switch代码也可以实现返回值,但是需要添加yield关键字。

public class Main {
    public static void main(String[] args) {
        System.out.println(newcase(1));
        System.out.println(newcase(2));
        System.out.println(newcase(3));
        System.out.println(newcase(4));
        System.out.println(old(1));
        System.out.println(old(2));
        System.out.println(old(3));
        System.out.println(old(4));
    }

    public static int newcase(int i) {
       return switch (i) {
            case 1 -> 1;
            case 2 -> 2;
            case 3 -> 3;
            default -> 0;
        };
    }

    public static int old(int i) {
        return switch (i) {
            case 1 : yield 1;
            case 2 : yield 2;
            case 3 : yield 3;
            default : yield 0;
        };
    }
}

        case -> 后面可以跟多行表达式,需要用花括号括起来,并且需要使用yield关键字声明返回值。

   public static int newcase(int i) {
       return switch (i) {
            case 1 -> {
                System.out.println("cse 1 , return 1");
                yield 1;
            }
            case 2 -> {
                System.out.println("cse 2 , return 2");
                yield 2;
            }
            case 3 -> {
                System.out.println("cse 3 , return 3");
                yield 3;
            }
            default -> {
                System.out.println("cse default , return 0");
                yield 0;
            }
        };
    }

1.16 新特性:智能转型

public class Main {
    public static void main(String[] args) {
        newCase("test");
        old("test");
    }

    public static void newCase(Object str) {
        if (str instanceof String s && s.length() > 1) {
            System.out.println(s.length());
        }
    }

    public static void old(Object str) {
        if (str instanceof String) {
            String s = (String) str;
            if (s.length() > 1) {
                System.out.println(s.length());
            }
        }
    }
}

1.17 新特性:模式匹配

1.17.1 违反里氏替换原则

        先来看一下代码。

    class Pet {
        void feed() {
            System.out.println("Pet feed");
        }
    }

    class Dog extends Pet {
        void walk() {
            System.out.println("Dog walk");
        };
    }

    class Fish extends Pet {
        void changeWater() {
            System.out.println("Fish changeWater");
        }
    }

      Dog和Fish都继承了Pet,但是他们都没有重写父类的方法,而且都新加了一个父类没有的方法。显然Dog和Fish已经不能用于完美替代代码中出现的Pet(将Dog或者Fish转换为Pet,则Dog的walk方法和Fish的Pet方法将不能使用)。

        模式匹配允许违反里氏替换原则,而不产生不可控的代码。下面的代码使用模式匹配来实现根据不同子类来调用子类的特有方法。

package com.hl;

import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        Stream.of(new Dog(), new Fish())
                .forEach(p -> careFor(p));
    }

    static public void careFor(Pet p) {
        switch (p) {
            case Dog d -> d.walk();
            case Fish f -> f.changeWater();
            case Pet p1 -> p.feed();
        };
    }

    static class Pet {
        void feed() {
            System.out.println("Pet feed");
        }
    }

    static class Dog extends Pet {
        void walk() {
            System.out.println("Dog walk");
        };
    }

    static class Fish extends Pet {
        void changeWater() {
            System.out.println("Fish changeWater");
        }
    }
}

        switch(p)中的p成为选择器表达式,在模式匹配诞生之前,选择器表达式只能是基本类型(char,byte,short或int),对应的包装类型为(Character、Byte、Short或Integer)、String或者enum类型。有了模式匹配,选择器表达式可以支持任何引用类型。

        上面的代码中,如果缺少case Pet p1,代码会出现编译错误。因为编译器识别到代码switch没有覆盖所有可能的输入值。可以使用下面的方式解决这个问题。

import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        Stream.of(new Dog(), new Fish())
                .forEach(p -> careFor(p));
    }

    static public void careFor(Pet p) {
        switch (p) {
            case Dog d -> d.walk();
            case Fish f -> f.changeWater();
        };
    }

    sealed interface Pet {
        void feed();
    }

    static final class Dog implements Pet {
        void walk() {
            System.out.println("Dog walk");
        };

        @Override
        public void feed() {
            
        }
    }

    static final class Fish implements Pet {
        void changeWater() {
            System.out.println("Fish changeWater");
        }

        @Override
        public void feed() {
            
        }
    }
}

        使用sealed关键字,指定Pet只能配Dog和Fish继承,所有即使没有case Pet也不会报编译错误。

         sealed关键字介绍:OnJava基础卷 10.12 新特性:密封类和密封接口

第二章、对象传递和返回

        Java中,给方法传递对象参数实际上传递的是对象的引用,如果修改了方法内部的对象的内容,那么方法外面的对象的内容也会被修改,因为他们都是同一个引用。可以使用3种方式来解决这个问题。

  1. 克隆:可以使用Object.clone()实现,不过该方法是一个浅拷贝,需要再自行克隆对象下的所有引用字段来实现深拷贝。(p63-p74)
  2. 序列化:将对象序列化存储,再反序列化成为对象,则相当与将对象拷贝了一次。但是序列化比克隆慢一个数量级。(p74-p76)
  3. 不可变对象:将对象下的所有字段都设计为只能读取,不能修改。如果需要修改,是通过new一个新对象,然后修改这个新对象并返回。这样的话,如果修改频繁,会导致创建许多临时对象。可以创建一个可以修改的伴生类来解决这个问题(p86-p91)

 第三章、集合主题

3.9 可选操作

不支持操作

        使用Arrays.asList()方法和Collections.unmodifibaleList()方法生成的集合是一个“不可修改”的集合。

public class Main {
    public static void main(String[] args) {
        List<String> arraysList = Arrays.asList("A B C D E F G ".split(" "));
        System.out.println("-----------arraysList modifyTest--------------");
        modifyTest(arraysList);
        System.out.println("-----------newList modifyTest---------------");
        modifyTest(new ArrayList<>(arraysList));
        System.out.println("-----------unmodifiableList modifyTest------------");
        modifyTest(Collections.unmodifiableList(new ArrayList<>(arraysList)));
    }
    private static void modifyTest(List<String> strs) {
        ArrayList<String> copyList = new ArrayList<>(strs);
        ArrayList<String> subList = new ArrayList<>(strs.subList(2, 4));
        check("retainAll", () -> strs.retainAll(copyList));
        check("subList retainAll", () -> strs.retainAll(subList));
        check("removeAll", () -> strs.removeAll(copyList));
        check("clear", () -> strs.clear());
        check("add", () -> strs.add("X"));
        check("addAll", () -> strs.addAll(copyList));
        check("remove", () -> strs.remove("C"));
        check("set", () -> strs.set(1, "X"));
    }

    private static void check(String desc, Runnable runnable) {
        try {
            runnable.run();
            System.out.println(desc + " run success");
        } catch (Exception e) {
            System.out.println(desc + " run exception, " + e);
        }
    }
}


    /**
     * Output:
     *-----------arraysList modifyTest--------------
     * retainAll run success
     * subList retainAll run exception, java.lang.UnsupportedOperationException: remove
     * removeAll run exception, java.lang.UnsupportedOperationException: remove
     * clear run exception, java.lang.UnsupportedOperationException
     * add run exception, java.lang.UnsupportedOperationException
     * addAll run exception, java.lang.UnsupportedOperationException
     * remove run exception, java.lang.UnsupportedOperationException: remove
     * set run success
     * -----------newList modifyTest---------------
     * retainAll run success
     * subList retainAll run success
     * removeAll run success
     * clear run success
     * add run success
     * addAll run success
     * remove run success
     * set run success
     * -----------unmodifiableList modifyTest------------
     * retainAll run exception, java.lang.UnsupportedOperationException
     * subList retainAll run exception, java.lang.UnsupportedOperationException
     * removeAll run exception, java.lang.UnsupportedOperationException
     * clear run exception, java.lang.UnsupportedOperationException
     * add run exception, java.lang.UnsupportedOperationException
     * addAll run exception, java.lang.UnsupportedOperationException
     * remove run exception, java.lang.UnsupportedOperationException
     * set run exception, java.lang.UnsupportedOperationException
     */

        从输出结果可以看出,arrayList方法和unmodifibaleList都是不可修改的,但是arrayList中retainAll一个完全相同的集合时和使用set方法修改元素时是执行成功的。

        这是因为Arrays.asList()方法会返回一个底层由固定大小的数组支撑的List,所以只有那些不修改List底层数组大小的操作是允许的。

        Collections.unmodifiableList()方法将集合包装到一个代理中,如果执行所有修改集合的方法,都会抛出UnsupportedOperationException。

第四章、注解

        下面是一个注解示例

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    int value() default 0;
}

        @Targe和@Retention是元注解(后面详细介绍)。

        注解通常包含一些可以设定值的元素,例如@Test中的value,如果在使用@Test不设定value的值,则value默认为0。 

        没有任何元素的注解被称为标记注解。

        如果注解的元素的值的名称为value,则在使用注解给value赋值时可以省略“名-值”的形式,而直接通过@Test(10)指定value的值。

    @Test(10)
    public void test() {

    }

4.1.2 元注解

        Java中定义了5个元注解。

注解效果
@Target

该注解可应用的地方:

CONSTRUCTION-构造器声明

FILED-字段声明(包含枚举常量)

LOCAL-VARIABLE-本地变量声明

METHOD-方法声明

PACKAGE-包声明

TYPE-类、接口(包括注解类型)或枚举声明

@Retetion

注解信息可以保存多久:

SOURCE-注解会被编译器丢弃

CLASS-注解在类文件中可被编译器使用,但会被虚拟机丢弃

RUNTIME-注解在运行时仍被虚拟机保存,因此可以通过反射读取到注解信息

@Document在javadoc中引入注解
@Inherited允许子类继承父注解
@Repeatable可以多次应用于同一个声明(JAVA8)

4.2编写注解处理器

        如果没有工具来读取注解,那它并不会带来什么实际性的作用。下面是一个@Test的注解处理器的例子。

public class Main {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        runTest(new Main());
    }

    private static void runTest(Main main) throws InvocationTargetException, IllegalAccessException {
        Method[] methods = main.getClass().getMethods();
        for (Method method : methods) {
            Test annotation = method.getAnnotation(Test.class);
            if (annotation != null && annotation.value() > 10) {
                method.invoke(main);
            }
        }
    }

    @Test(10)
    public void test1() {
        System.out.println("Annotation Test1");
    }

    @Test(11)
    public void test2() {
        System.out.println("Annotation Test2");
    }
}

4.2.1 注解元素

        注解所允许的元素类型:

  • 所有的基本类型(int、float、boolean)
  • String
  • Class
  • enum
  • Annotation
  • 以上任何类型的数组

 第五章、并发编程

  •         并发是指如何正确、高效地控制共享资源。

        同时处理多个任务,即不必等待一个任务完成就能开始处理其他任务。并发解决的是阻塞问题,即一个任务必须等待一个非其可控的外部条件满足后才能继续执行,最常见的例子是I/O,一个任务必须要等待输入才能执行(即被阻塞),类似的场景称为I/O密集型问题。

        I/O密集型问题通常是指那些需要频繁进行磁盘读写、网络通信等I/O操作的程序或任务。这些任务的特点是CPU往往处于空闲状态,因为它们在等待外部I/O操作完成。

  •         并行是指如何利用更多的资源来产生更快的响应

        同时在多处执行多个任务。并行解决的是所谓的计算密集型问题,即通过把任务分成多个部分,并在多个处理器(线程)上执行,从而提升程序运行的速度。

       计算密集型问题是指那些主要依赖于处理器速度和计算能力来解决的问题。这些问题通常涉及大量的数学运算、逻辑操作或数据处理,而较少受数据输入输出速度的限制。

       具体实例有:加密与解密、大数据分析。    

       这两个概念混淆的主要原因是,很多编程语言(包括Java)使用了相同的机制--线程--来实现并发和并行。

并发的新定义

        并发是一系列聚焦于如何减少等待并提升性能的技术。

        重点在于”减少等待“。不论运行在多少机器上,只有某种等待发生了,优化才有收益。比如请求I/O资源并且立刻就成功了,那么就没有延迟,也就不需要优化了。再比如在多处理器上运行多任务程序,如果每台处理器都在满负荷运行,任务间没有相互等待,尝试提高吞吐量就毫无意义。唯一需要应用并发的时机是程序中某处地方被迫等待。

        如果有很多任务,而处理器只有一个,那么处理器要承受额外的任务切换带来的性能损耗,这时并发可能反而会使系统变得更慢。

        对于I/O密集型问题,处理器经常需要等待外部I/O条件而处于空闲状态,这时,即使是单处理器系统,并发也能带来好处。它可以从一个等待中(被阻塞)的任务切换到另一个已经准备好的任务,来提高处理器使用率,从而提高运行速度。

        该书的作者对于Java并发的态度是,除非不万不得已,否则不要轻易使用Java并发。

个人理解:

        并发的侧重点是,处理器在处理一个任务时,如果遇到阻塞则处理器直接切换去处理其他可处理的任务,而不会一直处于阻塞状态等待外部条件。

        并行的侧重点是将一个任务分成多个部分,同时在多个处理器执行。

5.7 并行流

        Java流中使用.parallel()时,会使用内部分流器(spliterrator)将流自动分割成若干份,并且并行执行分割的流。所以可以通过使用.parallel轻松开启并行执行来提升速度。

public class Main {
    public static void main(String[] args) {
        sumStream(100000);
        parallel(100000);
    }

    private static void sumStream(int size) {
        long start = System.nanoTime();
        LongStream.rangeClosed(0, size).sum();
        long expend = NANOSECONDS.toMillis(System.nanoTime() - start);
        System.out.println("Sum stream: " + expend);
    }

    private static void parallel(int size) {
        long start = System.nanoTime();
        LongStream.rangeClosed(0, size).parallel().sum();
        long expend = NANOSECONDS.toMillis(System.nanoTime() - start);
        System.out.println("Sum parallel: " + expend);
    }
}
/**
 * Output:
 * Sum stream: 210
 * Sum parallel: 21
 */

5.7.1 parallel()并发灵丹妙药

        虽然使用parallel reduce比reduce快,但是basicSum没用使用并行执行却比parallel reduce快。

package com.test;

import java.util.Arrays;
import java.util.function.LongSupplier;
import static java.util.concurrent.TimeUnit.NANOSECONDS;

public class Main {
    public static void main(String[] args) {
        int size = 100000000;
        Long[] longs = new Long[size];
        Arrays.parallelSetAll(longs, i -> (long) i);
        checkExpend("basicSum", () -> basicSum(longs));
        checkExpend("reduce", () -> Arrays.stream(longs).reduce(0L, Long::sum));
        checkExpend("parallel reduce", () -> Arrays.stream(longs).parallel().reduce(0L, Long::sum));
    }

    private static long basicSum(Long[] longs) {
        long sum = 0L;
        for (int i = 0; i < longs.length; i++) {
            sum =+ longs[i];
        }
        return sum;
    }

    public static void checkExpend(String id, LongSupplier longSupplier) {
        long start = System.nanoTime();
        longSupplier.getAsLong();
        long expend = NANOSECONDS.toMillis(System.nanoTime() - start);
        System.out.println(id + ": " + expend);
    }

    /**
     * OutPut:
     * basicSum: 48
     * reduce: 1376
     * parallel reduce: 372
     */
}

5.7.2 parallel()和limit()的作用

package com.test;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {
    private static Deque<String> trace = new ConcurrentLinkedDeque<>();

    public static void main(String[] args) throws IOException {
        List<Integer> collect = Stream.generate(new IntFactory())
                .parallel()
                .limit(10)
                .collect(Collectors.toList());
        System.out.println(collect);
        Files.write(Paths.get("PSP2.txt"), trace);
    }

    static class IntFactory implements Supplier<Integer> {
        private AtomicInteger count = new AtomicInteger();

        @Override
        public Integer get() {
            trace.add(Thread.currentThread().getName() + ": " + count.get());
            return count.getAndIncrement();
        }
    }
    /**
     * Output:
     * [1, 9, 4, 8, 6, 0, 2, 7, 5, 3]
     */
}
PSP2.txt

0: ForkJoinPool.commonPool-worker-4: 0
1: main: 0
2: ForkJoinPool.commonPool-worker-2: 0
3: ForkJoinPool.commonPool-worker-3: 0
4: ForkJoinPool.commonPool-worker-5: 0
5: ForkJoinPool.commonPool-worker-7: 0
6: ForkJoinPool.commonPool-worker-4: 1
7: ForkJoinPool.commonPool-worker-4: 7
8: ForkJoinPool.commonPool-worker-3: 7
9: ForkJoinPool.commonPool-worker-7: 7
10: ForkJoinPool.commonPool-worker-5: 7
11: main: 7
12: ForkJoinPool.commonPool-worker-2: 7
13: ForkJoinPool.commonPool-worker-4: 9
14: ForkJoinPool.commonPool-worker-6: 0
15: ForkJoinPool.commonPool-worker-1: 0

        运行上面的代码后 ,你会发现IntFactory.get()被调用了16次。当你让它以并行方式生成流时,实际是在让所有的线程尽可能的调用get()方法,加上limit()意味着你只是想要这个get()方法结果的其中一些。

        这种情况下,使用IntStream.range()方法生成更合理。

package com.test;

import java.io.IOException;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {
    private static Deque<String> trace = new ConcurrentLinkedDeque<>();

    public static void main(String[] args) throws IOException {
        List<Integer> list = IntStream.range(0, 30)
                .peek(i -> System.out.println(Thread.currentThread().getName() + ": " + i))
                .limit(10)
                .parallel()
                .boxed()
                .collect(Collectors.toList());
        System.out.println(list);
    }

    static class IntFactory implements Supplier<Integer> {
        private AtomicInteger count = new AtomicInteger();

        @Override
        public Integer get() {
            trace.add(Thread.currentThread().getName() + ": " + count.get());
            return count.getAndIncrement();
        }
    }
    /**
     * Output:
     * ForkJoinPool.commonPool-worker-4: 7
     * ForkJoinPool.commonPool-worker-1: 4
     * ForkJoinPool.commonPool-worker-2: 9
     * ForkJoinPool.commonPool-worker-6: 5
     * main: 8
     * ForkJoinPool.commonPool-worker-7: 0
     * ForkJoinPool.commonPool-worker-5: 6
     * ForkJoinPool.commonPool-worker-3: 1
     * ForkJoinPool.commonPool-worker-4: 2
     * ForkJoinPool.commonPool-worker-1: 3
     * [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
     */
}

5.8 创建和运行任务

        Executors中提供了多个创建不同线程池的静态方法:

  • newFixedThreadPool(int nThreads): 创建一个固定线程数的线程池。
  • newCachedThreadPool(): 创建一个可缓存的线程池。根据情况自动创建线程和重用已经创建的线程。
  • newSingleThreadExecutor(): 创建一个单线程执行器。
  • newScheduledThreadPool(int corePoolSize): 创建一个支持定时及周期性任务执行的线程池。

例子:

  1. Fixed Thread Pool
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小为 3 的线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // 提交任务给线程池
        for (int i = 0; i < 10; i++) {
            int index = i;
            executor.submit(() -> {
                System.out.println("Task " + index + " is running in " + Thread.currentThread().getName());
            });
        }
        
        // 关闭线程池
        executor.shutdown();
    }
}

        2.Cached Thread Pool

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个缓存线程池
        ExecutorService executor = Executors.newCachedThreadPool();
        
        // 提交任务给线程池
        for (int i = 0; i < 10; i++) {
            int index = i;
            executor.submit(() -> {
                System.out.println("Task " + index + " is running in " + Thread.currentThread().getName());
            });
        }
        
        // 关闭线程池
        executor.shutdown();
    }
}

        3. Single Thread Executor

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadExecutorExample {
    public static void main(String[] args) {
        // 创建一个单线程执行器
        ExecutorService executor = Executors.newSingleThreadExecutor();
        
        // 提交任务给线程池
        for (int i = 0; i < 10; i++) {
            int index = i;
            executor.submit(() -> {
                System.out.println("Task " + index + " is running in " + Thread.currentThread().getName());
            });
        }
        
        // 关闭线程池
        executor.shutdown();
    }
}

        4. Scheduled Thread Pool

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个定时线程池
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
        
        // 提交一个任务,在延迟 2 秒后执行
        scheduler.schedule(() -> {
            System.out.println("Task is running in " + Thread.currentThread().getName());
        }, 2, TimeUnit.SECONDS);
        
        // 提交一个任务,每 1 秒执行一次
        scheduler.scheduleAtFixedRate(() -> {
            System.out.println("Fixed rate task is running in " + Thread.currentThread().getName());
        }, 0, 1, TimeUnit.SECONDS);
        
        // 提交一个任务,在上一个任务结束后的 1 秒后执行
        scheduler.scheduleWithFixedDelay(() -> {
            System.out.println("Fixed delay task is running in " + Thread.currentThread().getName());
        }, 0, 1, TimeUnit.SECONDS);
        
        // 关闭线程池
        scheduler.shutdown();
    }
}

5.8.3 生成结果

        使用Callable能够获取线程返回的结果。

package com.test;

import java.util.concurrent.*;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long start = System.nanoTime();
        Future<Integer> future = Executors.newCachedThreadPool()
                .submit(new myCallable());
        System.out.println("Task submitted");
        System.out.println(future.get());

        long expend = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start);
        System.out.println(expend + "s");
    }

    public static class myCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            int sum = IntStream.rangeClosed(0, 100).sum();
            Thread.sleep(30000);
            System.out.println(Thread.currentThread().getName() + ": " + sum);
            return sum;
        }
    }

    /**
     * Output:
     * Task submitted
     * pool-1-thread-1: 5050
     * 5050
     * 30s
     */
}

        但是Future.get()会阻塞等待任务执行完毕才能获取到结果,所以它只是将等待任务完成的问题推迟了。Futrue被认为是一个无效的解决方案。因此现在人们更推荐使用Java8 CompleteFuture(后面介绍).

5.9 终止长时间运行的任务

        Java最初的设计提供了某种机制来中断(interrupt)正在运行的任务,中断机制在阻塞方面存在一些问题。中断任务是混乱复杂的,因为你必须理解所有可能导致中断发生的状态,以及可能导致的数据丢失。

        所以中断任务最好的方法是设置一个任务定期检查的标识,由此任务可以通过自己的关闭流程来优雅的终止。

// concurrent/QuittableTask.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
import java.util.concurrent.atomic.AtomicBoolean;
import onjava.Nap;

public class QuittableTask implements Runnable {
  final int id;
  public QuittableTask(int id) { this.id = id; }
  private AtomicBoolean running =
    new AtomicBoolean(true);
  public void quit() { running.set(false); }
  @Override public void run() {
    while(running.get())                  // [1]
      new Nap(0.1);
    System.out.print(id + " ");           // [2]
  }
}
// concurrent/QuittingTasks.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
import java.util.*;
import java.util.stream.*;
import java.util.concurrent.*;
import onjava.Nap;

public class QuittingTasks {
  public static final int COUNT = 150;
  public static void main(String[] args) {
    ExecutorService es =
      Executors.newCachedThreadPool();
    List<QuittableTask> tasks =
      IntStream.range(1, COUNT)
        .mapToObj(QuittableTask::new)
        .peek(qt -> es.execute(qt))
        .collect(Collectors.toList());
    new Nap(1);
    tasks.forEach(QuittableTask::quit);
    es.shutdown();
  }
}
/* Output:
11 23 20 12 24 16 19 15 35 147 32 27 7 4 28 31 8 83 3 1
13 9 5 2 6 18 14 17 21 25 22 26 29 30 33 34 37 41 38 46
45 49 50 53 57 58 54 69 104 112 40 73 74 115 116 70 119
77 81 56 85 78 82 111 86 90 48 89 36 108 107 44 55 52
43 60 63 59 64 71 68 67 75 76 72 79 80 84 87 88 66 91
10 95 65 96 94 92 62 100 61 93 47 39 51 99 103 128 123
127 124 140 120 139 136 135 143 148 144 105 102 131 101
132 98 97 149 137 134 42 106 110 109 114 133 113 117
118 130 129 126 121 125 122 138 141 145 142 146
*/

5.10 CompletableFuture

5.10.1 基本用法

        可以通过CompletableFuture.completableFuture()来包装一个对象

package com.test;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long start = System.nanoTime();
        CompletableFuture<IntFactory> cf = CompletableFuture.completedFuture(new IntFactory());
        IntFactory intFactory = cf.get();
        System.out.println(intFactory.get());

        long expend = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
        System.out.println(expend + "ms");
    }

    public static class IntFactory implements Supplier<Integer> {
        private AtomicInteger count = new AtomicInteger();
        @Override
        public Integer get() {
            return count.getAndIncrement();
        }
    }

    /**
     * Output:
     * 0
     * 2ms
     */
}

        使用comletableFuture将IntFactory包装起来后,便可以通过CompletableFuture的方法操作IntFactory。

        使用thenApply(Function)会按顺序同步执行IntFactory.get()

package com.test;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
        long start = System.nanoTime();
        CompletableFuture.completedFuture(new IntFactory())
                .thenApply(IntFactory::get)
                .thenApply(IntFactory::get)
                .thenApply(IntFactory::get)
                .thenApply(IntFactory::get)
                .thenApply(IntFactory::get);

        long expend = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
        System.out.println(expend + "ms");
    }

    public static class IntFactory implements Supplier<IntFactory> {
        private AtomicInteger count = new AtomicInteger();
        @Override
        public IntFactory get() {
            System.out.println("count: " + count);
            count.getAndIncrement();
            return this;
        }
    }

    /**
     * Output:
     * count: 0
     * count: 1
     * count: 2
     * count: 3
     * count: 4
     * 12ms
     */
}

        使用thenApplyAsync(Function) 可以实现异步执行。调用thenApplyAsync(Function)后,会立即返回,并且在后台继续执行Function,Main线程不会等待Function执行完成,而是直接继续进行Main线程的其他处理。

        cf.join()会等待任务全部执行完成,否则Main线程会在任务提交完成后立马终止。

package com.test;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
        long start = System.nanoTime();
        CompletableFuture<IntFactory> cf = CompletableFuture.completedFuture(new IntFactory())
                .thenApplyAsync(IntFactory::get)
                .thenApplyAsync(IntFactory::get)
                .thenApplyAsync(IntFactory::get)
                .thenApplyAsync(IntFactory::get)
                .thenApplyAsync(IntFactory::get);
        System.out.println("submit complete: " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start) + "ms");
        cf.join();
        long expend = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
        System.out.println("execute complete: " + expend + "ms");
    }

    public static class IntFactory implements Supplier<IntFactory> {
        private AtomicInteger count = new AtomicInteger();
        @Override
        public IntFactory get() {
            System.out.println("count: " + count);
            count.getAndIncrement();
            return this;
        }
    }

    /**
     * Output:
     * count: 0
     * count: 1
     * count: 2
     * count: 3
     * count: 4
     * submit complete: 5ms
     * execute complete: 9ms
     */
}

5.10.2 其他操作

  • complete(value):尝试设置 CompletableFuture 的结果,如果已经完成,则不会改变现有结果。
  • obtrudeValue(value):强制设置 CompletableFuture 的结果,即使已经完成,也会覆盖现有结果。

使用 complete() 可以确保不会意外覆盖已有结果,而 obtrudeValue() 则适用于需要无条件设置结果的特殊场景。

// concurrent/CompletableUtilities.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
package onjava;
import java.util.concurrent.*;

public class CompletableUtilities {
  // Get and show value stored in a CF:
  public static void showr(CompletableFuture<?> c) {
    try {
      System.out.println(c.get());
    } catch(InterruptedException
            | ExecutionException e) {
      throw new RuntimeException(e);
    }
  }
  // For CF operations that have no value:
  public static void voidr(CompletableFuture<Void> c) {
    try {
      c.get(); // Returns void
    } catch(InterruptedException
            | ExecutionException e) {
      throw new RuntimeException(e);
    }
  }
}
// concurrent/CompletableOperations.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
import java.util.concurrent.*;
import static onjava.CompletableUtilities.*;

public class CompletableOperations {
  static CompletableFuture<Integer> cfi(int i) {
    return
      CompletableFuture.completedFuture(
        Integer.valueOf(i));
  }
  public static void main(String[] args) {
    showr(cfi(1)); // Basic test
    voidr(cfi(2).runAsync(() ->
      System.out.println("runAsync")));
    voidr(cfi(3).thenRunAsync(() ->
      System.out.println("thenRunAsync")));
    voidr(CompletableFuture.runAsync(() ->
      System.out.println("runAsync is static")));
    showr(CompletableFuture.supplyAsync(() -> 99));
    voidr(cfi(4).thenAcceptAsync(i ->
      System.out.println("thenAcceptAsync: " + i)));
    showr(cfi(5).thenApplyAsync(i -> i + 42));
    showr(cfi(6).thenComposeAsync(i -> cfi(i + 99)));
    CompletableFuture<Integer> c = cfi(7);
    c.obtrudeValue(111);
    showr(c);
    showr(cfi(8).toCompletableFuture());
    c = new CompletableFuture<>();
    c.complete(9);
    showr(c);
    c = new CompletableFuture<>();
    c.cancel(true);
    System.out.println("cancelled: " +
      c.isCancelled());
    System.out.println("completed exceptionally: " +
      c.isCompletedExceptionally());
    System.out.println("done: " + c.isDone());
    System.out.println(c);
    c = new CompletableFuture<>();
    System.out.println(c.getNow(777));
    c = new CompletableFuture<>();
    c.thenApplyAsync(i -> i + 42)
      .thenApplyAsync(i -> i * 12);
    System.out.println("dependents: " +
      c.getNumberOfDependents());
    c.thenApplyAsync(i -> i / 2);
    System.out.println("dependents: " +
      c.getNumberOfDependents());
  }
}
/* Output:
1
runAsync
thenRunAsync
runAsync is static
99
thenAcceptAsync: 4
47
105
111
8
9
cancelled: true
completed exceptionally: true
done: true
java.util.concurrent.CompletableFuture@1629346[Complete
d exceptionally]
777
dependents: 1
dependents: 2
*/

37b251e02beb4774a39fd02df7c41366.jpeg 

5.10.3 合并多个CompletableFuture

b057c366bb2b4d0e9bfedc43fa2b9151.jpeg

// concurrent/Workable.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
import java.util.concurrent.*;
import onjava.Nap;

public class Workable {
  String id;
  final double duration;
  public Workable(String id, double duration) {
    this.id = id;
    this.duration = duration;
  }
  @Override public String toString() {
    return "Workable[" + id + "]";
  }
  public static Workable work(Workable tt) {
    new Nap(tt.duration); // Seconds
    tt.id = tt.id + "W";
    System.out.println(tt);
    return tt;
  }
  public static CompletableFuture<Workable>
  make(String id, double duration) {
    return
      CompletableFuture.completedFuture(
        new Workable(id, duration))
      .thenApplyAsync(Workable::work);
  }
}
// concurrent/DualCompletableOperations.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
import java.util.concurrent.*;
import static onjava.CompletableUtilities.*;

public class DualCompletableOperations {
  static CompletableFuture<Workable> cfA, cfB;
  static void init() {
    cfA = Workable.make("A", 0.15);
    cfB = Workable.make("B", 0.10); // Always wins
  }
  static void join() {
    cfA.join();
    cfB.join();
    System.out.println("*****************");
  }
  public static void main(String[] args) {
    init();
    voidr(cfA.runAfterEitherAsync(cfB, () ->
      System.out.println("runAfterEither")));
    join();

    init();
    voidr(cfA.runAfterBothAsync(cfB, () ->
      System.out.println("runAfterBoth")));
    join();

    init();
    showr(cfA.applyToEitherAsync(cfB, w -> {
      System.out.println("applyToEither: " + w);
      return w;
    }));
    join();

    init();
    voidr(cfA.acceptEitherAsync(cfB, w -> {
      System.out.println("acceptEither: " + w);
    }));
    join();

    init();
    voidr(cfA.thenAcceptBothAsync(cfB, (w1, w2) -> {
      System.out.println("thenAcceptBoth: "
        + w1 + ", " + w2);
    }));
    join();

    init();
    showr(cfA.thenCombineAsync(cfB, (w1, w2) -> {
      System.out.println("thenCombine: "
        + w1 + ", " + w2);
      return w1;
    }));
    join();

    init();
    CompletableFuture<Workable>
      cfC = Workable.make("C", 0.08),
      cfD = Workable.make("D", 0.09);
    CompletableFuture.anyOf(cfA, cfB, cfC, cfD)
      .thenRunAsync(() ->
        System.out.println("anyOf"));
    join();

    init();
    cfC = Workable.make("C", 0.08);
    cfD = Workable.make("D", 0.09);
    CompletableFuture.allOf(cfA, cfB, cfC, cfD)
      .thenRunAsync(() ->
        System.out.println("allOf"));
    join();
  }
}
/* Output:
Workable[BW]
runAfterEither
Workable[AW]
*****************
Workable[BW]
Workable[AW]
runAfterBoth
*****************
Workable[BW]
applyToEither: Workable[BW]
Workable[BW]
Workable[AW]
*****************
Workable[BW]
acceptEither: Workable[BW]
Workable[AW]
*****************
Workable[BW]
Workable[AW]
thenAcceptBoth: Workable[AW], Workable[BW]
*****************
Workable[BW]
Workable[AW]
thenCombine: Workable[AW], Workable[BW]
Workable[AW]
*****************
Workable[CW]
anyOf
Workable[DW]
Workable[BW]
Workable[AW]
*****************
Workable[CW]
Workable[DW]
Workable[BW]
Workable[AW]
*****************
allOf
*/

5.10.5 异常

        在没有调用get()方法之前,CompletableFuture不会抛出异常,即使程序已经出现异常。

// concurrent/Breakable.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
import java.util.concurrent.*;

public class Breakable {
  String id;
  private int failcount;
  public Breakable(String id, int failcount) {
    this.id = id;
    this.failcount = failcount;
  }
  @Override public String toString() {
    return "Breakable_" + id +
      " [" + failcount + "]";
  }
  public static Breakable work(Breakable b) {
    if(--b.failcount == 0) {
      System.out.println(
        "Throwing Exception for " + b.id + "");
      throw new RuntimeException(
        "Breakable_" + b.id + " failed");
    }
    System.out.println(b);
    return b;
  }
}

 

// concurrent/CompletableExceptions.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
import java.util.concurrent.*;

public class CompletableExceptions {
  static CompletableFuture<Breakable>
  test(String id, int failcount) {
    return
      CompletableFuture.completedFuture(
        new Breakable(id, failcount))
          .thenApply(Breakable::work)
          .thenApply(Breakable::work)
          .thenApply(Breakable::work)
          .thenApply(Breakable::work);
  }
  public static void main(String[] args) {
    // Exceptions don't appear ...
    test("A", 1);
    test("B", 2);
    test("C", 3);
    test("D", 4);
    test("E", 5);
    // ... until you try to fetch the value:
    try {
      test("F", 2).get(); // or join()
    } catch(Exception e) {
      System.out.println(e.getMessage());
    }
    // Test for exceptions:
    System.out.println(
      test("G", 2).isCompletedExceptionally());
    // Counts as "done":
    System.out.println(test("H", 2).isDone());
    // Force an exception:
    CompletableFuture<Integer> cfi =
      new CompletableFuture<>();
    System.out.println("done? " + cfi.isDone());
    cfi.completeExceptionally(
      new RuntimeException("forced"));
    try {
      cfi.get();
    } catch(Exception e) {
      System.out.println(e.getMessage());
    }
  }
}
/* Output:
Throwing Exception for A
Breakable_B [1]
Throwing Exception for B
Breakable_C [2]
Breakable_C [1]
Throwing Exception for C
Breakable_D [3]
Breakable_D [2]
Breakable_D [1]
Throwing Exception for D
Breakable_E [4]
Breakable_E [3]
Breakable_E [2]
Breakable_E [1]
Breakable_F [1]
Throwing Exception for F
java.lang.RuntimeException: Breakable_F failed
Breakable_G [1]
Throwing Exception for G
true
Breakable_H [1]
Throwing Exception for H
true
done? false
java.lang.RuntimeException: forced
*/

        CompletableFutrue定义了异常处理机制,而不是简单的通过try-catch处理。

// concurrent/CatchCompletableExceptions.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
import java.util.concurrent.*;

public class CatchCompletableExceptions {
  static void handleException(int failcount) {
    // Call the Function only if there's an
    // exception, must produce same type as came in:
    CompletableExceptions
      .test("exceptionally", failcount)
      .exceptionally((ex) -> { // Function
        if(ex == null)
          System.out.println("I don't get it yet");
        return new Breakable(ex.getMessage(), 0);
      })
      .thenAccept(str ->
        System.out.println("result: " + str));

    // Create a new result (recover):
    CompletableExceptions
      .test("handle", failcount)
      .handle((result, fail) -> { // BiFunction
        if(fail != null)
          return "Failure recovery object";
        else
          return result + " is good";
      })
      .thenAccept(str ->
        System.out.println("result: " + str));

    // Do something but pass the same result through:
    CompletableExceptions
      .test("whenComplete", failcount)
      .whenComplete((result, fail) -> { // BiConsumer
        if(fail != null)
          System.out.println("It failed");
        else
          System.out.println(result + " OK");
      })
      .thenAccept(r ->
        System.out.println("result: " + r));
  }
  public static void main(String[] args) {
    System.out.println("**** Failure Mode ****");
    handleException(2);
    System.out.println("**** Success Mode ****");
    handleException(0);
  }
}
/* Output:
**** Failure Mode ****
Breakable_exceptionally [1]
Throwing Exception for exceptionally
result: Breakable_java.lang.RuntimeException:
Breakable_exceptionally failed [0]
Breakable_handle [1]
Throwing Exception for handle
result: Failure recovery object
Breakable_whenComplete [1]
Throwing Exception for whenComplete
It failed
**** Success Mode ****
Breakable_exceptionally [-1]
Breakable_exceptionally [-2]
Breakable_exceptionally [-3]
Breakable_exceptionally [-4]
result: Breakable_exceptionally [-4]
Breakable_handle [-1]
Breakable_handle [-2]
Breakable_handle [-3]
Breakable_handle [-4]
result: Breakable_handle [-4] is good
Breakable_whenComplete [-1]
Breakable_whenComplete [-2]
Breakable_whenComplete [-3]
Breakable_whenComplete [-4]
Breakable_whenComplete [-4] OK
result: Breakable_whenComplete [-4]
*/

        只有在出现异常时,exceptionally()参数才会运行。exceptionally()的限制在于Function返回值的类型必须和输入相同。将一个正确的对象插回到流,可使exceptionally()恢复到工作状态。
        handle()总是会被调用的,而且你必须检查 fail 是否为 true,以确定是否有异常发生。但是handle()可生成任意新类,因此它允许你执行处理,而不是像 exceptionally()那样只是恢复。
        whenComplete()和handle()类似,都必须测试失败情况,但是参数是消费者,只会使用而不会修改传递中的result对象。

5.11 死锁

        哲学家用餐问题,是对死锁的经典诠释。P260

        出现死锁需要同时满足下面4个条件

1.互斥。这些任务使用的至少一项资源必须不是共享的。此处,一根筷子同时只能被一位 Philosopher 使用。
2.至少一个任务必须持有一项资源,并且等待正被另一个任务持有的资源。也就是说,如果要出现死锁,一位Philosopher 必须持有一根筷子,并且正在等待另一根。
3.不能从一个任务中抢走一项资源。任务只能以正常的事件释放资源。我们的Philosopher很有礼貌,他们并不会从其他Philosopher 手里抢走筷子。
4.会发生循环等待,其中一个任务等待另一个任务持有的资源,另一个任务又在等待另一个任务持有的资源,以此类推,直到某个任务正在等待第一个任务持有的资源,由此一切都陷入了死循环。在 DiningPhilosophers.java中,由于每一位Philosopher都在试图先获取右边的筷子,再获取左边的,因此发生了循环等待。

        可以通过阻止上述条件来避免死锁。

        并行流适合用于那种很容易将数据拆分成无差别、易处理的片段来处理的问题。        

        CompletableFuture处理的工作片段最好是各不相同的,这样效果最好。

第六章 底层并发

6.1.1 最佳线程数

        事实证明,线程“通常”的最佳数量就是可用处理器的数量(对于某些问题来说可能不是这样,该方式主要用于计算密集型的问题场景)。这是因为如果线程数多于可用处理器的数量,处理器在线程间的切换会带来开销。

“工作窃取”线程池(WorkStealingPool)

        该线程池会创建和可用处理器一样多的线程数量。并且每个线程拥有一个工作队列,任务提交到线程池后会分配到工作队列中。当某个线程处于空闲状态时会窃取其他忙碌线程队列的任务来处理,让自己处于忙碌状态。

// lowlevel/WorkStealingPool.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
import java.util.stream.*;
import java.util.concurrent.*;

class ShowThread implements Runnable {
  @Override public void run() {
    System.out.println(
      Thread.currentThread().getName());
  }
}

public class WorkStealingPool {
  public static void main(String[] args)
    throws InterruptedException {
    System.out.println(
      Runtime.getRuntime().availableProcessors());
    ExecutorService exec =
      Executors.newWorkStealingPool();
    IntStream.range(0, 10)
      .mapToObj(n -> new ShowThread())
      .forEach(exec::execute);
    exec.awaitTermination(1, TimeUnit.SECONDS);
  }
}
/* Output:
8
ForkJoinPool-1-worker-1
ForkJoinPool-1-worker-2
ForkJoinPool-1-worker-1
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-4
ForkJoinPool-1-worker-4
ForkJoinPool-1-worker-2
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-5
ForkJoinPool-1-worker-1
*/

6.2 捕获异常

        如果使用submit方法提交任务到线程池,并且这个任务抛出异常,那么程序不会有关于这个异常的任何输出。

// lowlevel/SwallowedException.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
import java.util.concurrent.*;

public class SwallowedException {
  public static void main(String[] args)
    throws InterruptedException {
    ExecutorService exec =
      Executors.newSingleThreadExecutor();
    exec.submit(() -> {
      throw new RuntimeException();
    });
    exec.shutdown();
  }
}

        如果使用execute方法提交任务,虽然异常会抛给控制台并输出,但是即使在main()中用try-catch将抛出异常的方法块包裹住也不误捕获异常。

// lowlevel/ExceptionThread.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// {ThrowsException}
import java.util.concurrent.*;

public class ExceptionThread implements Runnable {
  @Override public void run() {
    throw new RuntimeException();
  }
  public static void main(String[] args) {
    ExecutorService es =
      Executors.newCachedThreadPool();
    es.execute(new ExceptionThread());
    es.shutdown();
  }
}
/* Output:
___[ Error Output ]___
Exception in thread "pool-1-thread-1"
java.lang.RuntimeException
        at ExceptionThread.run(ExceptionThread.java:7)
        at java.util.concurrent.ThreadPoolExecutor.runW
orker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Work
er.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)
*/

        需要通过Thread.UncaughtExceptionHandler接口实现对线程异常的捕获并处理,然后实现ThreadFactory将所有接收到的线程都转换为一个带有Thread.UncaughtExceptionHandler的新线程。

// lowlevel/CaptureUncaughtException.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
import java.util.concurrent.*;

class ExceptionThread2 implements Runnable {
  @Override public void run() {
    Thread t = Thread.currentThread();
    System.out.println("run() by " + t.getName());
    System.out.println(
      "eh = " + t.getUncaughtExceptionHandler());
    throw new RuntimeException();
  }
}

class MyUncaughtExceptionHandler implements
Thread.UncaughtExceptionHandler {
  @Override
  public void uncaughtException(Thread t, Throwable e) {
    System.out.println("caught " + e);
  }
}

class HandlerThreadFactory implements ThreadFactory {
  @Override public Thread newThread(Runnable r) {
    System.out.println(this + " creating new Thread");
    Thread t = new Thread(r);
    System.out.println("created " + t);
    t.setUncaughtExceptionHandler(
      new MyUncaughtExceptionHandler());
    System.out.println(
      "eh = " + t.getUncaughtExceptionHandler());
    return t;
  }
}

public class CaptureUncaughtException {
  public static void main(String[] args) {
    ExecutorService exec =
      Executors.newCachedThreadPool(
        new HandlerThreadFactory());
    exec.execute(new ExceptionThread2());
    exec.shutdown();
  }
}
/* Output:
HandlerThreadFactory@106d69c creating new Thread
created Thread[Thread-0,5,main]
eh = MyUncaughtExceptionHandler@52e922
run() by Thread-0
eh = MyUncaughtExceptionHandler@52e922
caught java.lang.RuntimeException
*/

        如果程序所有线程都用同一个异常处理器,那么可以通过下面的方式设置一个默认的异常处理器。

        如果线程没有通过前面的方式配置指定异常处理器,则会使用默认异常处理器。

public class SettingDefaultHandler {
  public static void main(String[] args) {
    Thread.setDefaultUncaughtExceptionHandler(
      new MyUncaughtExceptionHandler());
    ExecutorService es =
      Executors.newCachedThreadPool();
    es.execute(new ExceptionThread());
    es.shutdown();
  }
}
/* Output:
caught java.lang.RuntimeException
*/

6.3 共享资源

        在调用任意synchronized方法时,该对象都会被上锁,该对象的任何其他synchronized方法都无法被调用。例如如果f()被调用,则f()和g()在f()被释放前都无法被调用。

synchronized void f() {}
synchronized void g() {}

        对于上面的synchronized方法的锁实际上是这个对象本身,称为对象锁。当某个使用该对象锁的方法被调用时,该对象锁便会被锁住,其他所有使用该对象锁的方法都将无法被访问,直到该对象锁被释放。

        而如果在static方法上使用synchronized关键字,那实际上是使用类作为锁,即类锁。同样的,只要有一个使用类锁的方法被调用,则类锁便被锁住,其他所有使用类锁的方法将无法被访问。

public synchronized staic g() {} 

6.4 volatile关键字

6.4.1 字分裂

        如果你的数据类型足够大(例如long和double,这两者都是64位),JVM允许将64位数的读写操作分为两次32位数的独立操作(在64位机器上可能不会发生)。这增加了在读写操作过程中途发生上下文切换的可能性。使用volatile可以阻止字分裂。

        不过,也可以使用synchronized或atomic变量取代volatile。

6.4.2 可见性

        每个处理器都有自己的本地缓存,为例提升效率,处理器会把主存的数据缓存起来,不用每次都从内存中读取数据。

        每个线程又可以在处理器缓存中保存变量的本地副本,将变量设置为volatile可以阻止缓存,从而直接从内存中读取数据。

6.4.3 重排序和先行发生

        Java可能通过对执行进行重排序来优化性能,volatile可以防止对volatile变量的读写指定进行不正确的重排序。这样的重排序规则称为先行发生保证。

public class ReOrdering implements Runnable {
  int one, two, three, four, five, six;
  volatile int volaTile;
  @Override public void run() {
    one = 1;
    two = 2;
    three = 3;
    volaTile = 92;
    int x = four;
    int y = five;
    int z = six;
  }
}

        one、two和three的赋值操作可能会被重排序,只要它们都发生在volatile写操作之前。同样,x,y和z语句也可能会被重排序,只要它们都发生在volatile写操作之后。volatile 操作通常称为内存栅栏(memory barrier)。先行发生保证确保了 volatile 变量的读写指令无法穿过内存栅栏而被重排序。
        先行发生保证还有另一种效果: 当一个线程对某个volatile变量执行写操作时,所有在该写操作之前被该线程修改的其他变量--包括非 volatile变量--也都会被刷新到主存。当一个线程读取volatile变量的时候,也会读取到所有和volatile变量一起被刷到主存的变量,包括非 volatile 变量。虽然这个特性很重要,解决了 Java 5之前非常隐蔽的一些bug,但你不应该依赖该特性,"自动"将周围的变量隐式地变成了 volatile的。如果你希望某个变量是 volatile的,就应该让其他所有的代码维护人员明确地知道。

6.4.4 何时使用volatile

        如果你想使用volatile,原因很可能是你想要使一个变量变得线程安全,而又不带来同步导致的开销,由于volatile需要掌握一些复杂的技巧,应该使用atomic变量代替它。

6.5 原子性

        在Java中下面的操作并不是原子性的。这些操作会被分为几个指令去完成,在多线程环境下,指令运行期间可能存在上下文切换而导致计算结果错误。即使使用volatile修饰i变量,在多线程环境下也可能还是会存在问题,所以尽量不要使用volatile。

i++;
i += 1;

6.6 临界区

        要隔离的代码称为临界区。可以使用同步代码块来制定同步区,而不是使用同步方法将整个方法都制定为同步区。

Object syncObject = new Object();
synchronized(syncObject) { 
}
/*
*通过最合理的选择时使用this作为对象锁
*/
synchronied(this) {
}

第七章: Java IO 系统

        java引入nio的目标只有一个:速度。
        速度的提升来自于它更接近操作系统的I/O实现方式和结构:通道(channel)和缓冲区(buff)。

c4f989a7a3ca4c59a752adf1be799749.jpeg

OnJava基础卷

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jiabao998

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值