title: Day28-Java8新特性
date: 2020-08-26 15:58:42
author: 子陌
Java8新特性
- 特点:
- 速度更快
- 代码更少(Lambda表达式)
- 强大的Stream API
- 便于并行
- 最大化减少空指针异常 Optical
其中最为核心的为Lambda表达式和Stream API
HashMap:Java8之前,使用哈希算法 + 数组,无法解决碰撞问题,Java8之后,数组 + 链表 + 红黑树,当链表长度超过8或者总个数大于64时,转换为红黑树
ConcurrentHashMap(CAS算法,无锁算法):数组 + 链表 + 红黑树
Lambad表达式
Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
public class TestLambda {
//原来的匿名内部类
@Test
public void test1(){
Comparator<Integer> com = new Comparator<Integer>() {
@override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
}
TreeSet<Integer> ts= new TreeSet<>(com);
}
//Lambda 表达式
@Test
public void test2(){
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
TreeSet<Integer> ts= new TreeSet<>(com);
}
}
- Lambda表达式的基础语法:Java8中引入一个新的操作符"->"该操作符成为箭头操作符或者lambda操作符
- 左侧:Lambda 表达式的参数列表(数据类型可以省略不写,JVM可以根据上下文自动推断,即“类型推断”)
- 右侧:Lambda 表达式中所需执行的功能,即Lambda体
- 语法格式:
- 无参无返回值:
() -> System.out.println("hello lambda!");
- 有参无返回值:
(x) -> System.out.println(x);
,若只有一个参数,左侧小括号也可以省略不写 - 有参有返回值:
(x,y) -> {System.out.println("aaa"); return Integer.compare(x,y);};
,弱只有一条语句,大括号和return都可省略不写
- 无参无返回值:
- Lambda表达式需要“函数式接口”的支持
- 函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。可以使用注解
@FunctionalInterface
修饰
- 函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。可以使用注解
函数式接口
Java 8中四大内置核心函数式接口
-
Consumer<T>
:消费型接口void accept(T t);
-
Supplier<T>
:供给型接口T get();
-
Function<T, R>
:函数型接口R apply(T t);
-
Predicate<T>
:断言型接口boolean test(T t);
public static void main(String args[])}{
// Consumer<T>:消费型接口
happy(10000, (m) -> System.out.println("去网吧打游戏,消费:" + m + "元"));
// Supplier<T>:供给型接口
List<Integer> list = getNumList(10, () -> (int)(Math.random() * 100));
for(Integer num : list){
System.out.println(num);
}
// Function<T, R>:函数型接口
String n_str = strHandler("\t\t\t hello zimo ", (str) -> str.trim());
System.out.println(n_str);
// Predicate<T>:断言型接口
List<String> list = Arrays.asList("hello","lambda","www","OK","asdHas");
List<String> newList = filterStr(list, (s) -> s.length() > 3);
}
// 消费型接口
public void happy(double money, Consume<Double> con){
con.accept(money);
}
// 供给型接口
public List<Integer> getNumList(int num, Supplier<Integer> sup){
List<Integer> list = new ArrayList<>();
for(int i = 0; i < num; i++){
Integer n = sup.get();
list.add(n);
}
return list;
}
// 函数型接口
public String strHandler(String str, Function<String,String> fun){
return fun.apply(str);
}
// 断言型接口
public List<String> filterStr(List<String> list, Predicate<String> pre){
List<String> strList = new ArrayList()<>;
for(String str : list){
if(pre.test(str)){
strList.add(str);
}
}
return strList;
}
方法引用与构造器引用
若Lambda体中的内容有方法已经实现了,我们可以使用“方法引用”(可以理解为方法引用是Lambda表达式的另一种表现形式)
方法引用
主要有三种格式:
对象::实例方法名
类::静态方法名
类::实例方法名
public class TestDemo{
// 对象::实例方法名
@Test
public void test1(){
Consumer<String> con = (x) -> System.out.println(x);
PrintStream ps = System.out;
Consumer<String> con1 = ps::println; // System.out::println;
con1.accept("asdfghjkl");
}
@Test
public void test2(){
Employee emp = new Employee();
Supplier<String> sup = () -> emp.getName();
String name = sup.get();
Supplier<String> sup1 = () -> emp::getAge;
Integer age = sup1.get();
}
// 类::静态方法名
@Test
public void test3(){
Comparator<Integer> com = (x, y) -> Integer.compare(x, y); // compare是static方法
Comparator<Integer> com = (x, y) -> Integer::compare;
}
// 类::静态方法名
@Test
public void test3(){
Comparator<Integer> com = (x, y) -> Integer.compare(x, y); // compare是static方法
Comparator<Integer> com = (x, y) -> Integer::compare;
}
// 类::实例方法名
@Test
public void test4(){
BiPredicate<String, String> bp = (x, y) -> x.equals(y);
BiPredicate<String, String> bp = String::equals; // 1.equals(2)
}
}
构造器引用
格式:ClassName::new
public class TestDemo{
// 构造器引用
@Test
public void test(){
Supplier<Employee> sup = () -> new Employee();
// 构造器引用
Supplier<Employee> sup2 = Employeee::new;
}
@Test
public void test1(){
Function<Integer, Employee> fun = (x) -> new Employee(x);
// 构造器引用
Function<Integer, Employee> fun1 = Employeee::new;
fun1.apply(10001);
}
}
数组引用
格式:Type::new
public class TestDemo{
// 数组引用
@Test
public void test(){
Function<Integer, String> fun = (x) -> new String[x];
String[] strs = fun.apply(10);
Function<Integer, String> fun1 = (x) -> String[]::new;
String[] strs = fun.apply(20);
}
}
Stream API
"集合讲的是数据,流讲的是计算!"是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
-
注意:
- Stream自己不会存储元素
- Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream
- Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
-
Stream操作的三个步骤:
-
创建Stream
一个数据源(如:集合、数组),获取一个流
-
中间操作
一个中间操作链,对数据源的数据进行处理
-
终止操作(终端操作)
一个终止操作,执行中间操作链,并产生结果
-
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则**中间操作不会执行任何的处理!**而在终止操作时一次性全部处理,称为“惰性求值”。
public class TestStreamAPI{
List<Employee> employees = Arrays.asList(
new Employee("张三", 18, 9999.99, Status.FREE),
new Employee("李四", 58, 5555.55, Status.BUSY),
new Employee("王五", 26, 3333.33, Status.VOCATION),
new Employee("赵六", 36, 6666.66, Status.FREE),
new Employee("田七", 12, 8888.88, Status.BUSY)
);
// 创建Stream
@Test
public void test1(){
// 1.可以通过Collection系列集合提供的stream()或parallelStream()
List<Stream> list = new ArrayList<>();
Stream<Stream> st = list.Stream();
// 2.通过Arrays中的静态方法stream()获取数组流
Employee[] em = new Employee[10];
Stream<Employee> st1 = Arrays.stream(em);
// 3.通过Stream类中的静态方法of()
Stream<String> st2 = Stream.of("aa","bb","cc");
// 4.创建无限流
// 迭代
Stream<Integer> sss = Stream.iterate(0, (x) -> x+2);
sss.limit(10).forEach(System.out::println);
// 生成
Stream.generate(()->Math.random())
.limit(5)
.forEach(System.out::println);
}
// 中间操作
/**
* 1.筛选与切片
* filter —— 接收Lambda,从流中排除某些元素
* limit —— 截断流,使其元素不超过给定数量
* skip(n) —— 跳过元素,返回一个扔掉了前n个元素
* distinct —— 筛选,通过流所生成元素的hashCode()和equals()去除重复元素
*/
@Test
public void test2(){
employees.stream()
.filter((e)->e.getAge > 35) // 中间操作
.limit(2)
.forEach(System.out::println); // 终止操作:一次性执行全部
}
/**
* 2.映射
* map —— 接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
* flatMap —— 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
*/
@Test
public void test3(){
List<String> list = Arrays.asList("aa","bb","cc","dd","eee");
list.stream()
.map((str)->str.toUpperCase())
.forEach(System.out::println);
employee.stream()
.map(Employee::getName)
.forEach(System.out::println);
}
@Test
public void test4(){
List<String> list = Arrays.asList("aa","bb","cc","dd","eee");
Stream<Stream<Character>> stream = list.stream()
.map(TestStreamAPI::filterCharacter);
stream.forEach((sm)->{
sm.forEach(System.out::println);
});
Stream<Character> stream1 = list.stream()
.flatMap(TestStreamAPI::filterCharacter);
stream1.forEach(System.out::println);
}
public static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for(Character ch : str.toCharArray()){
list.add(ch);
}
return list.stream();
}
/**
* 3.排序
* sorted() —— 自然排序
* sorted(Comparator com) —— 定制排序
*/
@Test
public void test5(){
List<String> list = Arrays.asList("aa","bb","cc","dd","eee");
list.stream()
.sorted()
.forEach(System.out::println);
employees.stream()
.sorted((e1,e2) -> {
if(e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
}else{
return e1.getAge().compareTo(e2.getAge());
}
})
.forEach(System.out::println);
}
/**
* 4.查找与匹配
* allMatch —— 检查是否匹配所有元素
* anyMatch —— 检查是否至少匹配一个元素
* noneMatch —— 检查是否没有匹配所有元素
* findFirst —— 返回第一个元素
* findAny —— 返回当前流中的任意元素
* count —— 返回流中元素的总个数
* max —— 返回流中最大值
* min —— 返回流中最小值
*/
@Test
public void test6(){
employees.stream()
.allMatch((e)->e.getStatus().equals(Ststus.BUSY)); // 匹配所有BUSY false
.anyMatch((e)->e.getStatus().equals(Ststus.BUSY)); // 匹配至少一个BUSY true
.noneMatch((e)->e.getStatus().equals(Ststus.BUSY)); // 没有匹配BUSY的元素 false
.sorted((e1,e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.findFirst(); // 返回一个Optional<Employee>容器 optional.get()
}
/**
* 5.归约
* reduce(T identity, BinaryOperator) / reduce(BinaryOperator) —— 可以将流中元素反复结合起来,得到一个值
*/
@Test
public void test6(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream()
.reduce(0, (x,y)->x+y);
System.out.println(sum); // 55
Optional<Double> op = employees.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(op.get());
}
/**
* 5.收集
* collect —— 将流转换为其他形式。接受一个Collector接口的实现,用于给Stream中元素做汇总的方法
*/
@Test
public void test7(){
List<String> list = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
list.forEach(System.out::println);
}
}
并行流和顺序流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
Java 8中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API可以声明性地通过parallel()与sequential()在并行流与顺序流之间进行切换。
了解Fork/Join框架
Fork/Join框架: 就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行join 汇总。
Fork/Join框架与传统线程池的区别
采用"工作窃取"模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中输一个并把它放在自己的队列中。
相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行。这种方式减少了线程的等待时间,提高了性能。
public class ForkJoinCalculate extends RecursiveTask<Long>{
private static final long serialVersionUID = 134656970987L;
private long start;
private long end;
private static final long THRESHOLD = 10000;
public ForkJoinCalculate(long start, long end){
this.start = start;
this.end = end;
}
@Override
protected Long compute(){
long length = end - start;
if(length <= THRESHOLD){
long = sum = 0;
for(long i = start; i <= end; i++){
sum += i;
}
return sum;
}else{
long middle = (start + end) / 2;
ForkJoinCalculate left = new ForkJoinCalculate(start, middle);
left.fork(); // 拆分子任务,同时压入线程队列
ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end);
left.fork(); // 拆分子任务,同时压入线程队列
return left.join() + right.join();
}
}
public static void main(String args[]){
long start = System.currentTimeMillis();
Instant start1 = Instant.now(); // java 8 新特性
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinCalculate(0, 100000000L);
long sum = pool.invoke(task);
System.out.println(sum);
long end = System.currentTimeMillis();
Instant end1 = Instant.now(); // java 8 新特性
System.out.println(end - start);
System.out.println(Duration.between(start, end).getNano); // 获取纳秒值 toMillis()获取毫秒
}
}
Java 8 并行流
public class Demo{
public static void main(String args[]){
LongStream.rangeClosed(0, 1000000000000L)
.parallel() // 切换为并行流
.reduce(0, Long::sum);
}
}
Optional类
Optional<T> 类 (java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。
- 常用方法:
Optional.of(T t)
:创建一个Optional实例Optional.empty()
:创建一个空的Optional实例Optional.ofNullable(T t)
:若 t 不为 null ,创建Optional实例,否则创建空实例isPresent()
:判断是否包含值orElse(T t)
:如果调用对象包含值,返回该值,否则返回 torElseGet(Supplier s)
:如果调用对象包含值,返回该值,否则返回s获取的值map(Function f)
:如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
flatMap(Function mapper)
:与map类似,要求返回值必须是Optional
public class Demo{
public static void main(String args[]){
// of不能构建空实例
Optional<Player> op = Optional.of(new Player("zhagnsan","nan", 20));
Player player = op.get();
// empty构建空实例
Optional<Player> op1 = Optional.empty();
// 既可以构建空实例也可以构建非空实例
Optional<Player> op2 = Optional.ofNullable(new Player());
Optional<Player> op3 = Optional.ofNullable(null);
// 判断是否有值
if(op.isPresent()){
// 有值
Player player = op.get();
}
// 如果有值返回旧的,没有值就返回新构建的
Player player = op.orElse(new Player("lisi","nv", 18));
// 供给型接口
Player player = op.orElseGet(() -> new Player());
Optional<String> str1 = op.map((e)-> e.getName());
Optional<String> str2 = op.flatMap((e)-> Optional.of(e.getName());
}
}
接口中的默认方法与静态方法
接口默认方法的“类优先”原则
若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时
- 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
- 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突
public interface MyFun{
default String getName(){
return "hahaha";
}
}
public interface MyInterface {
default String getName(){
return "hehehe";
}
public static void show(){
System.out.println("接口中的静态方法");
}
}
public class MyClass {
public String getName(){
return "xixixi";
}
}
public class SubClass extends MyClass implements MyFun{
}
public class SubClass1 implements MyFun, MyInterface{
@Override
public String getName(){
return MyFun.super.getName();
}
}
public class TestDemo{
public static void main(String args[]){
SubClass sc = new SubClass();
sc.getName();
MyInterface.show();
}
}
- Java 8 允许接口的默认实现,并且支持接口中静态方法的实现
新时间日期API
-
日期:1.0 Date >>> 1.1 Calendar
-
时间:TimeZone
-
格式化日期时间:SimpleDateFormat(java.text)
他们都不是线程安全的
Java 8 新特性
线程安全的新时间日期
- java.time:针对时间和日期操作(ISO-8601标准)
- java.time.chrono:特殊日期格式,如日本、台湾等
- java.time.format:时间格式化
- java.time.temporal:时间交流器,对时间做运算
- java.time.zone:时区交流器
传统的方式,存在多线程安全问题:
import java.lang.reflect.Array;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TestSimpleDateFormat {
public static void main(String[] args) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
Callable<Date> task = new Callable<>() {
@Override
public Date call() throws Exception {
return sdf.parse("20161218" );
}
};
ExecutorService pool = Executors.newFixedThreadPool(10);
ArrayList<Future<Date>> results = new ArrayList<>();
for (int i = 0; i < 10; i++){
results.add(pool.submit(task));
}
for (Future<Date> future : results) {
System.out.println(future.get());
}
pool.shutdown();
}
}
传统改进版:使用锁才能能避免
import java.text.DateFormat;
import java.text.ParseException;
import java.text.simpleDateFormat;
import java.util.Date;
public class DateFormatThreadLocal {
private static firal ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){
protected DateFormat initialValue(){
return new SimpleDateFormat("yyyyMMdd");
}
};
public static Date convert(String source) throws ParseException{
return df.get().parse(source);
}
}
------------------------------------------------------------------------------------------------
import java.lang.reflect.Array;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TestSimpleDateFormat {
public static void main(String[] args) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
Callable<Date> task = new Callable<>() {
@Override
public Date call() throws Exception {
return DateFormatThreadLocal.convert("20161218" );
}
};
ExecutorService pool = Executors.newFixedThreadPool(10);
ArrayList<Future<Date>> results = new ArrayList<>();
for (int i = 0; i < 10; i++){
results.add(pool.submit(task));
}
for (Future<Date> future : results) {
System.out.println(future.get());
}
pool.shutdown();
}
}
Java 8 新特性,多线程安全日期
import java.lang.reflect.Array;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TestSimpleDateFormat {
public static void main(String[] args) throws Exception {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");
Callable<LocalDate> task = new Callable<>() {
@Override
public LocalDate call() throws Exception {
return LocalDate.parse("20161218",dtf);
}
};
ExecutorService pool = Executors.newFixedThreadPool(10);
ArrayList<Future<LocalDate>> results = new ArrayList<>();
for (int i = 0; i < 10; i++){
results.add(pool.submit(task));
}
for (Future<LocalDate> future : results) {
System.out.println(future.get());
}
pool.shutdown();
}
}
使用LocalDate、LocalTime、LocalDateTime
- LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。
注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法
-
LocalDateTime:时间和日期
LocalDateTime l = LocalDateTime.now() // 2020-09-27T10:50:32.891669
LocalDateTime.of(2015, 10, 19, 13, 22, 33) // 2015-10-19T13:22:33
LocalDateTime l1 = l.plusYears(2) // 2022-09-27T10:50:32.891669
LocalDateTime l2 = l1.minusMonths(2) // 2022-07-27T10:50:32.891669
-
Instant:时间戳(以Unix 元年:1970年1月1日 00:00:00到某个时间之间的毫秒值)
获取当前时间:
Instant.now().atOffset(ZoneOffset.ofHours(8)) // 默认获取UTC时区
获取时间戳:
Instant.now().atOffset(ZoneOffset.ofHours(8)).toEpochMilli()
-
Duration:计算两个时间之间的间隔
Duration.between(instant1, instant2).toMillis()
Duration.between(loaclTime1, loaclTime2).toMillis()
-
Preiod:计算两个日期之间的间隔
Preiod.between(localDate1, localDate2)
日期的操纵
-
TemporalAdiuster:时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。
-
TemporalAdjusters:该类通过静态方法提供了大量的常用TemporalAdjuster的实现。
例如获取下个周日:
LocalDate nextSunday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
时区的处理
-
Java8中加入了对时区的支持,带时区的时间为分别为:
ZonedDate、ZonedTime、ZonedDateTime其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式例如:Asia/Shanghai等
Zoneld:该类中包含了所有的时区信息
- getAvailableZonelds():可以获取所有时区时区信息
- of(id):用指定的时区信息获取Zoneld对象
其他新特性
重复注解与类型注解
Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。
Java JUC线程高级
-
volatile关键字 - 内存可见性
当多个线程进行操作共享数据时,可以保证内存中的数据可见性(最新的),相较于synchronized是一种较为轻量级的同步策略
- 注意:
- volatile不具备“互斥性”
- volatile不保证变量的“原子性”
- 注意:
-
原子变量 - CAS算法
-
i++的原子性问题
int i = 10; i = i++; // i = 10
i++的操作实际上分为三个步骤“读 - 改 - 写”:
int tmp = i; i = i + 1; i = tmp;
-
jdk1.5后java.util.concurrent.atomic 包下提供了常用的原子变量:
-
volatile 保证内存可见性
-
CAS(Compare - And - Swap) 算法保证数据的原子性
-
CAS算法是硬件对于并发操作共享数据的支持,是一种无锁的非阻塞算法
-
CAS包含了三个操作数:
- 内存值V
- 预估值A
- 更新值B
当且仅当V == A时, V = B,否则,什么也不做
-
-
-
模拟CAS算法
/** * 模拟CAS算法 * @author Liu_zimo * @version v0.9a by 2020-09-28 12:06:10 */ public class CompareAndSwap { private int value; // 获取内存值 public synchronized int get(){ return value; } // 比较 public synchronized int compareAndSwap(int expectedValue, int newValue){ int oldValue = value; if (oldValue == expectedValue){ this.value = newValue; } return oldValue; } // 设置 public synchronized boolean compareAndSet(int expectedValue, int newValue){ return expectedValue == compareAndSwap(expectedValue, newValue); } public static void main(String[] args) { final CompareAndSwap cas = new CompareAndSwap(); for (int i = 0; i < 10; i++) { new Thread(new Runnable(){ @Override public void run() { int expectedValue = cas.get(); boolean b = cas.compareAndSet(expectedValue, (int) (Math.random() * 101)); System.out.println(b); } }).start(); } } }
-
-
ConcurrentHashMap锁分段机制
-
Java 5.0在java.util.concurrent包中提供了多种并发容器类来改进同步容器的性能。
-
ConcurrentHashMap同步容器类是Java 5增加的一个线程安全的哈希表。对与多线程的操作,介于HashMap与Hashtable之间。内部采用“锁分段”机制替代Hashtable的独占锁。进而提高性能。
-
此包还提供了设计用于多线程上下文中的Collection实现:
ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList和CopyOnWriteArraySet。当期望许多线程访问一个给定collection时,ConcurrentHashMap通常优于同步的HashMap,ConcurrentSkipListMap通常优于同步的TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList优于同步的ArrayList.
CopyOnWriteArrayList/CopyOnWriteArraySet:写入并复制
注意:添加操作多时,效率低,因为每次添加时都会进行复制,开销非常的大。并发迭代操作多时可以选择
-
-
CountDownLatch闭锁
- Java 5.0在java.util.concurrent包中提供了多种并发容器类来改进同步容器的性能。
- CountDownLatch一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
- 闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行:
- 确保某个计算在其需要的所有资源都被初始化之后才继续执行
- 确保某个服务在其依赖的所有其他服务都已经启动之后才启动
- 等待直到某个操作所有参与者都准备就绪再继续执行
import java.time.Instant; import java.time.ZoneOffset; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * CountDownLathc闭锁:在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才继续执行 * * @author Liu_zimo * @version v0.9a by 2020-09-29 10:48:54 */ public class CountDownLatchDemo { public static void main(String[] args) { final CountDownLatch latch = new CountDownLatch(10); LatchDemo latchDemo = new LatchDemo(latch); long start = Instant.now().plusMillis(TimeUnit.HOURS.toMillis(8)).toEpochMilli(); for (int i = 0; i < 10; i++) { new Thread(latchDemo).start(); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } long end = Instant.now().plusMillis(TimeUnit.HOURS.toMillis(8)).toEpochMilli(); System.out.println("耗时为:" + (end - start)); } } class LatchDemo implements Runnable { private CountDownLatch latch; public LatchDemo(CountDownLatch latch) { this.latch = latch; } @Override public void run() { synchronized (this){ try { for (int i = 0; i < 50000; i++) { if (i % 2 == 0) { System.out.println(i); } } }finally { latch.countDown(); } } } }
-
实现Callable接口
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; /** * 1.创建执行线程的方式三:实现Callable接口,相对于Runnable接口方式,方法多了返回值,并且可以抛出异常 * 2.执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果。FutureTask是Future接口的实现类 * * @author Liu_zimo * @version v0.9a by 2020-09-28 11:27:10 */ public class CallableDemo { public static void main(String[] args) { ThreadDemo2 th = new ThreadDemo2(); // 执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果 FutureTask<Integer> result = new FutureTask<>(th); new Thread(result).start(); // 接收结果,等线程执行完才能get,FutureTask也可以用于闭锁 try { System.out.println(result.get()); } catch (ExecutionException | InterruptedException e) { e.printStackTrace(); } } } // Callable实现方式 class ThreadDemo2 implements Callable<Integer>{ @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i <= 100; i++) { sum += i; } return sum; } } // Runnable实现方式 class ThreadDemo1 implements Runnable{ @Override public void run() { } }
-
Lock同步锁
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 用于解决多线程安全问题的方式: * 1.同步代码块synchronized:隐士锁 * 2.同步方法synchronized * jdk1.5后: * 3.同步锁Lock:显示锁,需要通过lock()方法上锁,必须通过unlock()方法进行释放锁 * * @author Liu_zimo * @version v0.9a by 2020-09-29 10:48:54 */ public class LockDemo { public static void main(String[] args) { Ticket ticket = new Ticket(); new Thread(ticket, "1号窗口").start(); new Thread(ticket, "2号窗口").start(); new Thread(ticket, "3号窗口").start(); } } class Ticket implements Runnable { private int tick = 100; private Lock lock = new ReentrantLock(); @Override public void run() { try { lock.lock(); while (tick > 0) { System.out.println(Thread.currentThread().getName() + "正在出售:" + tick + ",余票为" + --tick); } } finally { lock.unlock(); } } }
-
Condition控制线程通信
- Condition 接口描述了可能公与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监听器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
- 在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是 await、signal 和 signalAll。
Condition实例,请使用其newCondition()方法。 - Condition实例实质上被绑定到一个锁上。要为特定Lock实例获得 Condition 实例,请使用其 newCondition() 方法。
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Lock实现生产者消费者模式:等待/唤醒 * * @author Liu_zimo * @version v0.9a by 2020-09-29 11:56:42 */ public class LockPreduceAndConsumeDemo { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor productor = new Productor(clerk); Consumer consumer = new Consumer(clerk); new Thread(productor, "生产者A").start(); new Thread(consumer, "消费者B").start(); new Thread(productor, "生产者AA").start(); new Thread(consumer, "消费者BB").start(); } } // 店铺 class Clerk{ private int product = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); // 进货 public /*synchronized*/ void get(){ lock.lock(); try { while (product >= 10){ // 避免虚假唤醒,应该总是使用在循环中 System.out.println("产品已满,无法添加"); try { condition.await(); // this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ":" + ++product); condition.signalAll(); // this.notifyAll(); }finally { lock.unlock(); } } // 卖货 public /*synchronized*/ void sale(){ lock.lock(); try { while (product <= 0){ System.out.println("货物不足,无法出售"); try { condition.await();// this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ":" + --product); condition.signalAll();// this.notifyAll(); }finally { lock.unlock(); } } } // 生产者 class Productor implements Runnable{ private Clerk clerk; public Productor(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 20; i++) { clerk.get(); } } } // 消费者 class Consumer implements Runnable{ private Clerk clerk; public Consumer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 20; i++) { clerk.sale(); } } }
-
线程八锁
题目:判断打印的【one】还是【two】
- 两个普通同步方法,两个线程,标准打印:one、two
- 新增Thread.sleep()方法给getOne,打印:one、two
- 新增普通方法getThree(),打印:three、one、two
- 两个普通方法,两个Number对象,打印:two、one
- 修改getOne()为静态同步方法,打印:two、one
- 修改两个方法均为静态同步方法,一个Number对象,打印:one、two
- 一个静态同步方法,一个非静态同步方法,两个Number对象,打印:two、one
- 两个静态同步方法,两个Number对象,打印:one、two
线程八锁的关键:
- 非静态方法的锁默认为this,静态方法的锁为对应的大Class实例
- 某一个时刻内,只能有一个线程持有锁,无论几个方法
/** * 线程八锁 * * @author Liu_zimo * @version v0.9a by 2020-10-09 11:42:31 */ public class ThreadEightLock { public static void main(String[] args) { Number number = new Number(); Number number2 = new Number(); new Thread(new Runnable() { @Override public void run() { number.getOne(); // number.getOne1(); } }).start(); new Thread(new Runnable() { @Override public void run() { number2.getTwo(); } }).start(); new Thread(new Runnable() { @Override public void run() { number.getTwo(); } }).start(); new Thread(new Runnable() { @Override public void run() { number.getThree(); } }).start(); } } class Number{ public static synchronized void getOne(){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("one"); } public static synchronized void getOne1(){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("one"); } public static synchronized void getTwo(){ System.out.println("two"); } public void getThree(){ System.out.println("Three"); } }
-
线程按序交替
编写一个程序,开启3个线程,这三个线程的ID分别为A、B、c,每个线程将自己的ID在屏幕上打印10遍,要求输出的结果必须按顺序显示。
- 如:ABCABCABC…依次递归
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 线程按序交替打印 * * @author Liu_zimo * @version v0.9a by 2020-09-29 14:57:14 */ public class ThreadOrderPrintDemo { public static void main(String[] args) { /* * 编写一个程序,开启三个线程,这三个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出的结果必须按顺序显示。 * 如:ABCABCABC...依次递归 */ AlternateDemo d = new AlternateDemo(); new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 20; i++) { d.loopA(i); } } }, "A").start(); new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 20; i++) { d.loopB(i); } } }, "B").start(); new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 20; i++) { d.loopC(i); } } }, "C").start(); } } class AlternateDemo{ private int number = 1; // 当前正在执行线程的标记 private Lock lock = new ReentrantLock(); private Condition conditionA = lock.newCondition(); private Condition conditionB = lock.newCondition(); private Condition conditionC = lock.newCondition(); public void loopA(int totalLoop){ lock.lock(); try { // 1.判断 if (number != 1){ conditionA.await(); } // 2.打印 for (int i = 0; i < 1; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop); } // 3.唤醒 number = 2; conditionB.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void loopB(int totalLoop){ lock.lock(); try { // 1.判断 if (number != 2){ conditionB.await(); } // 2.打印 for (int i = 0; i < 1; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop); } // 3.唤醒 number = 3; conditionC.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void loopC(int totalLoop){ lock.lock(); try { // 1.判断 if (number != 3){ conditionC.await(); } // 2.打印 for (int i = 0; i < 1; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop); } // 3.唤醒 number = 1; conditionA.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
-
ReadWriteLock读写锁
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * ReadWriteLock读写锁 * 写写/读写 需要互斥操作 * 读读 不需要互斥操作 * @author Liu_zimo * @version v0.9a by 2020-10-10 11:42:31 */ public class ReadWriteLockDemo { private int number = 0; private ReadWriteLock lock = new ReentrantReadWriteLock(); // 读 public void getNumber() { lock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + ":" + number); }finally { lock.readLock().unlock(); } } // 写 public void setNumber(int number) { lock.writeLock().lock(); try { this.number = number; System.out.println(Thread.currentThread().getName()); }finally { lock.writeLock().unlock(); } } public static void main(String[] args) { ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo(); new Thread(new Runnable() { @Override public void run() { readWriteLockDemo.setNumber((int)(Math.random() * 101)); } }, "write").start(); for (int i = 0; i < 100; i++) { new Thread(new Runnable() { @Override public void run() { readWriteLockDemo.getNumber(); } },"read").start(); } } }
-
线程池
import java.util.Random; import java.util.concurrent.*; /** * 线程池 * 1.线程池提供了一个线程队列,队列中保存着所有等待状态的线程。避免创建与销毁的额外性能开销,提高了响应速度 * 2.线程池的体系结构: * - java.util.concurrent.Executor:负责线程的使用与调度的根接口 * |-- ExecutorService子接口:线程池的主要接口 * |--ThreadPoolExecutor:实现类 * |--ScheduledExecutorService子接口:负责线程的调度 * |--ScheduledThreadPoolExecutor:继承了ThreadPoolExecutor,实现了ScheduledExecutorService * 3.工具类:Executors * ExecutorService newFixedThreadPool():创建固定大小的线程池 * ExecutorService newCachedThreadPool():缓存线程池,线程池的数据量不固定,可以根据需求自动的更改数量 * ExecutorService newSingleThreadExecutor():创建单个线程池。线程池中只有一个线程 * <p> * ScheduledExecutorService newScheduledThreadPool():创建固定大小的线程,可以延迟或定时的执行任务 * * @author Liu_zimo * @version v0.9a by 2020-10-10 15:30:31 */ public class TheadPoolDemo implements Runnable { public static void main(String[] args) throws ExecutionException, InterruptedException { // 1.创建线程池 ExecutorService pool = Executors.newFixedThreadPool(5); TheadPoolDemo tpd = new TheadPoolDemo(); // 2.为线程池中的线程分配任务,Runnable方式 for (int i = 0; i < 10; i++) { pool.submit(tpd); } // 2.1 Callable方式 Future<Integer> future = pool.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i <= 100; i++) { sum += i; } return sum; } }); System.out.println(future.get()); // 3.关闭线程池,等任务执行结束关闭 pool.shutdown(); } @Override public void run() { System.out.println(Thread.currentThread().getName() + "run..."); } }
-
线程调度
import java.util.Random; import java.util.concurrent.*; /** * 线程调度 * * @author Liu_zimo * @version v0.9a by 2020-10-10 16:42:31 */ public class TheadPoolDemo implements Runnable { public static void main(String[] args) throws ExecutionException, InterruptedException { // 线程池 + 调度 ScheduledExecutorService pool = Executors.newScheduledThreadPool(5); for (int i = 0; i < 10; i++) { ScheduledFuture<Integer> future = pool.schedule(new Callable<Integer>() { @Override public Integer call() throws Exception { int i = new Random().nextInt(100); System.out.println(Thread.currentThread().getName() + " : " + i); return i; } }, 1, TimeUnit.SECONDS); System.out.println(future.get()); } pool.shutdown(); } }
-
ForkJoinPool分支/合并框架 工作窃取
import java.util.concurrent.ForkJoinTask; import java.util.concurrent.RecursiveTask; import java.util.concurrent.ForkJoinPool; /** * Fork/Join 框架: * * @author Liu_zimo * @version v0.9a by 2020-10-10 18:08:54 */ public class ForkJoinPoolDemo { public static void main(String[] args) { ForkJoinPool pool = new ForkJoinPool(); ForkJoinTask<Long> task = new ForkJoinSumCalculate(0L, 100000000L); Long sum = pool.invoke(task); System.out.println(sum); } } class ForkJoinSumCalculate extends RecursiveTask<Long> { private static final long serialVersionUID = -259195479995561737L; private long start; private long end; private static final long THURSHOLD = 1000L; // 临界值 public ForkJoinSumCalculate(long start, long end) { this.start = start; this.end = end; } @Override protected Long compute() { if (end - start <= THURSHOLD){ long sum = 0L; for (long i = start; i <= end; i++) { sum += i; } return sum; }else { long middle = (start + end) / 2; ForkJoinSumCalculate left = new ForkJoinSumCalculate(start, middle); left.fork(); // 进行拆分,同时压入线程队列 ForkJoinSumCalculate right = new ForkJoinSumCalculate(middle + 1, end); right.fork(); long i = left.join() + right.join(); return i; } } }
Java NIO
Java NIO简介
Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java lO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
Java NIO与IO的主要区别
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向缓冲区(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
无 | 选择器(Selectors) |
缓冲区(Buffer)和通道(Channel)
Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到IO设备(例如:文件、套接字)的连接。若需要使用NIO系统,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。
简而言之,Channel负责传输,Buffer负责存储
-
缓冲区(Buffer)
一个用于特定基本数据类型的容器。由java.nio包定义的,所有缓冲区都是Buffer抽象类的子类。
Java NIO中的Buffer主要用于与NIO通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的。
-
通道
由java.nio.channels包定义的。Channel表示IO源与目标打开的连接。Channel类似于传统的“流”。只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互。
直接缓冲区和非直接缓冲区 -
字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则Java虚拟机会尽最大努力直接在此缓冲区上执行本机I/O操作。也就是说,在每次调用基础操作系统的一个本机l/O操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。
-
直接字节缓冲区可以通过调用此类的allocateDirect()工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机l/O操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
-
直接字节缓冲区还可以通过FileChannel的map()方法将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer。Java平台的实现有助于通过JNl从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。
-
字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其isDirect()方法来确定。提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理。
缓冲区示例:
import java.nio.ByteBuffer;
/**
* NIO:
* 1.缓冲区(Buffer):在Java NIO中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据
* 根据数据类型不同(boolean除外),提供了相应类型的缓冲区。
* ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer
* 上述缓冲区的管理方式几乎一致,通过allocate()获取缓冲区
* 2.缓冲区存取数据的两个核心方法:
* 2.1 put():存入数据到缓冲区
* 2.2 get():获取缓冲区这数据
* 3.缓冲区的四个核心属性:
* capacity:容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变
* limit:界限,表示缓冲区中可以操作数据的大小。(limit后数据不能进行读写)
* position:位置,表示缓冲区中正在操作数据的位置
*
* mark:标记,表示记录当前position的位置。可以通过reset()恢复到mark的位置
* 0 <= mark <= position <= limit <= capacity
*
* @author Liu_zimo
* @version v0.9a by 2020-10-12 12:02:42
*/
public class NIOBufferDemo {
public static void main(String[] args) {
// 1.分配指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // position:0 limit:1024 capacity:1024
// 2.利用put存入数据到缓冲区
byteBuffer.put("abcde".getBytes()); // position:5 limit:1024 capacity:1024
// 3.切换为读取数据模式,limit变为实际刻度的数据5
byteBuffer.flip(); // position:0 limit:5 capacity:1024
// 4.利用get读取缓冲区数据
byte[] dst = new byte[byteBuffer.limit()];
byteBuffer.get(dst); // position:5 limit:5 capacity:1024
System.out.println(new String(dst));
// 5.rewind():可重复读数据
byteBuffer.rewind(); // position:0 limit:5 capacity:1024
// 6.clear():清空缓冲区,但是缓冲区数据依然存在,但是处于被遗忘状态
byteBuffer.clear(); // position:0 limit:1024 capacity:1024
System.out.println((char) byteBuffer.get()); // a
String str = "abcde";
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(str.getBytes());
buffer.flip();
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes,0,2);
System.out.println(new String(bytes,0,2)); // position:2
// mark标记
buffer.mark();
buffer.get(bytes,2,2);
System.out.println(new String(bytes,2,2)); // position:4
// reset恢复到mark标记的位置
buffer.reset(); // position:2
// 获取直接缓冲区
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
System.out.println("buf是否为直接缓冲区:" + buf.isDirect());
}
}
文件通道(FileChannel)
通道示例:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Map;
import java.util.RandomAccess;
import java.util.Set;
import java.util.SortedMap;
/**
* NIO:
* 1.通道(Channel):用于源节点与目标节点的连接。在Java NIO中负责缓冲区中数据的传输。Channel本身不存储数据,因此需要配合缓冲区进行传输
* 2.通道主要实现类:
* java.nio.channels.Channel接口
* |-- FileChannel:本地
* |-- SocketChannel:网络
* |-- ServerSocketChannel:网络
* |-- DatagrameChannel:UDP
* 3.获取通道
* 3.1 Java针对支持通道的类提供了getChannel()方法
* 本地IO:
* FileInputStream/FileOutputStream
* RandomAccessFile
* 网络IO:
* Socket
* ServerSocket
* DatagramSocket
* 3.2 JDK 1.7中的NIO.2
* 针对各个通道提供了静态方法open()
* Files工具类的newByteChannel()
* 4.通道之间的数据传输
* transferFrom()
* tarnsferTo()
* 5.分散(Scatter)与聚集(Gather)
* 分散读取:将通道中的数据分散到多个缓冲区中(顺序)
* 聚集写入:将多个缓冲区中的数据聚集写入到通道(顺序)
* 6.字符集:Charset
* 编码:字符串 - > 字符数组
* 解码:字符数组 - > 字符串
*
* @author Liu_zimo
* @version v0.9a by 2020-10-12 12:02:42
*/
public class NIOChannelDemo {
public static void main(String[] args) throws IOException {
// 1.利用通道完成文件的复制(非直接缓冲区)
FileInputStream fileInputStream = new FileInputStream("1.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("2.jpg");
// 获取通道
FileChannel inChannel = fileInputStream.getChannel();
FileChannel outChannel = fileOutputStream.getChannel();
// 分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
// 将通道中数据存入缓冲区
while (inChannel.read(buf) != -1){
buf.flip(); // 切换读取模式
// 将缓冲区中数据写出通道
outChannel.write(buf);
buf.clear(); // 情况缓冲区
}
outChannel.close();
inChannel.close();
fileInputStream.close();
fileOutputStream.close();
// 2.使用直接缓冲区完成文件的复制(内存映射文件)
FileChannel in = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel out = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
// 内存映射文件
MappedByteBuffer inMapBuf = in.map(FileChannel.MapMode.READ_ONLY, 0, in.size());
MappedByteBuffer outMapBuf = out.map(FileChannel.MapMode.READ_WRITE, 0, in.size());
// 直接对缓冲区进行数据读写
byte[] dst = new byte[inMapBuf.limit()];
inMapBuf.get(dst);
outMapBuf.put(dst);
in.close();
out.close();
// 3.通道之间的数据传输(直接缓冲区)
FileChannel in1 = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel out1 = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
// in1.transferTo(0, in1.size(), out1);
out1.transferFrom(in1, 0, in1.size()); // 等价于上面那个
in1.close();
out1.close();
// 4.分散和聚集
RandomAccessFile rw = new RandomAccessFile("1.txt", "rw");
// 获取通道
FileChannel channel = rw.getChannel();
// 分配指定大小的缓冲区
ByteBuffer buf1 = ByteBuffer.allocate(100);
ByteBuffer buf2 = ByteBuffer.allocate(1000);
// 分散读取
ByteBuffer[] bufs = {buf1, buf2};
channel.read(bufs);
for (ByteBuffer byteBuffer : bufs) {
byteBuffer.flip();
}
System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));
// 聚集写入
RandomAccessFile rw1 = new RandomAccessFile("2.txt", "rw");
FileChannel channel1 = rw1.getChannel();
channel1.write(bufs);
rw.close();
rw1.close();
// 字符集
SortedMap<String, Charset> map = Charset.availableCharsets();
Set<Map.Entry<String, Charset>> entrySet = map.entrySet();
for (Map.Entry<String, Charset> entry : entrySet) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
Charset cs1 = Charset.forName("GBK");
// 获取编码器
CharsetEncoder ce = cs1.newEncoder();
// 获取解码器
CharsetDecoder cd = cs1.newDecoder();
CharBuffer charBuffer = CharBuffer.allocate(1024);
charBuffer.put("哈哈哈!");
charBuffer.flip();
// 编码
ByteBuffer encode = ce.encode(charBuffer);
// 解码
encode.flip();
CharBuffer decode = cd.decode(encode);
System.out.println(decode);
// GBK编,UTF-8解
Charset cs = Charset.forName("UTF-8");
encode.flip();
CharBuffer decode1 = cs.decode(encode);
System.out.println(decode.toString());
}
}
NIO的非阻塞式网络通信
阻塞式IO
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
/**
* 1.使用NIO完成网络通信的三个核心:
* 1.通道:负责连接
* java.nio.channels.Channel接口
* |-- SelectableChannel
* |-- SocketChannel:网络TCP
* |-- ServerSocketChannel:网络TCP
* |-- DatagrameChannel:UDP
*
* |-- Pipe.SinkChannel
* |-- Pipe.SourceChannel
* 2.缓冲区:负责数据的存取
* 3.选择器:是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况
*
* @author Liu_zimo
* @version v0.9a by 2020-10-12 12:02:42
*/
public class BlockingNIO {
public static void main(String[] args) {
BlockingNIO blockingNIO = new BlockingNIO();
new Thread(new Runnable() {
@Override
public void run() {
try {
blockingNIO.server();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
new BlockingNIO().client();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
public void client() throws IOException {
// 1.获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 10086));
FileChannel fileChannel = FileChannel.open(Paths.get("1.txt"), StandardOpenOption.READ);
// 2.分配指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 3.读取本地文件,并发送到服务端
while (fileChannel.read(buffer) != -1){
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
}
// 如果不通知服务器关闭,将一直处于阻塞状态
socketChannel.shutdownOutput();
// 添加:接收服务器反馈
int len = 0;
while ((len = socketChannel.read(buffer)) != -1){
buffer.flip();
System.out.println(new String(buffer.array(), 0, len));
buffer.clear();
}
// 4.关闭资源
fileChannel.close();
socketChannel.close();
}
public void server() throws IOException {
// 1.获取通道
ServerSocketChannel open = ServerSocketChannel.open();
FileChannel outChannel = FileChannel.open(Paths.get("2.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
// 2.绑定连接
open.bind(new InetSocketAddress(10086));
// 3.获取客户端连接的通道
SocketChannel accept = open.accept();
// 4.分配指定大小缓冲区
ByteBuffer b = ByteBuffer.allocate(1024);
// 5.获取客户端的数据,并保存到本地
while (accept.read(b) != -1){
b.flip();
outChannel.write(b);
b.clear();
}
// 添加:给客户端回复
b.put("服务端成功接收数据".getBytes());
b.flip();
accept.write(b);
// 6.关闭通道
accept.close();
outChannel.close();
open.close();
}
}
-
选择器(Selector)
选择器(Selector)是SelectableChannle对象的多路复用器,Selector可以同时监控多个SelectableChannel的IO状况,也就是说,利用Selector可使一个单独的线程管理多个Channel。Selector是非阻塞lO的核心。
创建Selector:通过调用Selector.open()方法创建一个Selector
向选择器注册通道:
SelectableCahnnel.register(Selector sel, int ops)
- 当调用
register(Selector sel, int ops)
将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数ops 指定。 - 可以监听的事件类型**(可使用SelectionKey的四个常量表示)**:
- 读:
SelectionKey.OP_READ
(1)
写:SelectionKey.OP_WRITE
(4)
连接:SelectionKey.OP_CONNECT
(8)
接收:SelectionKey.OP_ACCEPT
(16)
- 读:
- 若注册时不止监听一个事件,则可以使用“位或”操作符连接。
- 例:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
- 例:
Selector的常用方法
方法 描述 Set<SelectionKey> keys()
所有的 SelectionKey集合。代表注册在该Selector上的Channel selectedKeys()
被选择的SelectionKey集合。返回此Selector的已选择键集 int select()
监控所有注册的Channel,当它们中间有需要处理的IO操作时,
该方法返回,并将对应的SelectionKey加入被选择的SelectionKey
集合中,该方法返回这些Channel的数量。int select(long timeout)
可以设置超时时长的select()操作 int selectNow()
执行一个立即返回的select()操作,该方法不会阻塞线程 Selector wakeup()
使一个还未返回的select()方法立即返回 void close()
关闭该选择器 SelectionKey
表示SelectableChannel和Selector之间的注册关系。每次向选择器注册通道时就尝选择一个事件(选择键)。选择键包含两个表示为整
数值的操作集。操作集的每一位都表示该键的通道所支持的一类可选择操作。方法 描述 int intersetOps()
获取感兴趣事件集合 int readyOps()
获取通道已经准备就绪的操作的集合 SelectableChannel channel()
获取注册通道 Selector selector()
返回选择器 bolean isReadable()
检测Channel中读事件是否准备就绪 boolean isWritable()
检测Channel中写事件是否准备就绪 boolean isConnectable()
检测Channel中连接是否就绪 boolean isAcceptable()
检测Channel中接收是否就绪 - 当调用
-
SocketChannel、SeverSocketChannel、DatagramChannel
SocketChannel:
- Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。
- 操作步骤:
- 打开SocketChannel
- 读写数据
- 关闭SocketChannel
DatagramChannel
- Java NIO中的DatagramChannel是一个连接到UDP包的通道。
- 操作步骤:
- 打开DatagramChannel
- 接收/发送数据
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;
/**
* 1.使用NIO完成网络通信的三个核心:
* 1.通道:负责连接
* java.nio.channels.Channel接口
* |-- SelectableChannel
* |-- SocketChannel:网络TCP
* |-- ServerSocketChannel:网络TCP
* |-- DatagrameChannel:UDP
*
* |-- Pipe.SinkChannel
* |-- Pipe.SourceChannel
* 2.缓冲区:负责数据的存取
* 3.选择器:是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况
*
* @author Liu_zimo
* @version v0.9a by 2020-10-12 12:02:42
*/
public class NonBlockingNIO {
public static void main(String[] args) {
NonBlockingNIO blockingNIO = new NonBlockingNIO();
// blockingNIO.testTCP();
blockingNIO.testUDP();
}
private void testUDP() {
new Thread(new Runnable() {
@Override
public void run() {
try {
recv();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
send();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
public void testTCP(){
new Thread(new Runnable() {
@Override
public void run() {
try {
server();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
client();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
public void client() throws IOException {
// 1.获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 10086));
// 2.切换非阻塞模式
socketChannel.configureBlocking(false);
// 3.分配指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
/*
// 4.发送时间到服务端
buffer.put(LocalDateTime.now().toString().getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();*/
// 4.给服务端发消息
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String str = scanner.next();
buffer.put((new Date().toString() + "\n" + str).getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
}
// 5.关闭通道
socketChannel.close();
}
public void server() throws IOException {
// 1.获取通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 2.切换非阻塞模式
serverSocketChannel.configureBlocking(false);
// 3.绑定连接
serverSocketChannel.bind(new InetSocketAddress(10086));
// 4.获取选择器
Selector selector = Selector.open();
// 5.将通道注册到选择器上,并指定监听接收事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 6.轮询式的获取选择器上已经准备就绪的事件
while (selector.select() > 0){
// 7.获取当前选择器中,所有注册的选择键(已就绪的监听事件)
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
// 8.迭代获取
while (iterator.hasNext()){
SelectionKey next = iterator.next();
// 9.判断什么事件准备就绪
if (next.isAcceptable()){
// 10.若“接收就绪”,获取客户端连接
SocketChannel accept = serverSocketChannel.accept();
// 11.切换非阻塞模式
accept.configureBlocking(false);
// 12.将该通道注册到选择器上
accept.register(selector, SelectionKey.OP_READ);
}else if (next.isReadable()){
// 13.获取当前选择器上“读就绪”状态的通道
SocketChannel channel = (SocketChannel) next.channel();
// 14.读取数据,分配指定大小缓冲区
ByteBuffer b = ByteBuffer.allocate(1024);
int len = 0;
while ((len = channel.read(b)) > 0){
b.flip();
System.out.println(new String(b.array(),0,len));
b.clear();
}
}
// 15.取消选择键SelectionKey
iterator.remove();
}
}
}
public void send() throws IOException {
DatagramChannel open = DatagramChannel.open();
open.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String str = scanner.next();
buffer.put((new Date().toString() + ":\n" + str).toString().getBytes());
buffer.flip();
open.send(buffer, new InetSocketAddress("localhost",10010));
buffer.clear();
}
open.close();
}
public void recv() throws IOException {
DatagramChannel open = DatagramChannel.open();
open.configureBlocking(false);
open.bind(new InetSocketAddress(10010));
Selector select = Selector.open();
open.register(select, SelectionKey.OP_READ);
while (select.select() > 0){
Iterator<SelectionKey> it = select.selectedKeys().iterator();
while (it.hasNext()){
SelectionKey next = it.next();
if (next.isReadable()){
ByteBuffer buf = ByteBuffer.allocate(1024);
open.receive(buf);
buf.flip();
System.out.println(new String(buf.array(), 0, buf.limit()));
buf.clear();
}
}
it.remove();
}
}
}
管道(Pipe)
Java NIO管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
public class PipeDemo {
public static void main(String[] args) throws IOException {
// 1.获取管道
Pipe pipe = Pipe.open();
// 2.将缓冲区的数据写入管道
ByteBuffer buffer = ByteBuffer.allocate(1024);
Pipe.SinkChannel sink = pipe.sink();
buffer.put("通过单向管道发送数据".getBytes());
buffer.flip();
sink.write(buffer);
// 3.读取缓冲区中的数据
Pipe.SourceChannel source = pipe.source();
buffer.flip();
int len = source.read(buffer);
System.out.println(new String(buffer.array(), 0, len));
source.close();
sink.close();
}
}