java8与并行

申明式的编程方式

不需要提供明确的指令操作,所有的细节指令被程序库封装,只要提出要求,申明用意

public class Test1 {
	public static void main(String[] args) {
		int[]ary={1,2,5,2};
		for (int i = 0; i < ary.length; i++) {
			System.out.println(ary[i]);
		}
		Arrays.stream(ary).forEach(System.out::println);
	}
}
不变的对象
几乎所有传递的对象都不会被轻易修改

public class Test1 {
	public static void main(String[] args) {
		int[]ary={1,2,5,2};
		Arrays.stream(ary).map((x)->x=x+1).forEach(System.out::println);
		Arrays.stream(ary).forEach(System.out::println);
	}
}
2
3
6
3
1
2
5
2

易于并行

因为对象是不变的,不必担心多线程会写坏对象,不必同步和锁

代码更少

判断奇数加1

public class Test1 {
	static int[]arr={1,2,3,45,6};
	public static void main(String[] args) {
		for(int i=0;i<arr.length;i++){
			if(arr[i]%2!=0){
				arr[i]++;
			}
			System.out.println(arr[i]);
		}
		Arrays.stream(arr).map(x->(x%2==0?x:x+1)).forEach(System.out::println);
	}
}
FunctionalInterface注释
函数式接口,只定义了单一抽象方法的接口

	@FunctionalInterface
	public static interface IntHandler{
		void handle(int i);
	}
表明它是函数式接口,只包含一个抽象方法,但可以有其他方法,被Object实现的方法都不是抽象方法
接口默认方法

java8可以包含实例方法,需要用default关键字

	@FunctionalInterface
	public static interface IntHandler{
		void handle(int i);
		default void run(){}
	}
可以多实现,但会有多继承相同的问题
俩接口有相同实例方法,子类就会报错,需要指定

public interface IHorse {
	void eat();
	default void run(){
		System.out.println("hourse run");
	}
}
public interface IAbimal {
	void eat();
	default void breath(){
		System.out.println("breath");
	}
	default void run(){
		System.out.println("abimal run");
	}
}
public class Mule implements IHorse,IAbimal{
	@Override
	public void eat() {
		System.out.println("mule eat");
	}
	//重新实现run方法,指定
	@Override
	public void run() {
		IAbimal.super.run();
	}
	public static void main(String[] args) {
		Mule m = new Mule();
		m.eat();
		m.run();
		m.breath();
	}
}
比方java.util.Comparator接口,增加了default方法,用于多个比较器的整合
    default Comparator<T> thenComparing(Comparator<? super T> other) {
        Objects.requireNonNull(other);
        return (Comparator<T> & Serializable) (c1, c2) -> {
            int res = compare(c1, c2);
            return (res != 0) ? res : other.compare(c1, c2);
        };
    }
先比较字符串长度,在按照大小写不敏感的字母顺序排列
		Comparator<String>cmp = Comparator.comparingInt(String::length).thenComparing(String.CASE_INSENSITIVE_ORDER);
lambda表达式
即匿名表达式,是一段没有函数名的函数体,可以作为参数直接传递给相关的调用者

		List<Integer> numbers = Arrays.asList(1,2,3,4,5);
		numbers.forEach((Integer value)->System.out.println(value));
类似匿名内部类,简单的描述了应该执行的代码段

可以访问外部的局部变量,如下,外部的变量必须申明为final,保证不可变,合法的访问,但默认会加final,不加也会默认

		final int num =2;
		Function<Integer, Integer>stringConverter = (from)->from*num;
		System.out.println(stringConverter.apply(3));
num++会报错
方法引用
通过类名或方法名定位到一个静态方法或者实例方法

静态方法引用:InnerClassName::methodName

实例上的实例方法引用:instanceReference::methodName

超类上的实例方法引用:super::methodName

类型上的实例方法引用:CLassName::methodName

构造方法引用:Class::new

数组构造方法引用:TypeName[]::new

