普通视图

Received today — 2026年6月7日技术博客

Java线程的状态

2024年11月3日 00:00

JDK 1.5 前线程状态

线程状态中文名称描述
New新建刚创建的线程,还未启动。
Runnable可运行线程可以运行,可能在等待 CPU 调度。
Blocked阻塞线程被阻塞,正在等待锁的释放。
Dead终止线程执行完成或异常终止,已进入结束状态。

jdk1.5之前

JDK 1.5 后线程状态

java.lang.Thread.State

public enum State {    /**     * Thread state for a thread which has not yet started.     */    NEW,    /**     * Thread state for a runnable thread.  A thread in the runnable     * state is executing in the Java virtual machine but it may     * be waiting for other resources from the operating system     * such as processor.     */    RUNNABLE,    /**     * Thread state for a thread blocked waiting for a monitor lock.     * A thread in the blocked state is waiting for a monitor lock     * to enter a synchronized block/method or     * reenter a synchronized block/method after calling     * {@link Object#wait() Object.wait}.     */    BLOCKED,    /**     * Thread state for a waiting thread.     * A thread is in the waiting state due to calling one of the     * following methods:     * <ul>     *   <li>{@link Object#wait() Object.wait} with no timeout</li>     *   <li>{@link #join() Thread.join} with no timeout</li>     *   <li>{@link LockSupport#park() LockSupport.park}</li>     * </ul>     *     * <p>A thread in the waiting state is waiting for another thread to     * perform a particular action.     *     * For example, a thread that has called <tt>Object.wait()</tt>     * on an object is waiting for another thread to call     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on     * that object. A thread that has called <tt>Thread.join()</tt>     * is waiting for a specified thread to terminate.     */    WAITING,    /**     * Thread state for a waiting thread with a specified waiting time.     * A thread is in the timed waiting state due to calling one of     * the following methods with a specified positive waiting time:     * <ul>     *   <li>{@link #sleep Thread.sleep}</li>     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>     *   <li>{@link #join(long) Thread.join} with timeout</li>     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>     * </ul>     */    TIMED_WAITING,    /**     * Thread state for a terminated thread.     * The thread has completed execution.     */    TERMINATED;}
线程状态中文名称描述
New新建刚创建的线程,还未启动。
Runnable可运行线程可以运行,可能在等待 CPU 调度。
Blocked阻塞线程尝试获取锁失败,被阻塞,等待锁释放。
Waiting等待线程进入等待状态,等待其他线程显式唤醒,通常由 Object.wait() 引起。
Timed Waiting计时等待线程等待指定时间后自动唤醒,由 Thread.sleep()wait(time) 引起。
Terminated终止线程执行完成或异常终止,已进入结束状态。

jdk1.5之后

Java的单例

2022年9月21日 17:50

1. 懒汉式(Lazy Initialization)

这种方式在第一次调用时创建实例,延迟实例的创建。

public class LazySingleton {    private static LazySingleton instance;    private LazySingleton() {        // 私有构造函数    }    public static LazySingleton getInstance() {        if (instance == null) {            instance = new LazySingleton();        }        return instance;    }}

2. 线程安全的懒汉式

通过同步方法来保证线程安全。

public class ThreadSafeLazySingleton {    private static ThreadSafeLazySingleton instance;    private ThreadSafeLazySingleton() {        // 私有构造函数    }    public static synchronized ThreadSafeLazySingleton getInstance() {        if (instance == null) {            instance = new ThreadSafeLazySingleton();        }        return instance;    }}

3. 双重检查锁定(Double-Checked Locking)

通过双重检查来减少同步的开销。

public class DoubleCheckedLockingSingleton {    private static volatile DoubleCheckedLockingSingleton instance;    private DoubleCheckedLockingSingleton() {        // 私有构造函数    }    public static DoubleCheckedLockingSingleton getInstance() {        if (instance == null) {            synchronized (DoubleCheckedLockingSingleton.class) {                if (instance == null) {                    instance = new DoubleCheckedLockingSingleton();                }            }        }        return instance;    }}

4. 饿汉式(Eager Initialization)

在类加载时就创建实例,简单且线程安全。

public class EagerSingleton {    private static final EagerSingleton instance = new EagerSingleton();    private EagerSingleton() {        // 私有构造函数    }    public static EagerSingleton getInstance() {        return instance;    }}

5. 静态内部类(Static Inner Class)

利用类加载机制确保单例的延迟初始化和线程安全。

public class StaticInnerClassSingleton {    private StaticInnerClassSingleton() {        // 私有构造函数    }    private static class SingletonHolder {        private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();    }    public static StaticInnerClassSingleton getInstance() {        return SingletonHolder.INSTANCE;    }}

6. 枚举单例

使用枚举来实现单例,简洁且天然支持序列化。

public enum EnumSingleton {    INSTANCE;    public void someMethod() {        // 示例方法    }}

Java8的新特性

2022年6月20日 00:00

一、Lambda表达式

  lambda表达式是Java8的重要更新,lambda表达式可以用更简洁的代码来创建一个只有一个抽象方法的接口(函数式接口)的实例,从而更简单的创建匿名内部类的对象。

1.1 语法和使用

  lambda表达式的基本语法是形参列表(可以省略类型)箭头,以及代码块,例如() -> {},或者(x, y) -> {},如果只有一个参数,那么小括号()可以省略,如果代码块只有一条语句,那么代码块的花括号{}可一并省略,如果代码块内只有一处return,那么return也可一并省略。

例: TreeSet类的构造器需要传进去一个Comparator的匿名类对象进去,来进行排序,所以程序实现了一个匿名内部类来封装处理行为,而且不得不用匿名内部类的语法来封装对象。

@Testpublic void test() {    TreeSet<Integer> treeSet = new TreeSet<>(new Comparator<Integer>() {        @Override        public int compare(Integer o1, Integer o2) {            return Integer.compare(o1, o2);        }    });    treeSet.add(20);    treeSet.add(78);    treeSet.add(-98);    System.out.println(treeSet);}

Comparator接口是一个函数式接口,因此完全可以使用lambda表达式来简化创建匿名内部类对象,因此上面代码可以修改成这样

@Testpublic void test() {    TreeSet<Integer> treeSet = new TreeSet<>((Integer x, Integer y) -> {        return x.compareTo(y);    });    treeSet.add(20);    treeSet.add(78);    treeSet.add(-98);    System.out.println(treeSet);}

进一步简化: 参数类型可以省略,如果代码块只有一条语句,那么代码块的花括号{}可一并省略,如果代码块内只有一处return,那么return也可一并省略

@Testpublic void test() {    TreeSet<Integer> treeSet = new TreeSet<>((x, y) -> x.compareTo(y));    treeSet.add(20);    treeSet.add(78);    treeSet.add(-98);    System.out.println(treeSet);}

  逻辑与上面代码是完全相同的,只是不再需要new Xxx() {}这种繁琐的语法,不需要指出重写方法的名字,也不需要给出重写方法的返回值类型,只需要给出重写方法的括号以及括号内的形参变量即可,用lambda表达式的代码块代替掉匿名内部类抽象方法的方法体,lambda表达式在这里就像是一个匿名方法。

1.2 方法引用和构造器引用

  前面说过如果花括号只有一条代码,便可以省略花括号,不仅如此,还可以使用方法引用和构造器引用,使得lambda表达式变得再简洁一些,方法引用和构造器引用的语法是两个英文冒号::,支持以下使用方式

种类语法说明lambda表达式写法
类方法类名::类方法抽象方法全部参数传给该类某个方法作为参数(a,b,…) -> 类名.类方法(a,b,…)
特定对象实例方法特定对象::实例方法抽象方法全部参数传给该方法作为参数(a,b,…) -> 特定对象.实例方法(a,b,…)
某类对象实例方法类名::实例方法抽象方法第一个参数作为调用者,后面的参数全部传给该方法作为参数(a,b,c,…) -> a.实例方法(b,c,…)
构造器类名::new抽象方法全部参数传给该构造器作为参数(a,b,…) -> new 类名(a,b,…)

例: 类名::类方法

@FunctionalInterfaceinterface Convert {    Integer fun(String s);}@Testpublic void test8() {    Convert convert =  from -> Integer.valueOf(from);    System.out.println(convert.fun("150") + 1);}@Testpublic void test9() {    Convert convert = Integer::valueOf;    System.out.println(convert.fun("150") + 1);}

例: 特定对象::实例方法

@FunctionalInterfaceinterface Convert {    Integer fun(String s);}@Testpublic void test8() {    Convert convert = from -> "liuzijian.com".indexOf(from);    System.out.println(convert.fun("zi"));}@Testpublic void test9() {    Convert convert = "liuzijian.com"::indexOf;    System.out.println(convert.fun("zi"));}

例: 类名::实例方法

@FunctionalInterfaceinterface Fun {    String test(String a, int b, int c);}@Testpublic void test8() {    Fun fun = (a, b, c) -> a.substring(b, c);    String s = fun.test("abcdefghi", 3, 5);    System.out.println(s);}@Testpublic void test9() {    Fun fun = String::substring;    String s = fun.test("abcdefghi", 3, 5);    System.out.println(s);}

例: 类名::new

@FunctionalInterfaceinterface Fun {    BigDecimal test(String n);}@Testpublic void test8() {    Fun fun = (n) -> new BigDecimal(n);    BigDecimal b = fun.test("45.64");    System.out.println(b);}@Testpublic void test9() {    Fun fun = BigDecimal::new;    BigDecimal b = fun.test("45.64");    System.out.println(b);}

二、函数式接口

