JUC并发编程
0、普通线程知识回顾
- 线程创建方式对比
- 静态代理模式
- 线程的几种状态
1、基础知识
- java默认有两个线程,一个是main,一个是GC
- 创建线程的方法:继承Thread类 、实现Runnable接口、Callable接口
//Thread
public class MyThread extends Thread {
private String name;
public MyThread(String name){
this.name = name;
}
/**
* 这就是线程执行的逻辑体
*/
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(name+"下载了"+i+"%");
}
}
}
//测试代码
public class ThreadTest {
public static void main(String[] args) {
//创建一个线程的对象
MyThread mt = new MyThread("肖申克的救赎");
//启动一个线程
mt.start();
//创建一个线程的对象
MyThread mt1 = new MyThread("当幸福来敲门");
//启动一个线程
mt1.start();
//System.out.println("方法结束");
}
}
//Runnable
public class DownLoad implements Runnable {
private String name;
public DownLoad(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(name+"下载了"+i+"%");
}
}
}
//测试代码
public class ThreadTest {
public static void main(String[] args) {
//创建线程对象
Thread t = new Thread(new DownLoad("肖申克的救赎"));
Thread t1 = new Thread(new DownLoad("当幸福来敲门"));
t.start();
t1.start();
}
}
//Callable
/**
1. 读文件大小
*/
public class ReadFileCallable implements Callable<Long> {
private String fileName;
public ReadFileCallable(String fileName) {
this.fileName = fileName;
}
/**
* 计算文件大小并返回
*/
@Override
public Long call() throws Exception {
File f = new File(fileName);
if (f.exists() && f.isFile()) {
return f.length();
} else {
return null;
}
}
/**
* 测试
*/
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<Long> task = new FutureTask<Long>(new ReadFileCallable("D:\\temp\\501.txt"));
Thread thread = new Thread(task);
thread.start();
System.out.println(task.get());// 等待结果返回
}
}
问题:实现callable接口启动线程的时候为什么用FutureTask?
关系图:
答:我们都知道实现Runnable接口可以启动线程,而Runnable有几个实现类,其中一个就是FutureTask,
关于Callable的细节:
- java自己是无法开启线程的,它底层调用了start0();是一个native修饰的本地方法,也就是说调用了底层的c++,也就是说java无法直接操作硬件
2、关于synchronized锁的八个问题(八锁现象)
案例1:
原因:不是因为先调用的发短信再调用打电话导致一定先打印发短信,而是因为synchronized锁的存在,锁的对象是方法的调用者,调用者都是new出来的phone,即两个方法用的是同一个锁
案例2:
原因:进入hello方法不需要锁,所以当发短信延迟4秒等待的时候,hello直接就进去了
案例3:
原因:new了两个phone对象,所占用锁不一样,发短信延迟4秒的时候,也可以进入打电话方法
案例4:
原因:被static修饰的方法在类加载时候就已经存在了,可以理解为是一个模板,实际上就是Phone3这个类被加了锁
案例5:
原因:和上面一样,被static修饰的方法在类加载时候就已经存在了,可以理解为是一个模板,实际上就是Phone3这个类被加了锁,所以不管new几个对象出来,都是加载的同一个类模板
案例6:
原因:被static修饰的发短信方法在类加载时候就已经存在了,可以理解为是一个模板,实际上就是Phone4这个类被加了锁,而打电话这个方法没有被static修饰,所以它锁的还是调用对象,当new出一个对象的时候,类的锁和对象的锁互不影响,所以先执行打电话
案例7:
原因:被static修饰的发短信方法在类加载时候就已经存在了,可以理解为是一个模板,实际上就是Phone4这个类被加了锁,而打电话这个方法没有被static修饰,所以它锁的还是调用对象,当new出两个对象的时候,类的锁和对象的锁互不影响,所以先执行打电话
案例总结: new this 具体的一个手机
static Class 唯一的模板
3、并发集合类
案例:
分析:执行结果会有异常,因为ArrayList是线程不安全的,解决方案有以下几个
- ArrayList改成用synchronized修饰的Vector
List<String> list=new Vector<>();
但是Vector是在ArrayList之前JDK1.0都已经有了,1.2又补充了ArrayList,这说明Vector并不是最佳解决方案 - 不想写了,参考我的另一篇博客 第一阶段的第2条集合类线程不安全问题
4、线程锁
不想写了,参考我的另一篇博客 第一阶段的第3条线程锁
5、阻塞队列
具体参考参考我的另一篇博客 第一阶段的第4条阻塞队列(BlockingQueue,先进先出)
5、线程池
具体参考参考我的另一篇博客 第一阶段的第7条线程池)
6、函数式接口
1、概念介绍
JDK 8以前的接口:
interface 接口名 {
静态常量;
抽象方法;
}
JDK 8对接口的增强,接口还可以有默认方法和静态方法
interface 接口名 {
静态常量;
抽象方法;
默认方法;
静态方法;
}
接口默认方法和静态方法的区别
- 默认方法通过实例调用,静态方法通过接口名调用。
- 默认方法可以被继承,实现类可以直接使用接口默认方法,也可以重写接口默认方法。
- 静态方法不能被继承,实现类不能重写接口静态方法,只能使用接口名调用。
总结:如果这个方法需要被实现类继承或重写,使用默认方法,如果接口中的方法不需要被继承就使用静态方法
内置的函数式接口具体参考:JUC学习笔记,6、四大函数式接口
2、四大内置函数式接口
-
Function函数型接口
-
Predicate断定型接口
非运算:执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在 test 方法调用之前调。用 negate 方法,正如 and 和 or 方法一样
-
Consumer消费型接口
多个consumer的情况
-
Supplier供给型接口
-
自定义函数式接口案例
package com.skweb.qt.filter;
/**
* @Author Eric
* @params 不能为空的参数
* */
@FunctionalInterface
public interface MyPredicate {
boolean predicateParam(Object... o);
}
package com.skweb.qt.filter;
/*
* @Author Eric
* */
public class PredicateParams implements MyPredicate {
@Override
public boolean predicateParam(Object... os) {
for (Object o:os){
if(null==o){
return true;
}
}
return false;
}
}
7、方法引用简化
- 符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
- 应用场景 : 如果Lambda所要实现的方案 , 已经有其他方法存在相同方案,那么则可以使用方法引用。
- 方法引用在JDK 8中使用方式相当灵活,有以下几种形式:
- instanceName::methodName 对象::方法名
- ClassName::staticMethodName 类名::静态方法
- ClassName::methodName 类名::普通方法
- ClassName::new 类名::new 调用的构造器
- TypeName[]::new String[]::new 调用数组的构造器
- 方法引用的注意事项:
- 被引用的方法,参数要和接口中抽象方法的参数一样
- 当接口抽象方法有返回值时,被引用的方法也必须有返回值
8、Stream流式
1、串行Stream流
(1)获取穿行Stream流的两种方法
方式1 : 根据Collection获取流;首先, java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。
(2)常用方法
补充常用方法具体使用:
//公用部分代码
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
- Stream流的forEach方法,forEach 用来遍历流中的数据,
one.stream().forEach(System.out::println);
- Stream流的count方法,Stream流提供 count 方法来统计其中的元素个数,
System.out.println(one.stream().count());
- Stream流的filter方法,filter用于过滤数据,返回符合过滤条件的数据,
one.stream().filter(s -> s.length() == 2).forEach(System.out::println);
- Stream流的limit方法,limit 方法可以对流进行截取,只取用前n个。
one.stream().limit(3).forEach(System.out::println);
- Stream流的skip方法,如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:
one.stream().skip(2).forEach(System.out::println);
- Stream流的map方法,如果需要将流中的元素映射到另一个流中,可以使用 map 方法(将字符串类型转换成为了int类型,并自动装箱为 Integer 类对象):
@Test public void testMap() { Stream<String> original = Stream.of("11", "22", "33");
Stream<Integer> result =
original.map(Integer::parseInt); result.forEach(s -> System.out.println(s + 10)); }
- Stream流的sorted方法,如果需要将数据排序,可以使用 sorted 方法
@Test public void testSorted() {
// sorted(): 根据元素的自然顺序排序
// sorted(Comparator<? super T> comparator): 根据比较器指定的规则排序
Stream.of(33, 22, 11, 55) .sorted() .sorted((o1, o2) -> o2 - o1) .forEach(System.out::println); }
- Stream流的distinct方法,如果需要去除重复数据,可以使用 distinct 方法。
@Test
public void testDistinct() {
Stream.of(22, 33, 22, 11, 33) .distinct().forEach(System.out::println);
}
- Stream流的match方法,如果需要判断数据是否匹配指定的条件,可以使用 Match 相关方法
@Test public void testMatch() {
boolean b = Stream.of(5, 3, 6, 1)
// .allMatch(e -> e > 0);// allMatch: 元素是否全部满足条件
// .anyMatch(e -> e > 5); // anyMatch: 元素是否任意有一个满足条件
.noneMatch(e -> e < 0); // noneMatch: 元素是否全部不满足条件
System.out.println("b = " + b); }
- Stream流的find方法,如果需要找到某些数据,可以使用 find 相关方法
@Test public void testFind() {
Optional<Integer> first = Stream.of(5, 3, 6, 1).findFirst();
System.out.println("first = " + first.get());
Optional<Integer> any = Stream.of(5, 3, 6, 1).findAny();
System.out.println("any = " + any.get()); }
- Stream流的max和min方法,如果需要获取最大和最小值,可以使用 max 和 min 方法
@Test public void testMax_Min() {
Optional<Integer> max = Stream.of(5, 3, 6, 1).max((o1, o2) -> o1 - o2);
System.out.println("first = " + max.get());
Optional<Integer> min = Stream.of(5, 3, 6, 1).min((o1, o2) -> o1 - o2);
System.out.println("any = " + min.get()); }
- Stream流的reduce方法,如果需要将所有数据归纳得到一个数据,可以使用 reduce 方法
- Stream流的mapToInt,如果需要将Stream中的Integer类型数据转成int类型,可以使用 mapToInt 方法。
14.Stream流的concat方法,如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :
@Test
public void testContact() {
Stream<String> streamA = Stream.of("张三");
Stream<String> streamB = Stream.of("李四");
Stream<String> result = Stream.concat(streamA, streamB);
result.forEach(System.out::println); }
Stream注意事项(重要)
- Stream只能操作一次
- Stream方法返回的是新的流
- Stream不调用终结方法,中间的操作不会执行
(3)计算案例
案例一:
import java.util.Arrays;
import java.util.List;
public class StreamDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
User u1 = new User(1, "a", 1);
User u2 = new User(2, "b", 2);
User u3 = new User(3, "c", 3);
User u4 = new User(4, "d", 4);
User u5 = new User(5, "e", 5);
List<User> asList = Arrays.asList(u1, u2, u3, u4, u5);
asList.stream().filter((n) -> {
return n.getId() % 2 == 0;
}).filter((n) -> {
return n.getAge() > 3;
}).map((n) -> {
return n.getName().toUpperCase();
}).sorted((n1, n2) -> {
return n1.compareTo(n2);
}).limit(1).forEach(System.out::printf);
}
List<String> collect = asList.stream().filter((n) -> {
return n.getId() % 2 == 0;
}).filter((n) -> {
return n.getAge() > 3;
}).filter((n) -> {
return n != null;
}).map((n) -> {
return n.getName().toUpperCase();
}).sorted((n1, n2) -> {
return n1.compareTo(n2);
}).collect(Collectors.toList());
}
// 拼接
@Test
public void testJoining() {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 77));
String collect = studentStream .map(Student::getName) .collect(Collectors.joining(">_<", "^_^", "^v^"));
System.out.println(collect); }
//累加案例:假设你有一个名为Person的类,其中有一个整型的age属性:
public class Person {
private String name;
private int age;
// getters and setters...
}
//你可以通过以下方式使用Stream API来统计一个List<Person>中所有age属性的总和:
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 35)
);
int sum = people.stream()
.mapToInt(Person::getAge)
.sum();
System.out.println("Total age: " + sum);
}
}
//在这个例子中,stream()方法将列表转换为Stream,mapToInt(Person::getAge)将每个Person对象转换为其年龄,
//然后sum()方法将所有年龄相加得到总和。
总结:收集Stream流中的结果有以下几种方式
- 到集合中: Collectors.toList()/Collectors.toSet()/Collectors.toCollection()
- 到数组中: toArray()/toArray(int[]::new)
- 聚合计算:
Collectors.maxBy //最大值
Collectors.minBy //最小值
Collectors.counting //数量
Collectors.summingInt //求和
Collectors.averagingInt //平均值 - 分组: Collectors.groupingBy
- 分区: Collectors.partitionBy
- 拼接: Collectors.joinging
- 去重:.stream().distinct()
补充一个项目中实际用到的案例代码,仅供参考(包含了通过stream流进行分组、自定义排序、收集流结果等操作):
import com.waterlog.system.common.ResponseEnum;
import com.waterlog.system.common.ResponseResult;
import com.waterlog.system.service.WaterLogService;
import com.waterlog.system.vo.WaterLogVO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.waterlog.system.utils.ReadTxtUtil.resolveCharset;
@Service
public class WaterLogServiceImpl implements WaterLogService {
@Value("${waterLog.appPath}")
public String WATERLOG_APP_PATH;
@Value("${waterLog.resultPath}")
public String WATERLOG_RESULT_PATH;
@Override
public ResponseResult getWaterLogModelDatas() throws Exception {
File[] resultList = new File(WATERLOG_RESULT_PATH).listFiles();
if (resultList == null || resultList.length == 0) {
return new ResponseResult(ResponseEnum.FILENOTFOUND.getCode(), ResponseEnum.FILENOTFOUND.getMessage());
}
ArrayList<WaterLogVO> WaterLogVOList = new ArrayList<WaterLogVO>();
for (File file : resultList) {
if (file.getName().equals("日志.txt")) {
continue;
}
String filePath = file.getPath();
InputStream inputStream = new FileInputStream(filePath);
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, resolveCharset(filePath));
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String text;
WaterLogVO waterLogVO = new WaterLogVO();
Integer q = 0;
Integer h = 0;
Integer v = 0;
Integer temp = 2;
ArrayList<WaterLogVO.WaterLogData> waterLogDataList = new ArrayList<>();
while ((text = bufferedReader.readLine()) != null) {
if (text.contains("结果")) {
waterLogVO.setName(text);
} else {
String[] titleArr = text.split("\\s+");
Stream<String> titleArrStream = Stream.of(titleArr);
boolean isTitle = titleArrStream.anyMatch(item -> item.contains("时间"));
titleArrStream = Stream.of(titleArr);
if (isTitle) {
q = titleArrStream.anyMatch(item -> item.contains("q")) ? ++temp : q;
titleArrStream = Stream.of(titleArr);
h = titleArrStream.anyMatch(item -> item.contains("h")) ? ++temp : h;
titleArrStream = Stream.of(titleArr);
v = titleArrStream.anyMatch(item -> item.contains("v")) ? ++temp : v;
} else {
WaterLogVO.WaterLogData waterLogData = new WaterLogVO.WaterLogData();
waterLogData.setTime(titleArr[0] + " " + titleArr[1]);
waterLogData.setId(titleArr[2]);
if (q > 0) {
waterLogData.setQ(new BigDecimal(titleArr[q]));
}
if (h > 0) {
waterLogData.setH(new BigDecimal(titleArr[h]));
}
if (v > 0) {
waterLogData.setV(new BigDecimal(titleArr[v]));
}
waterLogDataList.add(waterLogData);
}
}
}
Map<String, List<WaterLogVO.WaterLogData>> map =
waterLogDataList.stream().collect(Collectors.groupingBy(WaterLogVO.WaterLogData::getTime));
List<List<WaterLogVO.WaterLogData>> collect = map.entrySet().stream().sorted((o1, o2) -> {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // 将日期时间格式化为字符串
LocalDateTime time1 = LocalDateTime.parse(o1.getKey(), formatter);
LocalDateTime time2 = LocalDateTime.parse(o2.getKey(), formatter);
return time1.compareTo(time2);
}).map(o -> o.getValue()).collect(Collectors.toList());
waterLogVO.setData(collect);
WaterLogVOList.add(waterLogVO);
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
}
return new ResponseResult(ResponseEnum.SUCCESS.getCode(), WaterLogVOList, ResponseEnum.SUCCESS.getMessage());
}
去重案例:
1、如果是基本类型,直接stream().distinct(),如果是对象,首先要重写equals和hashCode
2、使用
ctkjDevicesDatas.stream().distinct().forEach(n -> {
n.setUpdateTime(new Date());
ctkjDevicesDatasMapper.insert(n);
});
}
2、并行Stream流
(1)获取并行流的两种方式
- 直接获取并行的流
- 将串行流转成并行流
@Test
public void testgetParallelStream() {
ArrayList<Integer> list = new ArrayList<>();
// 直接获取并行的流 //
Stream<Integer> stream = list.parallelStream();
// 将串行流转成并行流
Stream<Integer> stream = list.stream().parallel(); }
(2)并行stream流带来的线程不安全问题
问题描述:Stream并行处理的过程会分而治之,也就是将一个大任务切分成多个小任务,这表示每个任务都是一个线程操作
解决方法: 加锁、使用线程安全的集合或者调用Stream的 toArray() / collect() 操作就是满足线程安全的了
方案一,同步代码块
方案二,使用安全集合类
方案三,使用stream内置方法转为安全的流
(3)parallelStream背后的技术
8、ForkJoin
必须在大数据量时使用
特点:工作窃取
说明:如上图,双端队列里有多个子任务,当B线程比A线程先执行完后,B会窃取A的任务帮忙执行,从而提高效率
ForkJoin案例:求和计算的任务(当求和数量过于庞大的时候,需要把任务拆分执行,最后合并)
业务代码:
测试代码:
9、Optional类
1、基本使用
2、高级使用
小结:Optional是一个可以为null的容器对象。orElse,ifPresent,ifPresentOrElse,map等方法避免对null的判断,写出更加优雅的代码。
9、时间API
1、基本使用
import java.time.LocalDate;
LocalDate的使用1
LocalDate的使用2
尽量减少旧版(utils包、sql包)时间api的使用!!!能用java8尽量使用这个,旧版日期时间 API 存在的问题
- 设计很差: 在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包
含日期。此外用于格式化和解析的类在java.text包中定义。 - 非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
- 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和
java.util.TimeZone类,但他们同样存在上述所有的问题。
LocalDate 、LocalTime 、LocalDateTime 案例介绍
// LocalDate:获取日期时间的信息。格式为 2019-10-16
@Test
public void test01() {
//创建指定日期
LocalDate fj = LocalDate.of(1985, 9, 23);
System.out.println("fj = " + fj); // 1985-09-23
//得到当前日期
LocalDate nowDate = LocalDate.now();
System.out.println("nowDate = " + nowDate); // 2019-10-16
//获取日期信息
System.out.println("年: " + nowDate.getYear());
System.out.println("月: " + nowDate.getMonthValue());
System.out.println("日: " + nowDate.getDayOfMonth());
System.out.println("星期: " + nowDate.getDayOfWeek());
}// LocalTime类: 获取时间信息。格式为 16:38:54.158549300
@Test
public void test02() {
// 得到指定的时间
LocalTime time = LocalTime.of(12, 15, 28, 129_900_000);
System.out.println("time = " + time); // 得到当前时间
LocalTime nowTime = LocalTime.now();
System.out.println("nowTime = " + nowTime);
//获取时间信息
System.out.println("小时: " + nowTime.getHour());
System.out.println("分钟: " + nowTime.getMinute());
System.out.println("秒: " + nowTime.getSecond());
System.out.println("纳秒: " + nowTime.getNano()); }//获取纳秒
//LocalDateTime类: 获取日期时间信息。格式为 2018-09-06T15:33:56.750
@Test
public void test03() {
LocalDateTime fj = LocalDateTime.of(1985, 9, 23, 9, 10, 20);
} System.out.println("fj = " + fj); // 1985-09-23T09:10:20
// 得到当前日期时间
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now); // 2019-10-16T16:42:24.497896800
System.out.println(now.getYear());
System.out.println(now.getMonthValue());
System.out.println(now.getDayOfMonth());
System.out.println(now.getHour());
System.out.println(now.getMinute());
System.out.println(now.getSecond());
System.out.println(now.getNano());
}
对日期时间的修改,对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。
withAttribute方法会创建对象的一个副本,并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对
象,他们不会影响原来的对象。
// LocalDateTime类: 对日期时间的修改
@Test
public void test05() {
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now); // 修改日期时间
LocalDateTime setYear = now.withYear(2078);
System.out.println("修改年份: " + setYear);
System.out.println("now == setYear: " + (now == setYear));
System.out.println("修改月份: " + now.withMonth(6));
System.out.println("修改小时: " + now.withHour(9));
System.out.println("修改分钟: " + now.withMinute(11)); // 再当前对象的基础上加上或减去指定的时间
LocalDateTime localDateTime = now.plusDays(5);
System.out.println("5天后: " + localDateTime);
System.out.println("now == localDateTime: " + (now == localDateTime));
System.out.println("10年后: " + now.plusYears(10));
System.out.println("20月后: " + now.plusMonths(20));
System.out.println("20年前: " + now.minusYears(20));
System.out.println("5月前: " + now.minusMonths(5));
System.out.println("100天前: " + now.minusDays(100));
};
日期时间的比较
// 日期时间的比较
@Test
public void test06() {
// 在JDK8中,LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期,可直接进行比较。
LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2018, 8, 8);
System.out.println(now.isBefore(date)); // false
System.out.println(now.isAfter(date)); // true
}
2、时间格式化和解析
通过 java.time.format.DateTimeFormatter 类可以进行日期时间解析与格式化。
// 日期格式化
@Test
public void test04() {
// 得到当前日期时间
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // 将日期时间格式化为字符串
String format = now.format(formatter);
System.out.println("format = " + format); // 将字符串解析为日期时间
LocalDateTime parse = LocalDateTime.parse("1985-09-23 10:12:22", formatter);
System.out.println("parse = " + parse);
}
JDK 8的 Instant 类,Instant 时间戳/时间线,内部保存了从1970年1月1日 00:00:00以来的秒和纳秒。
// 时间戳
@Test
public void test07() {
Instant now = Instant.now();
System.out.println("当前时间戳 = " + now); // 获取从1970年1月1日 00:00:00的秒
System.out.println(now.getNano());
System.out.println(now.getEpochSecond());
System.out.println(now.toEpochMilli());
System.out.println(System.currentTimeMillis());
Instant instant = Instant.ofEpochSecond(5);
System.out.println(instant);
}
3、JDK 8的计算日期时间差类
Duration/Period类: 计算日期时间差。
- Duration:用于计算2个时间(LocalTime,时分秒)的距离
- Period:用于计算2个日期(LocalDate,年月日)的距离
// Duration/Period类: 计算日期时间差
@Test
public void test08() { // Duration计算时间的距离
LocalTime now = LocalTime.now();
LocalTime time = LocalTime.of(14, 15, 20);
Duration duration = Duration.between(time, now);
System.out.println("相差的天数:" + duration.toDays());
System.out.println("相差的小时数:" + duration.toHours());
System.out.println("相差的分钟数:" + duration.toMinutes());
System.out.println("相差的秒数:" + duration.toSeconds()); // Period计算日期的距离
LocalDate nowDate = LocalDate.now();
LocalDate date = LocalDate.of(1998, 8, 8); // 让后面的时间减去前面的时间
Period period = Period.between(date, nowDate);
System.out.println("相差的年:" + period.getYears());
System.out.println("相差的月:" + period.getMonths());
System.out.println("相差的天:" + period.getDays());
}
10、重复注解与类型注解
1、重复注解
自从Java 5中引入 注解 以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK 8中使用@Repeatable注解定义重复注解。
使用步骤:
- 定义重复的注解容器注解
@Retention(RetentionPolicy.RUNTIME)
@interface MyTests {
MyTest[] value();
}
- 定义一个可以重复的注解
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyTests.class)
@interface MyTest {
String value();
}
- 配置多个重复的注解
@MyTest("tbc")
@MyTest("tba")
@MyTest("tba")
public class Demo01 {
@MyTest("mbc")
@MyTest("mba")
public void test() throws NoSuchMethodException { }
}
- 解析得到指定注解
// 3.配置多个重复
@MyTest("tbc")
@MyTest("tba")
@MyTest("tba")
public class Demo01 {
@Test
@MyTest("mbc")
@MyTest("mba")
public void test() throws NoSuchMethodException {
// 4.解析得到类上的指定注解
MyTest[] tests = Demo01.class.getAnnotationsByType(MyTest.class);
for (MyTest test : tests) {
System.out.println(test.value()); }// 得到方法上的指定注解
Annotation[] tests1 = Demo01.class.getMethod("test").getAnnotationsByType(MyTest.class);
for (Annotation annotation : tests1) {
System.out.println("annotation = " + annotation);
}
}
}
2、类型注解
JDK 8为@Target元注解新增了两种类型: TYPE_PARAMETER、TYPE_USE 。
TYPE_PARAMETER :表示该注解能写在类型参数的声明语句中。 类型参数声明如: <T>
、
TYPE_USE :表示注解可以再任何用到类型的地方使用。
案例一:TYPE_PARAMETER的使用
@Target(ElementType.TYPE_PARAMETER)
@interface TyptParam {
}
public class Demo02<@TyptParam T> {
public static void main( String[] args) {
}
public <@TyptParam E> void test( String a) {
}
}
案例二:TYPE_USE的使用
@Target(ElementType.TYPE_USE)
@interface NotNull {
}
public class Demo02<@TyptParam T extends String> {
private @NotNull int a = 10;
public static void main(@NotNull String[] args) {
@NotNull int x = 1;
@NotNull String s = new
@NotNull String();
}
public <@TyptParam E> void test( String a) {
}
}
小结:通过@Repeatable元注解可以定义可重复注解, TYPE_PARAMETER 可以让注解放在泛型上, TYPE_USE 可以让注解放
在类型的前面
11、异步回调
案例一:无返回值的异步方法,runAsync()和主线程异步执行,互不影响
案例二:有返回值的异步方法supplyAsync(),分为正常返回和异常返回,注意下面方法执行最后有个.get()方法,这个就是Callable接口的get()方法,为了得到线程的返回值
关于案例二的补充说明,whenCompleteAsync()方法能感知到执行结果和异常,但无法处理。所以异常执行时处理异常的方法,使用下图的exceptionally(),代码如下:
同样的,在案例二种,whenCompleteAsync()方法能感知到结果,但处理return结果得使用handle()方法,如下图:
补充一:异步回调还有一些串行方法,对上一次的返回结果再次进行处理
各方法的区别
补充二:任务组合
组合一,两个任务都完成后,触发第三个任务
组合二,两个任务完成一个,就触发第三个任务
多任务组合:
12、JMM
其他相关知识点不想写了,参考我的另一篇博客 1、 volatile关键字的理解
补充单例模式案例,单例模式就一定安全吗?
不一定,因为java里的反射可以破坏单例模式,可使用枚举实现单例模式,来解决反射带来的不安全问题
饿汉式
懒汉式是线程不安全的,需要用到synchronized和volatile实现DCL(双重锁定检查(DCL,Double Check Lock))
使用枚举来解决反射带来的单例模式的破坏