方法引用使用::定义,::前半部分表示类名或实例名,后半部分表示方法名,是构造方法就用new

public class InstanceMethodRef {
	static class User{
		int i;
		String string;
		public User(int i, String string) {
			this.i=i;
			this.string=string;
		}
		public int getI() {
			return i;
		}
		public void setI(int i) {
			this.i = i;
		}
		public String getString() {
			return string;
		}
		public void setString(String string) {
			this.string = string;
		}		
	}
	public static void main(String[] args) {
		List<User> users = new ArrayList<>();
		for(int i=1;i<10;i++){
			users.add(new User(i,"billy"+Integer.toString(i)));
		}
		users.stream().map(User::getString).forEach(System.out::println);
	}
}

使用静态方法或者调用明确,流内的元素自动作为参数使用,函数引用表示实例方法,且不存在调用目标,流内元素自动作为调用目标

如果一个类中存在同名的实例方法和静态函数,编译器既可以选择同名的实例方法,将流内元素作为调用目标,也可以使用静态方法,将流元素作为参数

比方Double

public String toString()
public static String toString(double d)

方法引用也可以直接调用构造函数

	static class ConstrMethodRef{
		@FunctionalInterface
		interface UserFactory<U extends User>{
			U create(int id,String name);
		}
		static UserFactory<User> uf = User::new;
	}
函数式接口,User::new创建接口实例时,系统会根据UserFactory.create()的函数签名选择合适的User构造函数,

这里是public User(int i, String string),创建UserFactory实例后,对UserFactory.create()的调用,都会委托给User的实际构造函数进行

函数式编程

public class Test2 {
	static int[]arr={1,2,3,45,6};
	public static void main(String[] args) {
		for (int i : arr) {
			System.out.println(i);
		}
		Arrays.stream(arr).forEach(new IntConsumer() {
			@Override
			public void accept(int value) {
				System.out.println(value);
			}
		});
		
	}
}

Arrays.stream()返回一个流对象,类似集合或数组,流对象也是一个对象的集合,有遍历处理流内元素的功能

IntConsumer()接口用于对每个流内对象进行处理,当前流是IntStream,即装有Integer元素的流,foreach会挨个将流内元素送入接口处理

foreach的参数是可以从上下文中推导的,以下不再手动推导,省略接口名

		Arrays.stream(arr).forEach((final int x)->{System.out.println(x);});
参数类型也可以推导

Arrays.stream(arr).forEach((x)->{System.out.println(x);});
去除花括号

Arrays.stream(arr).forEach((x)->System.out.println(x));
lambda表达式,由->分割,左面表示参数,右面实现体
方法引用

Arrays.stream(arr).forEach(System.out::println);
lambda表达式不仅可以简化匿名内部类的编写,还可以使用流式api对各种组件进行更自由的装配

输出俩次元素

		IntConsumer outprintln = System.out::println;
		IntConsumer errprintln = System.err::println;
		Arrays.stream(arr).forEach(outprintln.andThen(errprintln));
使用并行流过滤数据
public class PrimeUtil {
	public static boolean isPrime(int number){
		int tmp = number;
		if(tmp<2){
			return false;
		}
		for(int i=2;i<tmp;i++){
			if(tmp%i==0){
				return false;
			}
		}
		return true;
	} 
	public static void main(String[] args) {
		//串行,首先生成1到1000000的数字流,接着使用过滤函数,只选择所有的质数,最后统计数量
		//IntStream.range(1, 1000000).filter(PrimeUtil::isPrime).count();
		//并行
		IntStream.range(1, 1000000).parallel().filter(PrimeUtil::isPrime).count();
	}
}
从集合中得到并行流
		//使用stream得到一个流
		double ave = ss.stream().mapToInt(s->s.score).average().getAsDouble();
		//并行化
		double ave = ss.parallelStream().mapToInt(s->s.score).average().getAsDouble();
并行排序
Arrays.sort()串行排序
并行排序

		int[] arrr = new int[10000];
		Arrays.parallelSort(arrr);