  在Java8中,引入了函数式接口的概念,函数式接口是一个只有一个抽象方法的接口,通常用于Lambda表达式和方法引用,函数式接口可以有多个默认方法静态方法,但是必须只有一个抽象方法

2.1 定义

@FunctionalInterfacepublic interface MyPredicate<T> {        boolean fun(T obj);        default void other() {        System.out.println("hello world");    }    static void staticMethod() {        System.out.println("static method");    }}

  @FunctionalInterface注解:这是一个可选的注解,它可以帮助编译器在编译时检查接口是否符合函数式接口的要求,即是否只有一个抽象方法,如不符合还加这个注解,会导致编译器报错。

2.2 使用

编写一个实体类Employee

@Data@AllArgsConstructor@NoArgsConstructor@ToStringpublic class Employee {    private String name;    private Double salary;    private Integer age;    public Employee(Integer age) {        this.age = age;    }    public Employee(Integer age, String name) {        this.age = age;        this.name = name;    }}

新增一个按条件过滤的方法filter,将List<Employee>作为第一个参数,函数式接口MyPredicate<Employee>作为第二个参数传进filter()方法,方法体内循环将每个Employee对象一一作为参数传入接口的抽象方法fun()中,并调用,根据抽象方法运行后得到的布尔值判断是否过滤掉。

private List<Employee> filter(List<Employee>employees, MyPredicate<Employee> predicate) {    List<Employee>list = new ArrayList<>();    for (Employee e : employees) {        if (predicate.fun(e)) {            list.add(e);        }    }    return list;}

声明一个员工集合employees,插入5个对象,然后调用filter()方法,将employees作为第一个参数传入,然后直接new一个实现MyPredicate接口抽象方法的匿名内部类作为第二个参数传入,这样一来,调用时既告诉了目标方法filter()要处理的数据是employees,也一并将数据的具体处理规则obj.getAge() > 16告诉了目标方法,调用同一个方法可以有无数种处理数据的策略,这个实际上就是一种典型的策略模式,实际上Java8已经为我们写好了一种策略模式的函数式接口。

private List<Employee> employees = Arrays.asList(    new Employee("soo", 8547.322, 17),    new Employee("lili", 1000D, 15),    new Employee("王萌", 2154D, 16),    new Employee("张帆", 8547.322, 22),    new Employee("goog", 353D, 12));@Testpublic void test3() {    List<Employee>list = filter(employees, new MyPredicate<Employee>() {        @Override        public boolean fun(Employee obj) {            return obj.getAge() > 16;        }    });    System.out.println(list);}

Java8中,通过将策略接口实现简写为Lambda表达式的方式,可以使得语法显得更加简洁

List<Employee>list2 = filter(employees, (e) -> e.getAge() < 16);

2.3 内置的函数式接口

Java8提供了一些预定义的函数式接口,位于java.util.function包中

  • java.util.function.Consumer 消费
  • java.util.function.Supplier 供给
  • java.util.function.Function 函数
  • java.util.function.Predicate 断言
  • java.util.function.BinaryOperator 不常用
  • java.util.function.UnaryOperator 不常用

编写4个将函数式接口作为参数的方法

private void testConsumer(String str, Consumer<String>consumer) {    consumer.accept(str);}private String testSupplier(Supplier<String>supplier) {    return supplier.get();}private Integer testFunction(String str, Function<String, Integer>function) {    return function.apply(str);}private boolean testPredicate(String str, Predicate<String>predicate) {     return predicate.test(str); }

分别调用这些方法,按照业务逻辑通过匿名内部类的lambda表达式写法实现函数式接口的抽象方法,作为参数传入

@Testpublic void test4() {    testConsumer("hello lambda", (x) -> System.out.println(x));    String str = testSupplier(() -> { return "hello world"; });    System.out.println(str);    Integer integer = testFunction("66", (x) -> Integer.valueOf(x));    System.out.println(integer);    boolean b = testPredicate("hello", (e) -> e.equals("hello"));    System.out.println(b);}

得到运行结果

hello lambdahello world66true

还可以通过lambda表达式的方法引用和构造器引用将调用修改的更简洁一些

@Testpublic void test2() {    testConsumer("hello lambda", System.out::println);    Integer integer = testFunction("66", Integer::valueOf);}

三、Stream API

  Stream是Java8引入的一个新特性,是一个数据流,它提供了一种声明性的方法来处理集合、数组等数据源中的数据,可以更简洁、函数式的方式进行数据处理,它不会改变数据源本身,而是返回一个新的Stream或者是最终的结果。

Java8中引进的常见流式API包括:

  • java.util.stream.Stream
  • java.util.stream.LongStream
  • java.util.stream.IntStream
  • java.util.stream.DoubleStream

其中java.util.stream.Stream是个通用的流接口,以外的几种则代表流的元素类型为longintdouble

