并发编程学习笔记(五)——Java8、9、10

一:Java8的函数式编程

1. 特点

  • 将函数作为参数传递给另外一个函数
  • 将函数作为另外一个函数的返回值
  • 避免修改函数的外部状态
  • 声明式编程,不需要提供明确的指令操作
  • 传递的对象不变,易于并行

2. 基本概念

函数式接口

只定义单一抽象方法(被Object实现的方法不是抽象方法)的接口,可以存在实例方法

@FunctionalInterface
public interface Handler {
    void handle();
}
接口默认方法

接口可以包含多个默认实例方法,这样类可以通过多重实现接口,实现多重继承

@FunctionalInterface
public interface Handler {
    void handle();
    default void myfunction(){
        System.out.println("Hello");
    }
}

如果实现的多个接口具有同名的实例方法,则需要方法绑定

package function;
@FunctionalInterface
interface Handler1 {
    void handle1();
    default void myfunction(){
        System.out.println("Hello1");
    }
}

@FunctionalInterface
interface Handler2 {
    void handle2();
    default void myfunction(){
        System.out.println("Hello2");
    }
}
public class FunctionTest implements Handler1,Handler2 {
    @Override
    public void handle1() {
        System.out.println("类实现的handle1");
    }
    @Override
    public void handle2() {
        System.out.println("类实现的handle2");
    }

	//方法绑定
    @Override
    public void myfunction() {
        Handler2.super.myfunction();
    }

    public static void main(String[] args) {
        FunctionTest test = new FunctionTest();
        test.handle1();
        test.handle2();
        test.myfunction();
    }
}

lambda表达式

即匿名函数,可以作为参数直接传递给调用者,可以访问外部的常量

结构:(参数)-> 函数体

方法引用

通过类名和方法名来定位一个静态方法或实例方法,系统自动判断流中的元素(实例或类)是调用方法的调用目标还是调用方法传人的调用参数

  • 引用静态方法 ClassName::staticMethodName
  • 引用某个对象的实例方法 Object::instanceMethodName
  • 引用某个类型的任意对象的实例方法 ClassName::methodName
  • 引用超类的实例方法 super::methodName
  • 引用构造方法 ClassName::new
  • 引用数组构造方法:TypeName[]::new

如果一个类存在同名的实例方法和静态方法,则会出现编译错误

流对象

类似于集合或数组,是一个对象的集合。通过流对象可以方便的处理流内的元素

		List list = new ArrayList();
        list.stream();
        int[] arr = new int[10];
        Arrays.stream(arr);

3. 并行流

(1)获取流
list.stream().parallel(); / list.parallelStream();
Arrays.stream(arr).parallel();

(2)数组并行

		//并行排序,并且可以指定范围
		Arrays.parallelSort();
		//对数组的每一个值进行计算并更新
        Arrays.parallelSetAll();
        //将数组中每个元素替换为指定关联操作前缀的积累
        Arrays.parallelPrefix();

4. CompletableFuture

(1)可等待

如果CompletableFuture没有需要的数据,则线程会进入等待