赋值
		Random r = new Random();
		//串行
		Arrays.setAll(arr, (i)->r.nextInt());
		//并行
		Arrays.parallelSetAll(arr, (i)->r.nextInt());
CompletableFuture
可以作为函数调用的契约,向它请求一个数据,数据没准备好,请求线程会等待,这里可以手动设置完成状态

public class CompletableFutureTest {

	static class AskThread implements Runnable{
		CompletableFuture<Integer> re = null;

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

		@Override
		public void run() {
			int myRe = 0;
			try {
				myRe = re.get()*re.get();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (ExecutionException e) {
				e.printStackTrace();
			}
			System.out.println(myRe);
		}
		public static void main(String[] args) throws InterruptedException {
			final CompletableFuture<Integer> future = new CompletableFuture<>();
			new Thread(new AskThread(future)).start();
			Thread.sleep(1000);
			future.complete(60);
		}
	}
}
异步执行任务
public class CompletableFutureSync {

	public static Integer calc(Integer para){
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return para*para;
	}
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		final CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->calc(50));
		System.out.println(future.get());
	}
}
supplyAsync()函数中,会在一个新的线程中执行传入的参数,会立即返回,没计算完,get方法会等待
runAsync()用于没有返回值的场景

都可以指定线程池,也可以用默认线程池,但主线程退出,立即退出系统

流式调用

public class CompletableFutureSync {
	public static Integer calc(Integer para){
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return para*para;
	}
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		final CompletableFuture<Void> future = CompletableFuture.supplyAsync(()->calc(50))
				.thenApply((i)->Integer.toString(i)).thenApply((src)->"\""+src+"\"").thenAccept(System.out::println);		
		future.get();
	}
}
连续使用流式调用对任务的处理结果进行加工

最后的get()方法目的是等待函数执行完成,不使用,因为是异步的,主线程会退出,然后其他线程也会退出,导致方法无法正常执行

异常处理

public class CompletableFutureSync {
	public static Integer calc(Integer para){
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return para*para;
	}
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		CompletableFuture<Void> future = CompletableFuture.supplyAsync(()->calc(50))
				.exceptionally(ex->{
					System.out.println(ex.toString());
					return 0;
				})
				.thenApply((i)->Integer.toString(i)).thenApply((src)->"\""+src+"\"").thenAccept(System.out::println);		
		future.get();
	}
}
组合多个CompletableFuture

    public <U> CompletableFuture<U> thenCompose(
        Function<? super T, ? extends CompletionStage<U>> fn) {
        return uniComposeStage(null, fn);
    }
一种是使用thenCompose将执行结果传给下一个CompletableFuture

public class CompletableFutureSync {
	public static Integer calc(Integer para){
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return para*para;
	}
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		CompletableFuture<Void> future = CompletableFuture.supplyAsync(()->calc(50))
				.thenCompose((i)->CompletableFuture.supplyAsync(()->calc(i)))
				.thenApply((src)->"\""+src+"\"").thenAccept(System.out::println);		
		future.get();
	}
}
另一种是thenCombine()方法
先执行当前的和其他的,执行完后把结果传给BiFunction

    public <U,V> CompletableFuture<V> thenCombine(
        CompletionStage<? extends U> other,
        BiFunction<? super T,? super U,? extends V> fn) {
        return biApplyStage(null, other, fn);
    }
把俩个结果累加

public class CompletableFutureSync {
	public static Integer calc(Integer para){
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return para*para;
	}
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		CompletableFuture<Integer> future1= CompletableFuture.supplyAsync(()->calc(50));
		CompletableFuture<Integer> future2= CompletableFuture.supplyAsync(()->calc(25));
		CompletableFuture<Void> future3=future1.thenCombine(future2, (i,j)->(i+j))
				.thenApply((src)->"\""+src+"\"").thenAccept(System.out::println);		
		future3.get();
	}
}
读写锁的改进,StampedLock