  Stream操作是延迟执行的,这意味着它们会等到需要结果时在执行,Stream操作可以被链式调用,并且一般分为两类操作:中间操作和终止操作

3.1 创建Stream

从集合类型的stream()方法创建

List<String> list = new ArrayList<>();Stream<String> stream = list.stream();

从数组创建

Employee[] employees = new Employee[10];Stream<Employee> employeeStream = Arrays.stream(employees);

通过Stream的静态方法创建流

Employee[] employees = new Employee[10];Stream<Employee> employeeStream1 = Stream.of(employees);

迭代创建无限流,根据种子和消费接口

Stream.iterate(10, (x) -> x + 2)      .limit(10)      .forEach(System.out::println);

随机数

Stream.generate(Math::random)       .limit(20)       .forEach(System.out::println);

通过builder()创建一个int流

@Testpublic void test5() {        IntStream intStream = IntStream.builder()            .add(1)            .add(2)            .add(3)            .add(4).build();    // 下面的聚集方法每次只能执行一行    System.out.println(intStream.max().getAsInt());    //System.out.println(intStream.min().getAsInt());    //System.out.println(intStream.sum());    //System.out.println(intStream.count());    //System.out.println(intStream.average());}

3.2 Stream的操作

  Stream的操作包含中间操作终止操作,在《疯狂Java讲义》一书中,李刚老师也将其称为中间方法和末端方法,中间操作允许流保持打开状态,并允许直接调用后续方法,中间方法返回值是另一个流。终止方法是对流的最终操作,在对某个流执行终止操作后,整个流将不再可用。

常见中间操作

  • filter(Predicate predicate) 过滤流中不符合predicate的元素
  • mapToXxx(ToXxxFunction mapper) 使用ToXxxFunction对流中的元素进行一对一转换。返回的新流中包含ToXxxFunction转换生成的所有元素
  • peek(Consumer action) 依次对每个元素执行了一些操作,返回的流与原有的流包含相同的元素(多用于调试)
  • distinct() 排除流中所有重复元素,判断标准是equals()返回true
  • sorted() 该方法用于排序
  • sorted(Comparator comparator) 该方法用于根据自定义规则排序
  • limit(long maxSize) 截取流中的前maxSize个元素
  • skip(long n) 跳过流中的前n个元素
  • map(Function mapper) 映射每个元素为其他形式
  • flatMap(Function mapper) 将每个元素转换为一个流,然后将多个流合并成一个流

常见终止操作

  • collect(Collector collector) 将流中的元素收集到一个容器中(如集合、列表、映射等)
  • count() 返回流中的元素个数
  • forEach(Consumer action) 遍历流中所有元素,对每个元素执行action
  • toArray() 将流中所有元素转换为一个数组
  • reduce() 通过某种操作来合并流中的元素
  • min() 返回流中元素的最小值
  • max() 返回流中元素的最大值
  • anyMatch(Predicate predicate) 如果流中任一元素匹配给定条件,返回 true
  • allMatch(Predicate predicate) 如果流中所有元素都匹配给定条件,返回 true
  • noneMatch(Predicate predicate) 如果流中没有任何元素匹配给定条件,返回 true
  • findFirst() 返回流中的第一个元素
  • findAny() 返回流中的任意一个元素

中间操作返回的是一个新的Stream,并且中间操作是惰性执行的,直到终止操作才触发计算

下面是用例:

数据:

private List<Employee> employees = Arrays.asList(    new Employee("soo", 8547.322, 17),    new Employee("lili", 1000D, 18),    new Employee("王萌", 2154D, 16),    new Employee("张帆", 8547.322, 22),    new Employee("张帆", 8547.322, 22),    new Employee("张帆", 8547.322, 22),    new Employee("goog", 353D, 12));private List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");

例: stream+limit筛选切片,满足e.getAge() > 16条件的对象达到两个时就停止迭代,而不是迭代一遍后返回前两个,提高效率。终止操作forEach()不触发,中间操作filter()limit()也不会得到执行。

@Testpublic void test() {    employees.stream()    .filter((e) -> {        // 中间操作        System.out.println("中间操作");         return e.getAge() > 16;    })    .limit(2) //中间操作    .forEach(System.out::println); //终止操作    }

运行结果:

中间操作Employee(name=soo, salary=8547.322, age=17)中间操作Employee(name=lili, salary=1000.0, age=18)

例: 跳过流中的前n个元素,与limit相反

@Testpublic void test2() {    employees.stream().skip(2).forEach(System.out::println); }

运行结果

Employee(name=王萌, salary=2154.0, age=16)Employee(name=张帆, salary=8547.322, age=22)Employee(name=张帆, salary=8547.322, age=22)Employee(name=张帆, salary=8547.322, age=22)Employee(name=goog, salary=353.0, age=12)

例: 去重,根据equalshashCode,本例去重成功的前提是Employee类需要重写equalshashCode

//Employee类重写equals hashCode@Overridepublic boolean equals(Object o) {    if (this == o) return true;    if (o == null || getClass() != o.getClass()) return false;    Employee employee = (Employee) o;    if (!Objects.equals(name, employee.name)) return false;    if (!Objects.equals(salary, employee.salary)) return false;    return Objects.equals(age, employee.age);}@Overridepublic int hashCode() {    int result = name != null ? name.hashCode() : 0;    result = 31 * result + (salary != null ? salary.hashCode() : 0);    result = 31 * result + (age != null ? age.hashCode() : 0);    return result;}
@Testpublic void test3() {    employees.stream().distinct().forEach(System.out::println);}

运行结果

Employee(name=soo, salary=8547.322, age=17)Employee(name=lili, salary=1000.0, age=18)Employee(name=王萌, salary=2154.0, age=16)Employee(name=张帆, salary=8547.322, age=22)Employee(name=goog, salary=353.0, age=12)

例: flatMap将流中每个值,都转换成另一个流,然后把所有流连接成一个

下面程序先将"aaa"转换成由3个'a'构成的List<Character>,再将List<Character>转换为Stream<Character>"bbb""ccc"同理,最后将转换成的三个Stream<Character>合并为含有9个元素的Stream<Character>,再调用结束方法collect()将其变为含有9个元素的List<Character>,依次打印输出。

@Testpublic void test5() {    List<String> list = Arrays.asList("aaa", "bbb", "ccc");        Function<String, Stream<Character>> function = (e) -> {        List<Character> characters = new ArrayList<>();        for (char c : e.toCharArray()) {            characters.add(c);        }        return characters.stream();    };    List<Character> collect = list.stream()                .flatMap(function)                .collect(Collectors.toList());    collect.forEach(System.out::println);}

运行结果

aaabbbccc

例: map映射,得到流中的一个元素,处理组成新的流

@Testpublic void test4() {    employees.stream().map((e) -> e.getName()).forEach(System.out::println);}

运行结果

soolili王萌张帆张帆张帆goog

例: sorted()自然排序

@Testpublic void test() {    list.stream().sorted().forEach(System.out::println);}

运行结果

aaabbbcccdddeee

例: sorted(Comparator c)定制排序

@Testpublic void test2() {    employees.stream()            .sorted((e1, e2) -> e1.getAge() - e2.getAge())            .forEach(System.out::println);}

运行结果

Employee(name=goog, salary=353.0, age=12)Employee(name=王萌, salary=2154.0, age=16)Employee(name=soo, salary=8547.322, age=17)Employee(name=lili, salary=1000.0, age=18)Employee(name=张帆, salary=8547.322, age=22)Employee(name=张帆, salary=8547.322, age=22)Employee(name=张帆, salary=8547.322, age=22)

例: xxxMatchfindXXXcount()max()min()

@Testpublic void test3() {    boolean b = employees.stream().allMatch((e) -> e.getAge() > 10);    System.out.println(b);    b = employees.stream().anyMatch((e) -> e.getAge() > 100);    System.out.println(b);    b = employees.stream().noneMatch((e) -> e.getAge() > 100);    System.out.println(b);    Optional<Employee> first = employees.stream().findFirst();    System.out.println(first.get());    Optional<Employee> any = employees.stream().findAny();    System.out.println(any.get());    long count = employees.stream().count();    System.out.println(count);    Optional<Employee> max = employees.stream()            .max(Comparator.comparingInt(Employee::getAge));    System.out.println(max.get());    Optional<Integer> maxAge = employees.stream()            .map(Employee::getAge)            .max(Integer::compare);    System.out.println(maxAge.get());}

运行结果

truefalsetrueEmployee(name=soo, salary=8547.322, age=17)Employee(name=soo, salary=8547.322, age=17)7Employee(name=张帆, salary=8547.322, age=22)22

例: reduce() 将流中元素反复结合,得到新值,先将起始值作为x,从流中取出一个值作为y

@Testpublic void test() {    List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);    Integer sum = list.stream().reduce(0, Integer::sum);    System.out.println(sum);        Optional<Double> reduce = employees.stream().map(Employee::getSalary)            .reduce(Double::sum);    System.out.println(reduce.get());}

运行结果

4537696.288

例: .collect(Collectors.toList()) .collect(Collectors.toCollection()) 收集为集合

@Testpublic void test2() {    List<String> names = employees.stream()            .map(Employee::getName)            .collect(Collectors.toList());            //.collect(Collectors.toCollection(LinkedList::new))        names.forEach(System.out::println);}

运行结果

soolili王萌张帆张帆张帆goog

例: collect(Collectors.averagingDouble()) 求平均值

@Testpublic void test5() {    Double avg = employees.stream()            .collect(Collectors.averagingDouble(Employee::getSalary));    System.out.println(avg);}

例: collect(Collectors.joining()) 用相同的内容连接多个字符串,非常适合SQL等参数拼接场景

@Testpublic void test() {    String collect = list.stream().collect(Collectors.joining(","));    System.out.println(collect);}

运行结果

aaa,bbb,ccc,ddd,eee

例: 收集为Map Collectors.groupingBy()

将相同航司和票号的票和行李的价格加在一起

public class TestGroupBy {        private List<Detail> details = new ArrayList<>();        @Before    public void mock() {        details.add(new Detail(1, "001", "123456789", new BigDecimal("120.00")));        details.add(new Detail(2, "001", "123456789", new BigDecimal("99.32")));                details.add(new Detail(3, "003", "333222111", new BigDecimal("27.32")));        details.add(new Detail(4, "003", "333222111", new BigDecimal("36.00")));        details.add(new Detail(5, "003", "123456789", new BigDecimal("48.32")));                details.add(new Detail(6, "101", "123456789", new BigDecimal("53.32")));        details.add(new Detail(7, "101", "123456789", new BigDecimal("10.32")));        details.add(new Detail(8, "102", "333222111", new BigDecimal("3.32")));        details.add(new Detail(9, "103", "123456789", new BigDecimal("9.00")));        details.add(new Detail(10, "103", "123456789", new BigDecimal("12.12")));            }        @Test    public void test() {        Map<String, List<Detail>> groupByAir = details.parallelStream().collect(Collectors.groupingBy(Detail::getAir));        groupByAir.forEach((air, sameAirs) -> {            Map<String, List<Detail>> groupByDoc = sameAirs.parallelStream().collect(Collectors.groupingBy(Detail::getDocument));            groupByDoc.forEach((doc, sameDocs) -> {                Optional<BigDecimal> reduce = sameDocs.parallelStream().map(Detail::getPrice).reduce(BigDecimal::add);                reduce.ifPresent(e -> {                    System.out.println(air + " "+ doc + " " + e);                });            });        });    }        @Data    @AllArgsConstructor    public static class Detail {        /**         * ID         */        private Integer id;        /**         *航司编码         */        private String air;        /**         *票号         */        private String document;        /**         *机票价格         */        private BigDecimal price;    }    }

运行结果

001 123456789 219.32101 123456789 63.64102 333222111 3.32003 333222111 63.32003 123456789 48.32103 123456789 21.12

例: peek() 实时打印调试看流处理的每一步里面的元素是什么样的

@Testpublic void test6() {    List<String> names = Arrays.asList("liuzijian", "liutongtong", "zhaoying", "wangwendi");    names.stream()            .filter(name -> name.startsWith("liu"))            .peek(name -> System.out.println("过滤后: " + name))            .map(String::toUpperCase)            .peek(name -> System.out.println("变成大写后: " + name))            .collect(Collectors.toList());}

运行结果

过滤后: liuzijian变成大写后: LIUZIJIAN过滤后: liutongtong变成大写后: LIUTONGTONG

3.3 并行流和串行流

  在Java8中,流可以分为并行流和串行流,这两者的主要区别在于数据处理的方式。

  Java8的stream()默认是串行流,即数据按顺序一个一个处理,可以通过parallel()方法将串行流转换为并行流,或者直接在流创建时使用parallelStream()

  并行流底层是基于Java的ForkJoinPool实现的,这个池管理多个线程来并行处理数据,流的元素会被拆分成多个子任务并分配到不同的线程中处理,最后将结果合并。

  并行流本身并不保证顺序。但是,在某些操作中,比如Collectors.joining(),它会保证合并结果的顺序,这通过收集器的设计来实现。

例: 并行流遍历打印

@Testpublic void test() {    list.parallelStream().forEach(System.out::println);}

运行结果

ccceeedddbbbaaa

例: 并行流多线程将0加到100

LongStream.rangeClosed(0, 100000000000L)创建了从0100000000000L之间所有整数的流,然后reduce()会先将流分成多个子流,每个子流计算局部的和,在不同的线程中进行,每个线程分别计算一部分和,计算完成后,再将各个子任务计算的结果合并,得到计算结果932356074711512064

public static void main(String[] args) {    long reduce = LongStream.rangeClosed(0, 100000000000L)            .parallel() // 转换为并行流,底层是fork-join            .reduce(0, Long::sum);    System.out.println(reduce);}

以上就是Java8 StreamAPI的全部内容。

四、接口的默认方法

  Java8前的接口,只能有两个成员,全局静态常量和抽象方法,Java8引入了接口的默认方法和静态方法作为新特性,它们的引入是为了增强接口的功能,特别是在接口的扩展性和灵活性方面。

  接口中的默认方法,使用default修饰符修饰,可以带有实现,实现类可以直接继承使用,实现类可以选择重写默认方法,也可以直接使用。

  接口中的静态方法只能通过接口名调用,不能通过接口的实现类或实例调用,为接口提供相关的工具性功能,而不需要依赖具体的实现类,静态方法不会被实现类继承,也不能被实现类重写。

4.1 接口的默认方法和静态方法

编写一个接口test.testinterface.MyInterface,拥有两个默认方法test()hello()和一个静态方法helloworld()

package test.testinterface;public interface MyInterface {        default String test() {        System.out.println("default");        return "default";    }    default void hello() {        System.out.println("my interface");    }        static void helloworld() {        System.out.println("hello java8!!!");    }}

编写一个类test.testinterface.SubClass,实现接口MyInterface

package test.testinterface;public class SubClass  implements MyInterface {    public static void main(String[] args) {        SubClass subClass = new SubClass();          subClass.hello();        MyInterface.helloworld();    }}

不实现接口里面的hello()方法也可以直接调用默认方法hello(),而且可以通过接口名直接调用接口的静态方法helloworld(),程序输出:

my interfacehello java8!!!

4.2 方法冲突

编写另一个接口test.testinterface.OtherInterface,并实现一个默认方法hello

package test.testinterface;public interface OtherInterface {    default void hello() {        System.out.println("other interface");    }}

令类test.testinterface.SubClass再实现一个接口OtherInterface,该接口含有和接口MyInterface一样定义的default方法hello(),就产生了接口冲突,当实现的多个接口中有相同签名的默认方法时,子类必须显式重写冲突的方法hello(),最终程序输出结果:”sub hello!”

package test.testinterface;public class SubClass implements MyInterface, OtherInterface {    /**     * 多实现方法冲突,实现类必须实现     **/    @Override    public void hello() {        System.out.println("sub hello!");    }    public static void main(String[] args) {        SubClass subClass = new SubClass();        subClass.hello();    }}

4.3 类优先

编写一个类test.testinterface.MyClass,里面有一个方法String test(),并让SubClass类继承它,并执行subClass.test();,得到输出结果:”class”,但是SubClass实现的接口MyInterface里面也有个方法String test(),却没有被执行,而是执行了类里面的方法,说明类优先,如果类或其父类中已经提供了方法实现,则优先使用类的实现,而不是接口的默认方法。

package test.testinterface;public class MyClass  {    public String test() {        System.out.println("class");        return "class";    }}
package test.testinterface;public class SubClass extends MyClass implements MyInterface, OtherInterface {    // 多实现方法冲突,实现类必须实现    @Override    public void hello() {        System.out.println("sub hello!");    }    public static void main(String[] args) {        SubClass subClass = new SubClass();        // 类优先原则, 继承类的方法        subClass.test();    }}

五、新的日期和时间API (java.time)

5.1 旧API的线程安全问题

  旧的日期时间工具类java.text.SimpleDateFormat存在线程安全问题,例如SimpleDateFormat线程不安全,内部依赖一个Calendar实例来解析和格式化日期,而Calendar是线程不安全的,多线程格式化会并发更新Calendar状态会导致出现异常。

以下代码使用100个线程并发调用一个format对象进行日期解析操作,会导致出现错误。

package test.time;import java.text.SimpleDateFormat;public class Test1 {    public static void main(String[] args)  {                SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");        Runnable r = new Runnable() {            @Override            public void run() {                try {                    System.out.println(format.parse("20191231"));                } catch (Exception e) {                    throw new RuntimeException(e);                }            }        };        for (int i=0; i<100; i++) {            new Thread(r, "t"+i).start();        }    }}

可以采取同步块,线程单独持有format对象,以及线程池内使用ThreadLocal的办法解决。采用同步代码块时,只能有一个线程执行parse方法,可以避免线程安全问题。

package test.time;import java.text.SimpleDateFormat;public class Test1 {    public static void main(String[] args)  {                SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");        Runnable r = new Runnable() {            @Override            public void run() {                synchronized (format) {                    try {                        System.out.println(format.parse("20191231"));                    } catch (Exception e) {                        throw new RuntimeException(e);                    }                }            }        };        for (int i=0; i<100; i++) {            new Thread(r, "t"+i).start();        }    }}

采用线程独自持有format对象的方法解决,每个线程执行时创建一个format对象,每个线程单独持有,防止线程安全问题。

package test.time;import java.text.SimpleDateFormat;public class Test1 {    public static void main(String[] args)  {        Runnable r = new Runnable() {            @Override            public void run() {                try {                    System.out.println(new SimpleDateFormat("yyyyMMdd").parse("20191231"));                } catch (Exception e) {                    throw new RuntimeException(e);                }            }        };        for (int i=0; i<100; i++) {            new Thread(r, "t"+i).start();        }    }}

线程池+ThreadLocal,10个线程同时派发100个格式化任务,可以为每个线程绑定一个format对象,各自使用,也可以避免线程安全问题。

package test.time;import java.text.SimpleDateFormat;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Test1 {        public static void main(String[] args)  {        ExecutorService executorService = Executors.newFixedThreadPool(10);        ThreadLocal<SimpleDateFormat> threadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd"));        Runnable runnable = new Runnable() {            @Override            public void run() {                try {                    System.out.println(threadLocal.get().parse("20191231"));                }                catch (Exception e) {                    throw new RuntimeException(e);                }            }        };        for (int i=0; i<100; i++) {            executorService.submit(runnable, "t"+i);        }        executorService.shutdown();    }}

5.2 新的日期时间API

  Java 8通过发布新的Date-TimeAPI(JSR310)进一步加强了对日期与时间的处理。

  首先,在旧版的Java中,日期时间API存在诸多问题,首先java.util.Date是非线程安全的,所有的日期类都是可变的。

  其次,Java的日期/时间类的定义并不一致,在java.utiljava.sql的包中都有日期类,负责格式化和解析的类又在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,而且放进sql包下并不合理。

  而且,无法更好的处理时区,日期类不能国际化,没有时区支持,因此Java引入了java.util.Calendarjava.util.TimeZone,但是它们仍然存在一样的问题。

于是Java8引入了新的日期时间API,位于java.time包下,该包下有几个重要的类:

  • java.time.Instant 时间戳
  • java.time.Duration 时间差
  • java.time.LocalDate 只包含日期,例如2011-07-11
  • java.time.LocalTime 只包含时间,例如09:00:01
  • java.time.LocalDateTime 同时包含日期和时间,例如2024-11-30 04:09:45
  • java.time.Period 时间段
  • java.time.OffsetDateTime 带有时区偏移量的日期和时间,是LocalDateTime和ZoneOffset的结合体,更适用于需要精确到时间和偏移量的场景,尤其当你关心的只是某个时间点相对于 UTC 的偏移。例如,在处理需要表示时间差(例如时间戳、系统日志等)时,OffsetDateTime 比较合适。
  • java.time.ZoneOffset 时区偏移量,比如+8:00
  • java.time.ZonedDateTime 带有时区的日期和时间,是LocalDateTimeZoneId的组合,ZonedDateTime更适用于需要考虑时区历史和夏令时等复杂问题的场景。例如,如果你需要表示某个特定时区(如America/New_York)的时间,并且要处理夏令时,ZonedDateTime会更加准确
  • java.time.Clock 时钟
package test.time;import org.junit.Test;import java.time.*;import java.time.format.DateTimeFormatter;import java.time.format.FormatStyle;import java.time.temporal.*;import java.util.Date;public class Test4 {    /**     * java8 API获取当前时间     */    @Test    public void current() {        Instant instant = Instant.now();        LocalDate localDate = LocalDate.now();        LocalTime localTime = LocalTime.now();        LocalDateTime localDateTime = LocalDateTime.now();        ZonedDateTime zonedDateTime = ZonedDateTime.now();        System.out.println(instant);        System.out.println(localDate);        System.out.println(localTime);        System.out.println(localDateTime);        System.out.println(zonedDateTime);    }        /**     * Instant的常见方法     */    @Test    public void testInstant() {        //通过Instant获取当前时间戳,格林威治时间        Instant now = Instant.now();        System.out.println(now);        //添加时区,转换为带时区的时间:OffsetDateTime        OffsetDateTime us = now.atOffset(ZoneOffset.ofHours(-4));        System.out.println(us);//US        //设置偏移量        OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(+8));        System.out.println(offsetDateTime);//CN        System.out.println(now.atOffset(ZoneOffset.ofHours(+9)));//JP        System.out.println(now.atOffset(ZoneOffset.ofHours(+10)));//AU        //根据给定的Unix时间戳(即自1970年1月1日00:00:00 UTC起的秒数)创建一个Instant对象        Instant instant = Instant.ofEpochSecond(1);//开始于1970        System.out.println(instant);        //设置时区        ZonedDateTime zonedDateTime = now.atZone(ZoneId.of("GMT+9"));        LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();        System.out.println(localDateTime);    }    /**     * LocalDateTime LocalDate LocalTime 的常见方法和使用     */    @Test    public void testLocalDateTime() {        // 获取当前时间        LocalDateTime now = LocalDateTime.now();        System.out.println(now);        //构造时间        LocalDateTime localDateTime = LocalDateTime.of(2019,8,8,12,23,50);        System.out.println(localDateTime);        //从LocalDate和LocalTime构造时间        System.out.println(LocalDateTime.of(LocalDate.now(), LocalTime.now()));        // 获取年月日时分秒        System.out.println(localDateTime.getYear());        System.out.println(localDateTime.getDayOfYear());        System.out.println(localDateTime.getDayOfMonth());        //星期        DayOfWeek dayOfWeek = localDateTime.getDayOfWeek();        System.out.println(dayOfWeek);        //当前时间的纳秒部分,表示这个时间点内的精细时间        System.out.println(localDateTime.getNano());        //时间计算        System.out.println(LocalDateTime.now().plusMonths(2));        System.out.println(LocalDateTime.now().minusYears(2));        System.out.println(LocalDateTime.now().plusHours(24));        System.out.println(LocalDateTime.now().plusNanos(500));        System.out.println(LocalDateTime.now().plusYears(2).plusMonths(8).plusDays(9));        // Period.of 用于创建一个表示特定时间间隔的Period对象        System.out.println(LocalDateTime.now().plus(Period.of(3, 5, 20))); ;        // ChronoUnit.DECADES代表十年        System.out.println(LocalDateTime.now().plus(3, ChronoUnit.DECADES)) ;        // 时间修改        System.out.println(LocalDateTime.now().withMonth(2));        System.out.println(LocalDateTime.now().withDayOfMonth(25));        System.out.println(LocalDateTime.now().withSecond(22));        System.out.println(LocalDateTime.now().with(ChronoField.DAY_OF_MONTH, 2));        System.out.println(LocalDateTime.now().with(ChronoField.MONTH_OF_YEAR, 8));        // LocalDate LocalTime        System.out.println(LocalDate.of(2020, 1, 19));        System.out.println(LocalDate.of(2020, Month.AUGUST, 19));        System.out.println(LocalDate.of(2020, Month.of(12), 19));        System.out.println(LocalTime.of(20, 0));        System.out.println(LocalDate.now().withMonth(8));        System.out.println(LocalDate.of(2020, Month.AUGUST, 19).plusDays(5));        System.out.println(LocalDate.of(2020, Month.of(12), 19));        System.out.println( LocalTime.of(20, 0).plusHours(8) );        // LocalDate的方法,判断当前年份是否为闰年        System.out.println(LocalDate.now().isLeapYear());    }    /**     * TemporalAdjusters 时间校正器     */    @Test    public void testTemporalAdjusters() {        // 下一个周四        LocalDateTime dateTime = LocalDateTime.now();        dateTime.with(TemporalAdjusters.next(DayOfWeek.THURSDAY));        System.out.println(dateTime);        dateTime.with(TemporalAdjusters.previous(DayOfWeek.THURSDAY));        System.out.println(dateTime);        dateTime.with(TemporalAdjusters.nextOrSame(DayOfWeek.THURSDAY));        System.out.println(dateTime);        dateTime.with(TemporalAdjusters.previousOrSame(DayOfWeek.THURSDAY));        System.out.println(dateTime);        System.out.println(LocalDate.now().with(TemporalAdjusters.nextOrSame(DayOfWeek.SATURDAY)));        // 获取月份第一天        System.out.println(LocalDate.now().with(TemporalAdjusters.firstDayOfMonth()));        System.out.println(LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth()));        // 自定义 计算下一个工作日        LocalDateTime nextWorkDay = LocalDateTime.now().with((e) -> {            LocalDateTime temp = LocalDateTime.from(e);            DayOfWeek dayOfWeek = temp.getDayOfWeek();            if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {                return temp.plusDays(3);            } else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {                return temp.plusDays(2);            } else {                return temp.plusDays(1);            }        });        System.out.println(nextWorkDay);    }    public void test() {        System.out.println(Year.now());        System.out.println(YearMonth.now());        System.out.println(MonthDay.now());    }    /**     * 计算时间间隔:武汉封了多少天,多少小时     */    @Test    public void testChronoUnit() {        LocalDateTime from = LocalDateTime.of(2020, Month.JANUARY, 23, 10, 0,0);        LocalDateTime to = LocalDateTime.of(2020, Month.APRIL, 8, 0, 0,0);        long days = ChronoUnit.DAYS.between(from, to);        long hours = ChronoUnit.HOURS.between(from, to);        System.out.println( days );        System.out.println( hours );    }    /**     * 使用 TemporalQuery 来计算当前时间与一个指定时间点(2020年1月19日10:00:00)之间的小时差,     * 并将其作为 long 类型的值返回     */    @Test    public void testTemporalQuery() {        long l = LocalDateTime.now().query(new TemporalQuery<Long>() {            @Override            public Long queryFrom(TemporalAccessor temporal) {                LocalDateTime now = LocalDateTime.from(temporal);                LocalDateTime from = LocalDateTime.of(2020, Month.JANUARY, 19, 10, 0,0);                return ChronoUnit.HOURS.between(from, now);            }        });        System.out.println(l);    }    /**     * Duration类,只能计算时间差异     */    @Test    public void testDurationPeriod() {        LocalTime start = LocalTime.of(20, 0);        LocalTime end = LocalTime.of(21, 30);        // 时间间隔        Duration between = Duration.between(start, end);        System.out.println(between.toHours());        System.out.println(between.toMinutes());    }    /**     * 格式化 DateTimeFormatter     */    @Test    public void testDateTimeFormatter() {        DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;        System.out.println(LocalDateTime.now().format(formatter));        LocalDate localDate = LocalDate.parse("2009-12-31", DateTimeFormatter.ofPattern("yyyy-MM-dd"));        System.out.println(localDate);        LocalDateTime localDateTime = LocalDateTime.parse("2009-12-31 01:01:02", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));        System.out.println(localDateTime);        // 2024年12月1日 星期日        System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL)));        // 2024年12月1日        System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)));        // 24-12-1        System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)));        // 2024-12-1        System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)));    }    @Test    public void getAvailableZoneIds() {        // 当前系统时区        System.out.println(ZoneId.systemDefault());        // 打印java8中所有支持时区        ZoneId.getAvailableZoneIds().forEach(System.out::println);    }    /**     * OffsetDateTime     */    @Test    public void testOffsetDateTime() {        OffsetDateTime offsetDateTime = new Date().toInstant().atOffset(ZoneOffset.of("-4"));        System.out.println(offsetDateTime);        System.out.println(offsetDateTime.toLocalDateTime());        OffsetDateTime of = OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.of("-4"));        System.out.println(of);    }    /**     * ZonedDateTime     */    @Test    public void testZonedDateTime() {        // 当前时间转换为东京时间是几时        ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));        System.out.println(zonedDateTime);        System.out.println(zonedDateTime.toLocalDateTime());        ZonedDateTime of = ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("Asia/Tokyo"));        System.out.println(of);        // 为当前时间带上时区        ZonedDateTime tokyo = LocalDateTime.now().atZone(ZoneId.of("Asia/Tokyo"));        System.out.println(tokyo);        System.out.println(tokyo.toLocalDateTime());        // 将一个时区时间转换为同一时刻另一个时区时间        ZonedDateTime beijing = tokyo.withZoneSameInstant(ZoneId.of("GMT+8"));        System.out.println(beijing);        ZonedDateTime usa = LocalDateTime.now()                .atZone(ZoneId.systemDefault())                .withZoneSameInstant(ZoneId.of("GMT-4"));        System.out.println(usa);    }}

新API和旧的Date之前的互转

package test.time;import org.junit.Test;import java.sql.Timestamp;import java.time.*;import java.util.Calendar;import java.util.Date;public class Test5 {    /**     * 将 LocalDateTime 和系统默认时区结合,转换为 ZonedDateTime     * 再将 ZonedDateTime 转换为 Instant,这是一个包含 UTC 时间戳的对象。     * Date.from():将 Instant 转换为 java.util.Date 对象     */    @Test    public void toDate() {        LocalDateTime localDateTime = LocalDateTime.now();        Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();        Date date = Date.from(instant);        System.out.println(date);    }    @Test    public void toLocalDateTime() {        Date date = new Date();        LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());        System.out.println(dateTime);    }    /**     * java.sql.Date 转换 LocalDateTime     */    @Test    public void sqlDate() {        java.sql.Date date = new java.sql.Date(System.currentTimeMillis());        LocalDate localDate = date.toLocalDate();        System.out.println(localDate);        Timestamp timestamp = new Timestamp(System.currentTimeMillis());        LocalDateTime localDateTime = timestamp.toLocalDateTime();        System.out.println(localDateTime);    }    /**     * Calendar 转换 LocalDateTime     */    @Test    public void calendarToLocalDateTime() {        Calendar calendar = Calendar.getInstance();        ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId());        System.out.println(zonedDateTime.toLocalDateTime());    }}

六、Optional

  java.util.Optional是一个容器类,用来表示可能包含或者不包含值的对象。它提供了一种优雅的方式来避免出现空指针,从而帮助开发者更安全、更清晰地处理可能为NULL的值。

6.1 创建

包装一个非空的值,如果传入的变量为null会直接抛出空指针异常,如果直接写死null进去,IDEA可能直接编译出错

Optional<String> optional = Optional.of("Hello, World!");

ofNullable方法允许填充一个可能为空的值进去

Optional<String> optional = Optional.ofNullable(null);

空的Optional对象

Optional<String> optional = Optional.empty();

6.2 检查

可以使用isPresent()方法判断

Optional<String> optional = Optional.empty();if (optional.isPresent()) {    System.out.println("Value: " + optional.get());} else {    System.out.println("No value present");}

还可以采用ifPresent()避免if显式调用

optional.ifPresent(value -> System.out.println("Value: " + value));

6.3 默认值

如果为空,提供一个默认值

Optional<String> optional = Optional.empty();String value = optional.orElse("Default Value");

还可以通过提供的Supplier函数式接口生成默认值

Optional<String> optional = Optional.empty();String value = optional.orElseGet(() -> "Generated Default Value");// optional.orElseGet(String::new);

如果值不存在,可以抛出自定义异常

Optional<String> optional = Optional.empty();String value = optional.orElseThrow(() -> new RuntimeException("Value is missing!"));

6.4 转换

map() 如果有值进行处理,并返回处理后的Optional对象,否则返回Optional.empty()

空值,不执行输出

Optional<String> optional = Optional.empty();Optional<String> upperCase = optional.map(String::toUpperCase);upperCase.ifPresent(System.out::println); 

非空,处理后返回新的Optional,输出:HELLO WORLD

Optional<String> optional = Optional.ofNullable("hello world");Optional<String> upperCase = optional.map(String::toUpperCase);upperCase.ifPresent(System.out::println); 

使用flatMap()进一步防止空指针异常,如果optional中的值为null,flatMap()直接返回Optional.empty(),否则,它返回一个包含e.getName()的Optional对象

Employee employee = new Employee();employee.setName("XXX");Optional<Employee> optional = Optional.ofNullable(employee);Optional<String> s = optional.flatMap((e) -> Optional.of(e.getName()));s.ifPresent(System.out::println);

七、重复注解 (Repeating Annotations)

1.首先创建一个容器注解,这个注解类型包含一个注解数组,存储多个相同类型的注解

package test.anno;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import static java.lang.annotation.ElementType.CONSTRUCTOR;import static java.lang.annotation.ElementType.FIELD;import static java.lang.annotation.ElementType.LOCAL_VARIABLE;import static java.lang.annotation.ElementType.METHOD;import static java.lang.annotation.ElementType.PARAMETER;import static java.lang.annotation.ElementType.TYPE;@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})@Retention(RetentionPolicy.RUNTIME)public @interface MyAnnotations {    MyAnnotation[] value();}

2.定义一个重复注解,并使用@Repeatable标记

package test.anno;import java.lang.annotation.Repeatable;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import static java.lang.annotation.ElementType.CONSTRUCTOR;import static java.lang.annotation.ElementType.FIELD;import static java.lang.annotation.ElementType.LOCAL_VARIABLE;import static java.lang.annotation.ElementType.METHOD;import static java.lang.annotation.ElementType.PARAMETER;import static java.lang.annotation.ElementType.TYPE;import static java.lang.annotation.ElementType.TYPE_PARAMETER; // 类型注解@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE_PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Repeatable(MyAnnotations.class)public @interface MyAnnotation {    String value() default "hello world";}

3.测试,通过反射访问方法上的注解,由于MyAnnotation是重复注解,所以一个方法加上多个也不会语法报错,然后提取其中的多个MyAnnotation注解。

package test.anno;import java.lang.reflect.Method;import java.util.Arrays;public class TestAnnotation {        @MyAnnotation("hello")    @MyAnnotation("world")    public void test(String s) {            }    public static void main(String[] args) {        Class<TestAnnotation> clazz = TestAnnotation.class;        try {            Method method = clazz.getMethod("test", String.class);            MyAnnotation[] annotations = method.getAnnotationsByType(MyAnnotation.class);            Arrays.stream(annotations).map(MyAnnotation::value).forEach(System.out::println);        }         catch (Exception e) {            e.printStackTrace();        }                    }}

volatile作用分析

2022年5月1日 09:14

Java 内存模型 (JMM) 中的一个核心问题是线程对共享变量的可见性。在多线程环境中,每个线程都有自己的工作内存(即 CPU 缓存)。当一个线程修改了某个变量,其他线程并不能立即看到这个修改,因为每个线程可能都在使用自己工作内存中的缓存值。

volatile 的可见性机制:

  • 强制刷新主内存:当一个线程对 volatile 修饰的变量进行写操作时,修改后的值会立即被刷新到主内存中,而不是缓存在该线程的工作内存中。

  • 强制读取主内存:当一个线程对 volatile 修饰的变量进行读操作时,会直接从主内存中读取最新值,而不会从线程的工作内存中读取缓存的值。

  • 示例代码

public class TestVolatile {        static volatile boolean flag = false;        public static void main(String[] args) {                new Thread(() -> {            try {                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }            flag = true;                        System.out.println("子线程修改flag的值为:  " + flag);                    }).start();                        // while(true) 调用底层代码,效率极高,不会从主存中再次获取被其他线程修改过的数据        while (true) {            if (flag) {                System.out.println("flag is true 主线程结束循环!");                break;            }        }    }}

因此,volatile 保证了变量的可见性,即当一个线程修改了 volatile 变量后,其他线程能够立即看到最新的值。

volatile 与 synchronized 的对比

特性volatilesynchronized
可见性保证可见性保证可见性
原子性不保证原子性保证原子性
重排序禁止指令重排序保证顺序执行
性能开销较低(无锁机制)较高(加锁/解锁开销)
使用场景适用于简单状态标志位或单次读写适用于复杂的临界区保护

Java的线程和常见方法

2022年5月1日 00:00

在学习和使用Java的多线程前,需要了解一些关于计算机中进程,线程的基础知识。

1.线程,进程和管程

1.1 线程(Thread)

  • 定义:线程是操作系统中能够独立运行的最小单位,是进程的一个执行分支。一个进程可以包含多个线程,它们共享同一进程的资源(如内存和文件句柄)。
  • 特点
    • 线程之间的创建和销毁开销较小。
    • 线程间共享内存,通信较为高效,但也容易引发竞争条件和数据不一致问题。

1.2 进程(Process)

  • 定义:进程是程序在计算机上运行的实例,它拥有自己的内存空间和资源。进程之间是相互独立的,通常通过进程间通信(IPC)进行数据交换。
  • 特点
    • 进程有自己的地址空间,线程间不共享内存。
    • 进程的创建和销毁开销较大,但提供更好的隔离性和稳定性。

1.3 管程(Monitor)

  • 定义:管程是一种高层次的同步机制,用于控制对共享资源的访问。它将共享资源的访问和管理封装在一个对象中,并提供互斥访问。
  • 特点
    • 管程通常包括一个互斥锁和一些条件变量。
    • 通过管程,可以避免线程间的竞争条件,简化线程同步的复杂性。

2.串行、并行和并发

2.1 串行(Serial)

  • 定义:派发多个任务,所有任务都按照顺序先后执行。
  • 特点:顺序执行,执行总时长几乎等于每个任务执行的时间相加。

2.2 并行(Parallelism)

  • 定义:并行是指派发多个任务在同一时刻同时执行直到全部完成。通常是在多核处理器上,多个任务可以同时在不同的核心上运行。
  • 特点:同一时间分别执行,每个任务执行都不被打断,执行总时长约等于耗时最长的那个任务需要的时间。

2.3 并发(Concurrency)

  • 定义:派发多个任务在同一时间段内进行,不一定是同时执行的。任务可能在共享的时间片上交替运行。
  • 特点:同一时间交替的执行,任务有被其他任务抢走时间片后中断和抢占其他任务的时间片的可能,执行总时长可能小于以串行或并行来执行这些任务的总时长。

3.Java多线程的实现和常见方法

3.1 继承Thread类创建子线程

通过继承java.lang.Thread,重写其run()方法来创建自定义线程类,然后实例化该类并调用start()方法启动线程,jvm自动调用run()方法,run()方法运行的就是子线程。

start()方法执行后,线程不一定立即创建,因为线程是操作系统的资源,需要等待操作系统分配

1.可以通过重写带参(线程名)构造函数,或调用setName()设置线程名。

2.每个Thread只能执行一次run()方法否则会出现IllegalThreadStateException异常,如果我们自己直接运行线程的run()方法等同于对象调用方法,仍然是单线程。

3.通过Thread.currentThread()可以获取当前运行的线程。

class MyThread extends Thread {    public MyThread(String name) {        super(name);    }    @Override    public void run() {        System.out.println("Thread is running.");        System.out.println(Thread.currentThread().getName());    }}MyThread thread = new MyThread("t1");thread.start();
class MyThread extends Thread {    @Override    public void run() {        System.out.println("Thread is running.");        System.out.println(Thread.currentThread().getName());    }}MyThread thread = new MyThread();thread.setName("t1");thread.start();

通过匿名内部类写法

new Thread("t1") {   @Override   public void run() {      System.out.println("Thread is running.");      System.out.println(Thread.currentThread().getName());   }}.start();

3.2 实现Runnable接口创建子线程

通过实现java.lang.Runnable接口并重写其run()方法实现一个Target对象,然后将该Target传递给Thread,同时可以选择指定一个线程名,再调用Thread的start()方法启动线程,jvm自动调用run()方法,在run()方法开启子线程。

实现Runnable接口创建子线程,既可以避免单继承的局限,又能使得代码更加清晰,把子线程的任务与执行任务的Thread对象分开,可以用一个任务创建出多个线程同时执行。

class MyRunnable implements Runnable {    @Override    public void run() {        System.out.println("Thread is running.");    }}Thread thread = new Thread(new MyRunnable());thread.start();Thread thread2 = new Thread(new MyRunnable(), "t2");thread2.start();

无论哪种方式创建线程,都不能自己手动调用Thread的run()方法,必须调用start()start()方法最终调用C++实现的native方法start0(),由JVM调用run()方法,所以自己调用run()方法无法实现多线程。
java.lang.Thread

public synchronized void start() {  /**  * This method is not invoked for the main method thread or "system"  * group threads created/set up by the VM. Any new functionality added  * to this method in the future may have to also be added to the VM.  *  * A zero status value corresponds to state "NEW".  */  if (threadStatus != 0)     throw new IllegalThreadStateException();  /* Notify the group that this thread is about to be started  * so that it can be added to the group's list of threads  * and the group's unstarted count can be decremented. */  group.add(this);  boolean started = false;  try {     start0();     started = true;  } finally {     try {           if (!started) {              group.threadStartFailed(this);           }     } catch (Throwable ignore) {           /* do nothing. If start0 threw a Throwable then           it will be passed up the call stack */     }  }}private native void start0();

3.3 创建守护线程

JVM的线程分为用户线程和守护线程

用户线程:系统的工作线程,会完成这个程序需要完成的业务操作。

守护线程:服务线程,没有服务对象就没有必要继续运行下去了。

Java中,通过设置setDaemon(true)来实现一个守护线程,需要在调用start()方法之前设置,当主线程结束后,守护线程即使还有任务未完成,JVM进程也会退出。如果子线程没有设置为守护线程,即使主线程完成,子线程仍然继续执行未完成的任务,JVM不会退出。

public static void main(String[] args) {   Thread daemon = new Thread(() -> {      System.out.println(Thread.currentThread().getName());      while (true) {      }   }, "daemon");   daemon.setDaemon(true);   daemon.start();   System.out.println(Thread.currentThread().getName());}

3.4 线程的暂停:sleep()

暂停执行(睡眠)多少毫秒,让出CPU,但是不释放锁

Thread.sleep(2000);

3.5 检查线程是否存活:isAlive()

通过Thread.sleep();使线程休眠,观测线程开启和结束后的状态。

public static void main(String[] args) {   Thread t1 = new Thread(() -> {      System.out.println(Thread.currentThread().getName());      try {         Thread.sleep(2000);      } catch (InterruptedException e) {         throw new RuntimeException(e);      }   }, "t1");   t1.start();   System.out.println(t1.isAlive()); //true   try {      Thread.sleep(4000);   } catch (InterruptedException e) {      throw new RuntimeException(e);   }   System.out.println(t1.isAlive()); //false}

3.6 让出CPU:yield()

静态方法,当前线程让出CPU,给同级或更高优先级线程机会,不会释放锁。

Thread.yield();

还有一些线程的方法涉及到线程间的通信,见:Java线程间的通信机制

Java线程间的通信机制

2022年5月1日 00:00

当需要多个线程共同完成一件任务,而且需要有规律的执行,那么多个线程之间需要一定的通信机制,可以协调他们的工作,以此实现多线程共同操作一份数据。

1.等待唤醒机制

这是一种线程间的协作机制,与争夺锁的竞争机制相对应,当一个线程满足某个条件时,就进入等待状态( wait/wait(m) ),等到其他线程执行完指定的代码后,再将其唤醒,或者可以指定时间,到时间了自动唤醒,有多个线程等待时,如果有需要,可以notifyAll()唤醒所有等待的线程,wait/notify就是一种线程间的协助机制。

wait()notify()notifyAll()都是java.lang.Object中的方法,这说明在Java语言的设计中,任何对象都能充当同步监视器(锁)

1.1 wait

作用:使当前线程等待,直到其他线程调用相同对象的notify()notifyAll()方法,或者线程被中断。
使用场景:当一个线程需要等待某个条件发生时,比如资源可用、状态改变等。
条件:调用wait()方法时,当前线程必须持有该对象的监视器锁(synchronized),否则会抛出 IllegalMonitorStateException 异常。

synchronized (lock) {    while (condition) {        lock.wait(); // 释放锁并等待    }    // 条件满足后的操作}

1.2 notify

作用:唤醒在该对象上等待的一个线程。如果有多个线程在等待,则随机选择一个线程唤醒。
使用场景:在某个条件被满足时,通知一个等待的线程继续执行。

synchronized (lock) {    // 修改条件    lock.notify(); // 唤醒一个等待线程}

1.3 notifyAll

作用:唤醒在该对象上等待的所有线程。
使用场景:当条件变化可能影响所有等待线程时,使用 notifyAll() 确保所有线程都有机会重新检查条件。

synchronized (lock) {    // 修改条件    lock.notifyAll(); // 唤醒所有等待线程}

1.4 例:两个线程交替累加

第1次执行:假设线程1先抢到锁,进去后会先唤醒了线程2,但是锁还是线程1持有的,线程2只能等待,线程1将变量i加1变成1,wait释放锁进入等待唤醒状态。

第2次执行:线程2拿到锁后同样先唤醒了线程1,但是现在锁是线程2持有,线程1无法执行,线程2将变量i加1变成2后,wait释放锁进入等待唤醒状态。

第3次执行:线程1拿到锁还是先唤醒线程2然后将变量i加1变成3,然后wait释放锁。

……

第99次执行:线程1拿到锁还是先唤醒线程2然后执行加1将变量i变成99,然后wait释放锁。

第100次执行:线程2进来先唤醒了线程1,然后将i加到100后wait释放锁,此时循环加到100就已经完成,但是线程的执行还没有结束,被唤醒的线程1继续执行,先唤醒了线程2然后进入if判断,但是这次已经不能再加了,所以线程1没有wait就退出了while循环,离开synchronized块后自动失去锁,线程2随后自然得到锁进入synchronized块,同样判断已经不能再加了后随即也跳出while循环,线程1和线程2就都在就绪状态自然结束了,随后JVM进程退出。

public class TestAdd {    static int i = 0;    public static void main(String[] args) {        Runnable runnable = new Runnable() {            @Override            public void run() {                while (true) {                    synchronized (this){                        this.notify();                        if (i < 100) {                            System.out.println(Thread.currentThread().getName() + "---" + ++i);                        }                        else {                            break;                        }                        try {                            this.wait();                        } catch (InterruptedException e) {                            throw new RuntimeException(e);                        }                    }                }            }        };        new Thread(runnable).start();        new Thread(runnable).start();    }    }

1.5 wait退出时检查

lock.wait()阻塞了的线程,一旦被唤醒,或超时时,会从wait()方法下面的代码继续向下执行,即直接跳出if执行下面的代码,需要注意的是此时已经醒来的线程并不会再次判断if中的条件是否满足而是跳出if直接向下执行,这样就会遇到一个问题,线程阻塞期间,其他线程进行的一些操作可能造成条件改变,不能满足if中的条件了,如果不加以二次判断就继续执行,就可能导致程序出错。

synchronized (lock) {    if (条件不满足) {        lock.wait();    }    //继续执行后面操作}

Java给出的解决办法是:让wait总是出现在循环中,使用while去判断wait的条件而不是if,当使用while时,线程被唤醒后,不会继续跳出while块向下执行,而是会再判断一次while中的逻辑,如果条件不满足会继续wait,直到条件满足跳出while。

synchronized (lock) {    while (条件不满足) {        lock.wait();    }    //继续执行后面操作}

例1:如果使用if,wait中的线程被唤醒时,不会再次判断if中的条件

public class SimpleWakeupDemo {    private volatile static boolean flag = true;     private static final Object lock = new Object();    public static boolean condition() {        System.out.println(Thread.currentThread().getName() + "判断flag = " + flag);        return flag;    }    public static void main(String[] args) {        Thread t1 = new Thread() {            @Override            public void run() {                synchronized (lock) {                    if (condition()) {                        try {                            System.out.println(Thread.currentThread().getName() +" before wait");                            lock.wait();                            System.out.println(Thread.currentThread().getName() +" after wait");                        } catch (InterruptedException e) {                            throw new RuntimeException(e);                        }                    }                    System.out.println(Thread.currentThread().getName() + "打印flag = " + flag);                }            }        };        Thread t2 = new Thread() {            @Override            public void run() {                synchronized (lock) {                    try {                        Thread.sleep(900);                        lock.notifyAll();                    } catch (InterruptedException e) {                        throw new RuntimeException(e);                    }                }            }        };        t1.start();        t2.start();    }}
Thread-0判断flag = trueThread-0 before waitThread-0 after waitThread-0打印flag = true

如果改成while,可以看到wait结束后,会再次判断条件是否成立

while (condition()) {    try {        System.out.println(Thread.currentThread().getName() +" before wait");        lock.wait();        System.out.println(Thread.currentThread().getName() +" after wait");    } catch (InterruptedException e) {        throw new RuntimeException(e);    }}
Thread-0判断flag = trueThread-0 before waitThread-0 after waitThread-0判断flag = trueThread-0 before wait

例2:生产消费模型:两个线程操作同一变量,一个判断变量为0就加1变成1,另一个判断变量为1就减1变成0

public class SimpleWakeupDemo {    private volatile static int product = 0;     private static final Object lock = new Object();    public static void main(String[] args) {        Runnable consumer = new Runnable() {            @Override            public void run() {                synchronized (lock) {                    while (product < 1) {                        try {                            System.out.println(Thread.currentThread().getName() + "不足");                            System.out.println("before wait");                            lock.wait();                            System.out.println("after wait");                        } catch (InterruptedException e) {                            throw new RuntimeException(e);                        }                    }                    product --;                    System.out.println(Thread.currentThread().getName() + " = " + product);                    lock.notifyAll();                }            }        };        Runnable productor = new Runnable() {            @Override            public void run() {                synchronized (lock) {                    try {                        Thread.sleep(900);                    } catch (InterruptedException e) {                        throw new RuntimeException(e);                    }                    while (!(product < 1)) {                        try {                            System.out.println(Thread.currentThread().getName() + "已满");                            System.out.println("before wait");                            lock.wait();                            System.out.println("after wait");                        } catch (InterruptedException e) {                            throw new RuntimeException(e);                        }                    }                    product ++;                    System.out.println(Thread.currentThread().getName() + " = " + product);                    lock.notifyAll();                }            }        };        for (int i = 0; i < 10; i++) {            new Thread(productor, "productor-"+i).start();            new Thread(consumer, "consumer-"+i).start();        }    }}

当使用while时,操作后的数总是0或1

productor-0 = 1productor-3已满before waitconsumer-8 = 0productor-9 = 1productor-8已满before waitconsumer-1 = 0productor-7 = 1productor-6已满before waitconsumer-6 = 0consumer-5不足before waitproductor-1 = 1consumer-4 = 0productor-4 = 1consumer-3 = 0productor-2 = 1consumer-9 = 0consumer-2不足before waitconsumer-7不足before waitproductor-5 = 1consumer-0 = 0after waitconsumer-7不足before waitafter waitconsumer-2不足before waitafter waitconsumer-5不足before waitafter waitproductor-6 = 1after waitproductor-8已满before waitafter waitproductor-3已满before waitafter waitconsumer-5 = 0after waitconsumer-2不足before waitafter waitconsumer-7不足before waitafter waitproductor-3 = 1after waitproductor-8已满before waitafter waitconsumer-7 = 0after waitconsumer-2不足before waitafter waitproductor-8 = 1after waitconsumer-2 = 0

如果换成if,就会错误的出现其他的数字

productor-0 = 1productor-2已满before waitproductor-9已满before waitconsumer-8 = 0productor-7 = 1productor-8已满before waitconsumer-7 = 0consumer-6不足before waitconsumer-4不足before waitproductor-6 = 1consumer-5 = 0productor-5 = 1productor-4已满before waitconsumer-3 = 0productor-1 = 1productor-3已满before waitconsumer-2 = 0consumer-9不足before waitconsumer-1不足before waitconsumer-0不足before waitafter waitproductor-3 = 1after waitproductor-4 = 2after waitconsumer-4 = 1after waitconsumer-6 = 0after waitproductor-8 = 1after waitproductor-9 = 2after waitproductor-2 = 3after waitconsumer-0 = 2after waitconsumer-1 = 1after waitconsumer-9 = 0

1.6 小总结

线程互相交替执行的过程可以简记为:1判断,2干活,3通知

synchronized (Object) {    while (....) { //判断        try {            Object.wait();        } catch (InterruptedException e) {            throw new RuntimeException(e);        }    }    //干活    //.........    Object.notifyAll(); //通知}   

2.线程连接:join方法

主执行中插入其他线程m1,主线程立刻被阻塞,直到插入的线程m1执行完成。

public static void main(String s[]) {    Thread m1 = new Thread() {        @Override        public void run() {            for (int i = 0; i < 100; i++) {                try {                    Thread.sleep(100);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println(this.getName() +" = " + i);            }        }    };    m1.setName("m1");    m1.start();    for (int i = 0; i < 100; i++) {        /**i加到20的时候,插入子线程,子线程执行完了(消亡),主线程再继续*/        if (i == 20) {            try {                m1.join();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        System.out.println(Thread.currentThread().getName() +" = " + i);    }}

3.volatile

volatile是一种轻量级的线程通信机制,具体见:volatile作用分析

Java线程安全和同步机制

2022年5月1日 00:00

当多个线程同时访问同一资源(变量,文件,记录),如果只有读操作,则不会有线程安全问题,如果有读和写操作,则会产生线程安全问题,必须保证共享数据同一时刻只能有同一个线程操作。Java采取的办法是synchronized同步代码块或同步方法。同步代码块或同步方法解决了线程安全问题,但是操作共享数据时,线程时串行执行的,意味着效率较低。

1.多线程安全问题

经典卖票案例:

两个线程一块卖票,没有加同步代码块,程序运行结果不正确,存在超卖重卖

public class Ticket {    public static void main(String[] args) {        TicketTask t1 = new TicketTask();        TicketTask t2 = new TicketTask();        t1.start();        t2.start();    }    static class TicketTask extends Thread {        static int ticket = 200;        @Override        public void run() {            while (true) {                if (ticket > 0) {                    System.out.println(Thread.currentThread().getName() +" " + ticket);                    ticket--;                }                else {                    break;                }            }        }    }}

2 同步代码块和同步方法解决线程安全问题

2.1 同步代码块

需要被同步的代码,即为操作共享数据的代码。共享数据,即为多个线程都需要操作的数据。同步监视器可以由任何类的对象担任,但是多个线程必须共用同一个同步监视器。

同步代码块的语法

synchronized (同步监视器/) {  //需要被同步的代码}

加入同步代码块,程序运行结果正确,当有线程操作共享数据,其他线程需要等待。lock作为同步监视器,锁住代码块中的操作,谁获得同步监视器,谁运行同步代码块中的代码。

public class Ticket {    public static void main(String[] args) {        TicketTask t1 = new TicketTask();        TicketTask t2 = new TicketTask();        t1.start();        t2.start();    }    static class TicketTask extends Thread {        static int ticket = 200;        static final Object lock = new Object();        @Override        public void run() {            while (true) {                synchronized (lock) {                    if (ticket > 0) {                        System.out.println(Thread.currentThread().getName() +" " + ticket);                        ticket--;                    }                    else {                        break;                    }                }            }        }    }}

2.2 同步方法

如果需要同步执行的代码恰好在一个方法中,可以使用同步方法保证线程安全,在方法声明上使用 synchronized 关键字,此时锁为对象实例(this)。同一时刻,只有一个线程能够执行该实例的方法。

public synchronized void test() {}

3 同步代码块和同步方法的使用

3.1 Runnable创建线程时使用同步代码块

使用synchronized使得实例方法加锁变为同步方法,因为Runnable对象只有一个,所以锁可以直接使用当前调用者this

public class TestRunnable {    public static void main(String[] args) {        Target target = new Target();        for (int i = 0; i < 10; i++) {            new Thread(target, "T"+i).start();        }    }}class Target implements Runnable {    private Integer i = 1000;    @Override    public void run() {        while (true) {            try {                Thread.sleep(200);            } catch (InterruptedException e) {                e.printStackTrace();            }            synchronized (this) {                if (i > 0) {                    i--;                    System.out.println(Thread.currentThread().getName() + "->" + i);                } else {                    break;                }            }        }    }}

3.2 Thread类创建线程时,使用同步代码块

Thread类创建线程时,使用同步代码块加锁,因为Thread对象是多个,所以需要静态的监视器对象object,如果还用this就出现了多个锁

public class TestThread {    public static void main(String[] args) {        for (int i = 0; i < 10; i++) {            new MyThread("T"+i).start();        }    }}class MyThread extends Thread {    private static Integer i = 1000;    //同步监视器,锁    private static Object object = new Object();    public MyThread(String name) {        super(name);    }    @Override    public void run() {        while(true) {             synchronized(object) {                if (i > 0) {                    i--;                    System.out.println(Thread.currentThread().getName() +"->" + i);                } else {                    break;                }            }        }    }}

3.3 Runnable创建线程时,使用同步方法加锁

public class TestRunnable {    public static void main(String[] args) {        Runnable runnable = new Runnable() {            private int num = 1000;            @Override            public void run() {                while (true) {                    this.show();                }            }             public synchronized void show() {                try {                    Thread.sleep(20);                } catch (InterruptedException e) {                    e.printStackTrace();                }                if (num > 0) {                    num--;                    System.out.println(Thread.currentThread().getName() + "->" + num);                }            }        };        for (int i = 0; i < 10; i++) {            new Thread(runnable, "T" + i).start();        }    }}

3.4 Thread创建线程时,使用同步方法加锁

public class TestThread {    public static void main(String[] args) {        TicketTest ticketTest = new TicketTest();        for (int i = 0; i < 10; i++) {            new Thread("T"+i) {                @Override                public void run() {                    while (true) {                        ticketTest.test();                    }                }            }.start();        }    }    static class TicketTest  {        private int num = 1000;        private synchronized void test() {            if (num > 0) {                num--;                System.out.println(Thread.currentThread().getName() + "->" + num);            }        }    }}

3.5 静态方法加锁和使用.class对象做锁

在静态方法上使用 synchronized,锁住的是类的.class对象,每个类的class对象只有一个,所以同时只能有一个线程进入方法。

public static synchronized void staticMethod() {    // 方法体}

同步块上使用.class对象做锁,因为每个类.class对象只有一个,故也能用于保证线程安全

synchronized (A.class) {}

4 死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁。一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

写程序时,要避免出现死锁。

示例代码1

死锁原因的分析:这个例子是个比较明显的死锁,线程t1t2几乎同时启动,在一秒钟的等待时间里,t1获得了锁lock1t2获得了锁lock2,一秒钟后t1又想去获得lock2,但是现在lock2t2持有,需要一直等直到t2释放lock2,与此同时,t2也想去获得lock1,但是lock1现在被t1持有,需要一直等待,直到t1释放lock1,两个线程都在争抢在对方持有的锁,且都在等待对方先释放各自持有的锁,不然就一直等待,线程都一直处在阻塞状态无法继续运行,造成死锁。

public class TestDeadLock {    public static void main(String[] args) {        Object lock1 = new Object();        Object lock2 = new Object();        Thread t1 = new Thread() {            @Override            public void run() {                synchronized (lock1) {                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        Thread.currentThread().interrupt();                    }                    synchronized (lock2) {                        System.out.println(lock1);                        System.out.println(lock2);                    }                }            }        };        Thread t2 = new Thread(){            @Override            public void run() {                synchronized (lock2) {                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        Thread.currentThread().interrupt();                    }                    synchronized (lock1) {                        System.out.println(lock1);                        System.out.println(lock2);                    }                }            }        };        t1.start();        t2.start();    }}

示例代码2

这是一个不是非常明显的死锁的例子,线程thread1thread2几乎同时开始执行,thread1执行a.fun(b)时,由于A类的fun方法是个同步方法,故锁是当前调用者this对象,即a,调用fun方法,thread1便持有了锁a,与此同时,thread2同理的持有了锁b,这些都在一秒钟前完成了,1秒钟后,thread1执行blast同步方法,同理需要先获得锁b,但是锁b目前被thread2持有,同时thread2也开始执行alast方法,需要先持有锁a,但是锁athread1持有,双方都在等待对方先释放自己需要的锁,否则就一直阻塞无法继续运行,造成死锁。

public class TestDeadLock2  {    public static void main(String[] args) {        A a = new A();        B b = new B();        Thread thread1 = new Thread() {            @Override            public void run() {                a.fun(b);            }        };        Thread thread2 = new Thread() {            @Override            public void run() {                b.fun(a);            }        };        thread1.start();        thread2.start();    }    public static class A extends Thread {        public synchronized void fun(B b) {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                throw new RuntimeException(e);            }            b.last();        }        public synchronized void last() {        }    }    public static class B extends Thread {        public synchronized void fun(A a) {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                throw new RuntimeException(e);            }            a.last();        }        public synchronized void last() {        }    }}
❌