package function;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureTest {
    private static class AskThread extends Thread{
        CompletableFuture<Integer> future;

        public AskThread(CompletableFuture<Integer> future) {
            this.future = future;
        }

        //如果数据没有准备好,则会阻塞
        @Override
        public void run() {
            try {
                System.out.println("get data:"+future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        CompletableFuture<Integer> future = new CompletableFuture<>();
        AskThread thread = new AskThread(future);
        thread.start();
        Thread.sleep(3000);
        future.complete(5);
    }
}

(2)异步调用

在异步调用后可以立即返回CompletableFuture实例使用,如果需要立即获得CompletableFuture的数据,而数据还没有准备好,才需要等待

  • supplyAsync(线程池):返回值
  • runAsync(线程池):没有返回值

默认在ForkJoinPool.commonPool中,所有线程都是守护线程

package function;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureTest {
    
    private static int async(int data){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return data;
    }
    public static void main(String[] args) throws InterruptedException, ExecutionException {

        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->async(10));
        System.out.println("立即返回:"+future);
        System.out.println(future.get());
    }
}

(3)流式调用
CompletableFuture<Void> future = CompletableFuture
                .supplyAsync(()->async(10))
                .thenApply((i) -> i++)
                .thenApply((str) -> "\""+str+"\"")
                .thenAccept(System.out::println);
        System.out.println("立即返回:"+future);
        System.out.println(future.get());
(4)捕获异常
CompletableFuture<Void> future = CompletableFuture
                .supplyAsync(()->async(10))
                .exceptionally(ex-> {
                    System.out.println(ex.toString());
                    return 0;
                })
                .thenApply((i) -> i++)
                .thenApply((str) -> "\""+str+"\"")
                .thenAccept(System.out::println);
(5)组合
thenCompose

将执行结果传递给下一个CompletableFuture

CompletableFuture<Void> future = CompletableFuture
                .supplyAsync(()->async(10))
                .exceptionally(ex-> {
                    System.out.println(ex.toString());
                    return 0;
                })
                .thenCompose((i)->CompletableFuture.supplyAsync(()->async(i)))
                .thenApply((str) -> "\""+str+"\"")
                .thenAccept(System.out::println);
thenCombine

对两个CompletableFuture的执行结果进行操作

CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(()->async(10));
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(()->async(20));
        CompletableFuture<Void> result = future1.thenCombine(future2,(i,j)->i+j)
                .thenApply((str) -> "\""+str+"\"")
                .thenAccept(System.out::println);
        result.get();
(6)指定时间

指定future在一定时间内执行,超时则抛出异常

CompletableFuture<Void> future = CompletableFuture
                .supplyAsync(()->async(10))
                .orTimeout(1, TimeUnit.SECONDS)
                .exceptionally(ex-> {
                    System.out.println(ex.toString());
                    return 0;
                })
//                .thenCompose((i)->CompletableFuture.supplyAsync(()->async(i)))
                .thenApply((str) -> "\""+str+"\"")
                .thenAccept(System.out::println);
        System.out.println("立即返回:"+future);
        System.out.println(future.get());

5. 改进读写锁——StampedLock

乐观的读策略,读锁不会阻塞写锁

(1)使用
import java.util.concurrent.locks.StampedLock;

public class StampedLockTest {
    //数据
    private int data;
    //锁
    private StampedLock lock;

    //写锁,排他锁
    public void write(int data){
        long stamp = lock.writeLock();
        try {
            this.data = data;
        }
        finally {
            lock.unlockWrite(stamp);
        }
    }

    //读锁,共享锁
    public int read(){
        //尝试乐观读
        long stamp = lock.tryOptimisticRead();
        int current = data;
        //判断在读的时候有没有被写
        if (!lock.validate(stamp)){
            //如果被修改,可以在死循环中不断读,直到成功
            //或者升级锁的级别,变成悲观锁,如果当前数据被修改则挂起
            stamp = lock.readLock();
            try {
                current = data;
            }finally {
                lock.unlockRead(stamp);
            }
        }
        return current;
    }

}

(2)注意

StampedLock使用死循环反复尝试的策略

  • 挂起线程时,使用Unsafe.park(),遇到中断直接中断
  • 死循环没有处理中断,导致不断循环
 public long readLock() {
        long s = state, next;  // bypass acquireRead on common uncontended case
        return ((whead == wtail && (s & ABITS) < RFULL &&
                 U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ?
                next : acquireRead(false, 0L));
    }
(3)实现原理

基于CLH锁(自旋锁),保证没有饥饿发生和先来先服务顺序
锁维护了一个等待线程队列,记录申请锁失败的线程。每一个结点代表一个线程,保存一个标记位,判断线程是否释放锁

  • 线程申请锁,把队列的尾部节点作为其前序节点,如果前序节点没有释放锁,则自旋等待;反之可以执行
  • 线程释放锁,将自身的标记位置为false
  static final class WNode {
        volatile WNode prev;
        volatile WNode next;
        volatile WNode cowait;    // list of linked readers
        volatile Thread thread;   // non-null while possibly parked
        volatile int status;      // 0, WAITING, or CANCELLED
        final int mode;           // RMODE or WMODE
        WNode(int m, WNode p) { mode = m; prev = p; }
    }

    /** Head of CLH queue */
    private transient volatile WNode whead;
    /** Tail (last) of CLH queue */
    private transient volatile WNode wtail;

写锁:

 public long writeLock() {
        long s, next;  // bypass acquireWrite in fully unlocked case only
        //设置state的写锁位为1
        return ((((s = state) & ABITS) == 0L &&
                 U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?
                next : acquireWrite(false, 0L));
    }

乐观读:

 public long tryOptimisticRead() {
        long s;
        //判断state的写锁位=0
        return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;
    }

校准:

public boolean validate(long stamp) {
        U.loadFence();
        return (stamp & SBITS) == (state & SBITS);
    }

悲观锁:

 public long readLock() {
        long s = state, next;  // bypass acquireRead on common uncontended case
        //尝试state加1
        return ((whead == wtail && (s & ABITS) < RFULL &&
                 U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ?
                next : acquireRead(false, 0L));
    }
  • 如果悲观锁失败,则进入自旋
  • 多次自旋失败后启用CLH队列,再自旋
    • 自旋失败后,通过Unsafe.park()方法挂起线程
    • 获取锁成功后,激活自己的所有读线程

6. 增强原子类

(1)LongAdder

在一般的原子类中,修改数据时会陷入死循环,直到修改成功,如果有大量线程竞争,则修改失败的几率高
通过热点分离,把数据分解成多个单元,线程访问某个单元并修改单元值,单元值的合成就是该数据的值

  • 把所有数据记录在一个变量中,如果对变量的修改没有冲突,则成功
  • 如果发生冲突,则把数据分解成多个单元,再修改
  • 如果修改单元发生冲突,则创建新的单元,或者单元数量加倍
    public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        //先尝试修改变量base
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)) //再尝试修改单元值
                longAccumulate(x, null, uncontended);	//重新修改
        }
    }

使用:

adder.increment();	//增加
adder.sum();	//单元总值
(2)LongAccumulator

LongAdder只能实现整数的加法,而LongAccumulator可以实现任意函数

使用:

`LongAccumulator accumulator = new LongAccumulator(二元函数(接收两个long型参数),初始值);`
accumulator.accumulate(参数);	//传入参数到二元函数,返回一个long值

7. 增强ConcurrentHashMap

  • foreach:接口是一个Consumer或BigConsumer,用于对Map的数据进行消费
  • reduce:foreach操作的function版本
  • 条件插入computeIfAbsent:判断是否不存在,是则添加,保证线程安全
  • search:查找第一个使得function返回不为null的值
  • mappingCount:返回Map的条目总数(结果不一定准确)
  • newKeySet:实现安全的HashSet

8. 发布和订阅模式

(1)反应式编程

用于处理异步流中的数据,每当应用收到数据项,便进行处理

  • Publisher:发布者,将数据发布到流中
    • onCompleted(): 没有数据
    • onError(): 发生异常
    • onSubscribe():订阅者注册后被调用的第一个方法
    • onNext():下一个数据准备好
  • Subscriber:接收者,从流中接收数据并处理
    • request():请求数据个数
    • cancel():停止接收新消息
import java.util.Arrays;

import java.util.concurrent.Flow.*;
import java.util.concurrent.SubmissionPublisher;  

public class FlowDemo
{
   public static void main(String[] args)
   {
      // 创建数据发布者
      SubmissionPublisher<String> publisher = new SubmissionPublisher<>();

      // 注册订阅者
      MySubscriber<String> subscriber = new MySubscriber<>();
      publisher.subscribe(subscriber);

      //发布数据,发布完成关闭发布者
      System.out.println("Publishing data items...");
      String[] items = { "jan", "feb", "mar", "apr", "may", "jun",
                         "jul", "aug", "sep", "oct", "nov", "dec" };
      Arrays.asList(items).stream().forEach(i -> publisher.submit(i));
      publisher.close();

	//等待订阅者处理
      try
      {
         synchronized("A")
         {
            "A".wait();
         }
      }
      catch (InterruptedException ie)
      {
      }
   }
}

class MySubscriber<T> implements Subscriber<T>
{
   private Subscription subscription;

	//注册时调用,开始请求数据
   @Override
   public void onSubscribe(Subscription subscription)
   {
      this.subscription = subscription;
      subscription.request(1);
   }

	//请求数据,继续请求下一个
   @Override
   public void onNext(T item)
   {
      System.out.println("Received: " + item);
      subscription.request(1);
   }

   @Override
   public void onError(Throwable t)
   {
      t.printStackTrace();
      synchronized("A")
      {
         "A".notifyAll();
      }
   }

   @Override
   public void onComplete()
   {
      System.out.println("Done");
      synchronized("A")
      {
         "A".notifyAll();
      }
   }
}
(2)数据处理链
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【为什么学习这门课程?】 课程教授如何通过模式、面向对象设计技术和Java编程语言的特性来开发高质量并发软件应用程序和可重用框架。 多核的分布式核处理器、廉价的大容量存储、无处不在的连接性和通用软件平台的融合趋势,正推动着软件工程师和程序员的需求变化,他们需要知道如何为连接到云计算平台的客户端设备开发并发软件。尽管目前在处理器、存储和网络方面有许多改进,但是从客观上说,想要根据预算额度按时开发和交付高质量的软件仍然是有难度的,特别是开发高质量的并发软件应用程序和可重用服务。 【课程亮点】 本课程通过示例描述了如何通过使用面向对象的设计技术、Java编程语言特性、类库、应用模式和框架等技术要点,来有效降低并发软件开发的复杂性。课程中使用了许多Java应用程序示例来展示并发软件中的面向模式设计和编程技术。 【讲师介绍】 Douglas C. Schmidt(道格拉斯·施密特)—— ACE / TAO初始研发者、《C++网络编程》作者 施密特博士是ACE、TAO和CIAO的初始研发者,过去的20年里,领导了面向模式DRE中间件框架的发展。这些技术已被全球数千家公司和机构成功应用于许多领域,包括国防和安全、数据通信/电信、金融服务、医疗工程和大型多人在线游戏。施密特博士曾担任卡耐基梅隆大学软件工程学院的首席技术官,目前是美国范德堡大学(Vanderbilt University)的计算机科学教授。主要研究分布式实时和嵌入式系统的模式、优化、中间件和基于模型的工具。  施密特博士还是《C++报告》的前主编和《C/ C++用户》杂志专栏作家。发表了500多篇技术论文相关的话题, 主要涉及模式、优化技术、面向对象的框架和实证分析和特定领域的建模环境。与人合著了四本模式领域的经典书,包括《C++网络编程》第一卷和第二卷、《编程设计中的模式语言》、《面向模式的软件设计》。
嗨!很高兴回答你关于Java并发编程的问题。请问你想知道什么方面的内容呢?我可以分享一些学习笔记和建议给你。 1. 并发编程基础:了解并发编程的基本概念,如线程、进程、锁、同步等。学习Java中的并发编程模型以及相关的API,如Thread、Runnable、Lock、Condition等。 2. 线程安全性:学习如何保证多线程环境下的数据安全性,了解共享资源的问题以及如何使用同步机制来防止数据竞争和并发问题。 3. 线程间的通信:掌握线程间的通信方式,如使用wait/notify机制、Lock/Condition等来实现线程的协调与通信。 4. 并发容器:学习并发容器的使用,如ConcurrentHashMap、ConcurrentLinkedQueue等。了解它们的实现原理以及在多线程环境下的性能特点。 5. 并发工具类:熟悉Java提供的并发工具类,如CountDownLatch、CyclicBarrier、Semaphore等,它们可以帮助你更方便地实现线程间的协作。 6. 并发编程模式:学习一些常见的并发编程模式,如生产者-消费者模式、读者-写者模式、线程池模式等。了解这些模式的应用场景和实现方式。 7. 性能优化与调试:学习如何分析和调试多线程程序的性能问题,了解一些性能优化的技巧和工具,如使用线程池、减少锁竞争、避免死锁等。 这些只是一些基本的学习笔记和建议,Java并发编程是一个庞大而复杂的领域,需要不断的实践和深入学习才能掌握。希望对你有所帮助!如果你有更具体的问题,欢迎继续提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值