原本读写锁读锁会堵塞写锁,使用的是悲观策略,有大量的读线程,也可能导致写线程的饥饿

这个lock提供了乐观的读策略,类似无锁操作

public class Point {
	private double x,y;
	private final StampedLock s1 = new StampedLock();
	void move(double deltax,double deltay){//排他锁
		long stamp = s1.writeLock();
		try {
			x+=deltax;
			y+=deltay;
		} finally{
			s1.unlockWrite(stamp);
		}
	}
	double distanceFromOrigin(){//只读方法
		long stamp = s1.tryOptimisticRead();//尝试一次乐观锁,返回一个类似时间戳的邮戳整数
		double currentx =x,currenty=y;
		if(!s1.validate(stamp)){//判断是否被修改过
			stamp = s1.readLock();
			try {
				currentx=x;
				currenty=y;
			} finally{
				s1.unlockRead(stamp);
			}
		}
		return Math.sqrt(currentx*currentx+currenty+currenty);
	}
}
此处把乐观锁升级成悲观锁,也可以像CAS操作一样在死循环中不断使用乐观锁
StampedLock的内部实现类似于CAS操作的死循环反复尝试策略,挂起线程时,使用的是Unsafe.park()函数,这个函数遇到线程中断时会直接返回,不会抛异常,导致堵塞在park()上的线程被中断后会再次进入循环,退出条件得不到满足,会疯狂占用CPU

没遇到疯狂占用CPU的情况

public class StampedLockCPUDemo {

	static Thread[]holdCpuThreads = new Thread[3];
	static final StampedLock lock = new StampedLock();
	public static void main(String[] args) throws InterruptedException {
		new Thread(){
			@Override
			public void run(){
				long readLong = lock.writeLock();
				LockSupport.parkNanos(600000000000000L);
				lock.unlockWrite(readLong);
			}
		}.start();
		Thread.sleep(100);
		for(int i=0;i<3;i++){
			holdCpuThreads[i]=new Thread(new HoldCPUReadThread());
			holdCpuThreads[i].start();
		}
		Thread.sleep(10000);
		//线程中断会占用CPU
		for(int i=0;i<3;i++){
			holdCpuThreads[i].interrupt();
		}
	}
	private static class HoldCPUReadThread implements Runnable{
		@Override
		public void run(){
			long lockr = lock.readLock();
			System.out.println(Thread.currentThread().getName()+"获取读锁");
			lock.unlockRead(lockr);
		}
	}
}
内部实现是基于CLH锁的,这是个自旋锁,保证没有饥饿发生,保证先进先出的顺序,锁维护等待线程队列,所有申请锁,但没有成功的线程都记录在这个队列中,每一个节点(线程)保存一个标记位(locked),用于判断当前线程是否已经释放锁,当一个线程试图获得锁时,取得当前等待队列的尾部节点作为其前序节点,使用如下代码判断前序节点是否已经成功释放锁

while (pred.locked){}

    /** Wait nodes */
    static final class WNode {
        volatile WNode prev;
        volatile WNode next;
        volatile WNode cowait;    // 读节点列表
        volatile Thread thread;   // 当可能被暂停时非空
        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;
WNode为链表的基本元素,每一个WNode表示一个等待线程,
private transient volatile long state;//当前锁的等待状态
倒数第八位表示写锁状态,该位为1,表示当前由写锁占用

乐观锁会执行如下操作

    public long tryOptimisticRead() {
        long s;
        return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;
    }
WBIT表示写锁状态位,值为0x80,成功,返回当前值,末尾7位清0,末尾7位表示当前正在读取的线程数量
乐观锁读后有线程申请写锁,state状态改变,设置写锁位为1,通过加上WBIT
    public long writeLock() {
        long s, next;  // bypass acquireWrite in fully unlocked case only
        return ((((s = state) & ABITS) == 0L &&
                 U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?
                next : acquireWrite(false, 0L));
    }

下述用于比较当前stamp和发生乐观锁时取得的stamp,不一致,乐观锁失败

    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
        return ((whead == wtail && (s & ABITS) < RFULL &&
                 U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ?
                next : acquireRead(false, 0L));
    }
悲观锁会设置state状态,将state加1,前提是读线程数量没有溢出,用于统计读线程数量,失败进入acquireRead()二次尝试锁读取,会自旋,通过CAS操作获取锁,失败,会启用CLH队列,将自己加入到队列,之后再进行自旋,获得读锁,进一步把自己cowait队列中的读线程全部激活(使用Unsafe.unpark()方法),依然无法获得读锁,使用Unsafe.unpark()挂起当前线程
acquireWrite类似acquireRead,释放锁与加锁相反

    public void unlockWrite(long stamp) {
        WNode h;
        if (state != stamp || (stamp & WBIT) == 0L)
            throw new IllegalMonitorStateException();
        state = (stamp += WBIT) == 0L ? ORIGIN : stamp;
        if ((h = whead) != null && h.status != 0)
            release(h);
    }
将写标记位清0,state溢出,退回初始值,等待队列不为空,则从等待队列中激活一个线程,大部分是第一个线程继续执行
原子类的增强
更快的原子类,LongAdder
在java.util.concurrent.atomic包下,使用CAS指令

AtomicInteger等是在一个死循环中,不断尝试修改目标值,直到修改成功,竞争不激烈,修改成功的概率很高,否则,失败的概率很高,失败会多次循环尝试,影响性能

竞争激烈提高性能

1、热点分离,将竞争的数据进行分解

可以减小锁粒度,仿照ConcurrentHashMap,将AtomicInteger的内部核心数据value分离成一个数组,每个线程访问时,通过hash等算法映射到其中一个数字进行计算,最终的结果就是这个数组的求和累加

LongAddr也是这种思想,但它不会一开始就使用数组,先将所有数据都记录在一个base变量中,多线程修改base不冲突,没必要拓展成cell数组,冲突,就会初始化cell数组,使用新策略,如果在cell上更新依然发生冲突,系统会尝试创建新的cell,或将cell翻倍,减少冲突

    public void increment() {
        add(1L);
    }
    public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        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);
        }
    }
开始cellsnull,数据会向base增加,如果对base操作冲突,设置冲突标记uncontented为true,接着如果cells数组不可用,,或者当前线程对应的cell为null,直接进入longAccumulate方法,否则会尝试使用CAS方法更新对应的cell数据,成功就退出,失败进入longAccumulate
public  class LongAdderDemo {

	private static final int MAX_THREADS=3;//线程数
	private static final int TASK_COUNT=3;//任务数
	private static final int TAEGET_COUNT=10000000;//目标总数
	
	private AtomicLong account = new AtomicLong(0L);//无锁的原子操作
	private LongAdder laccount = new LongAdder();//改进的无锁的原子操作
	private long count =0;
	static CountDownLatch cdlsync = new CountDownLatch(TASK_COUNT);
	static CountDownLatch cdlatomic = new CountDownLatch(TASK_COUNT);
	static CountDownLatch cdladdr = new CountDownLatch(TASK_COUNT);

	protected synchronized long inc(){//有锁的原子加法
		return count++;
	}
	protected synchronized long getCount(){//有锁的操作
		return count;
	}
	//有锁
	public class SyncThread implements Runnable{
		protected String name;
		private long starttime;
		LongAdderDemo out;	
		public SyncThread(long starttime, LongAdderDemo out) {
			this.starttime = starttime;
			this.out = out;
		}
		@Override
		public void run() {
			long v= out.getCount();
			while(v<TAEGET_COUNT){//到达目标值前不断循环
				v=out.inc();
			}
			long endTime = System.currentTimeMillis();
			System.out.println("SynchThread spend:"+(endTime-starttime)+"ms"+"v="+v);
			cdlsync.countDown();
		}
	}
	//无锁
	public class AtomicThread implements Runnable{
		protected String name;
		private long starttime;
		public AtomicThread(long starttime) {
			this.starttime = starttime;
		}
		@Override
		public void run() {
			long v= account.get();
			while(v<TAEGET_COUNT){//到达目标值前不断循环
				v=account.incrementAndGet();//无锁的加法
			}
			long endTime = System.currentTimeMillis();
			System.out.println("AtomicThread spend:"+(endTime-starttime)+"ms"+"v="+v);
			cdlsync.countDown();
		}
	}
	public class LongAddrThread implements Runnable{
		protected String name;
		private long starttime;
		public LongAddrThread(long starttime) {
			this.starttime = starttime;
		}
		@Override
		public void run() {
			long v= laccount.sum();
			while(v<TAEGET_COUNT){//到达目标值前不断循环
				laccount.increment();
				v=laccount.sum();
			}
			long endTime = System.currentTimeMillis();
			System.out.println("LongAddrThread spend:"+(endTime-starttime)+"ms"+"v="+v);
			cdladdr.countDown();
		}
	}
	public void testAtomicLong() throws InterruptedException{
		ExecutorService executorService = Executors.newFixedThreadPool(MAX_THREADS);
		long starttime = System.currentTimeMillis();
		LongAddrThread atomic = new LongAddrThread(starttime);
		for(int i=0;i<TASK_COUNT;i++){
			executorService.submit(atomic);
		}
		cdladdr.await();
		executorService.shutdown();
	}
	public void testAtomic() throws InterruptedException{
		ExecutorService executorService = Executors.newFixedThreadPool(MAX_THREADS);
		long starttime = System.currentTimeMillis();
		AtomicThread atomic = new AtomicThread(starttime);
		for(int i=0;i<TASK_COUNT;i++){
			executorService.submit(atomic);
		}
		cdlatomic.await();
		executorService.shutdown();
	}
	public void testSync() throws InterruptedException{
		ExecutorService executorService = Executors.newFixedThreadPool(MAX_THREADS);
		long starttime = System.currentTimeMillis();
		SyncThread sync = new SyncThread(starttime,this);
		for(int i=0;i<TASK_COUNT;i++){
			executorService.submit(sync);
		}
		cdlsync.await();
		executorService.shutdown();
	}
	public static void main(String[] args) throws InterruptedException {
		LongAdderDemo l1= new LongAdderDemo();
		LongAdderDemo l2= new LongAdderDemo();
		LongAdderDemo l3= new LongAdderDemo();
		l3.testAtomicLong();
		l1.testSync();
		l2.testAtomic();	
	}
}
查的是无锁原子最快,奇怪
解决伪共享

LongAddr的增强版,LongAccumulator

都将一个long型整数进行分割,储存在不同的变量中,防止多线程竞争

LongAddr只是每次对给定的整数执行一次加法,LongAccumulator可以实现任意函数操作

    public LongAccumulator(LongBinaryOperator accumulatorFunction,
                           long identity) {
        this.function = accumulatorFunction;
        base = this.identity = identity;
    }
第一个参数是需要执行的二元函数(接收俩个long型参数并返回long),第二个参数是初始值
public class LongAccumulatorDemo {
	//通过多线程访问若干整数,并返回最大值
	public static void main(String[] args) throws InterruptedException {
		LongAccumulator accumulator = new LongAccumulator(Long::max, Long.MIN_VALUE);
		Thread[] ts = new Thread[1000];
		for(int i=0;i<1000;i++){
			ts[i]=new Thread(()->{
				Random random = new Random();
				long value = random.nextLong();
				accumulator.accumulate(value);
			});
			ts[i].start();
		}
		for(int i=0;i<1000;i++){
			ts[i].join();
		}
		System.out.println(accumulator.longValue());
	}
}
accumulate传入数据,Long::max函数句柄识别最大值并保存在内部(可能是cell数组内,也可能是base),通过longValue()函数对所有的cell进行Long::max操作,得到最大值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值