写给大忙人看的 Java SE 8练习题(六)

本文提供了一系列关于Java SE 8并发编程的实践题目,涉及AtomicReference、LongAdder、ConcurrentHashMap等工具的使用,以及多线程场景下的性能比较和问题解决,包括计算最长字符串、生成递增ID、并发操作集合等挑战。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

系列文章:

写给大忙人看的 Java SE 8练习题(一)
写给大忙人看的 Java SE 8练习题(二)
写给大忙人看的 Java SE 8练习题(五)

代码地址:

代码地址

题目:

1、编写一个程序,跟踪一组线程中最长的字符串。你可以使用一个 AtomicReference 和一个合适的累加器。

public static void main(String[] args) {
	String[] list = new String[] { 
			"djkfs", 
			"weriew", 
			"vcnxbm", 
			"sdnwqer", 
			"odrteworoe", 
			"dfowe[er[w", 
			"pqwrj3221rmgbg,", 
			"owieroiewtw", 
			"ropweiotyer", 
			"oidshfdslfd"
	};
	AtomicReference<String> atomicReference = new AtomicReference<String>();
	BinaryOperator<String> operator = (first, second) -> {
		try {
			return first.length() > second.length() ? first : second;
		} catch (Exception e) {
			return first != null ? first : second;
		}
	};

	ExecutorService pools = Executors.newCachedThreadPool();
	Arrays.stream(list).forEach(item -> {
		pools.submit(() -> {
			atomicReference.accumulateAndGet(item, operator);
		});
	});
	try {
		Thread.sleep(1000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}

	System.out.println("the longest world is " + atomicReference.get());
}

2、LongAdder 对生成一个递增的 ID 序列有帮助吗?请说明原因。

没有帮助。LongAdder 是针对高并发场景而设计的,每个线程的数据存放于数组不同的下标位置,当需要总数时才调用 sum 接口计算结果,即 LongAdder 并不关心中间过程的结果。如果生成一个递增的 ID 序列,意味着每次加法都必须得到结果,那么就失去了 LongAdder 的意义。

3、生成 1000 个线程,每个线程会将计数器累加 100000 次,比较使用 AtomicLong 和 LongAdder 所获得的性能。

private static AtomicLong atomicLong = new AtomicLong();
private static LongAdder longAdder = new LongAdder();
	
public static void main(String[] args) {
	BiConsumer<String, Runnable> biConsumer = (item, runnable) -> {
		long begin = System.currentTimeMillis();
		ExecutorService pool = Executors.newCachedThreadPool();
		for (int i = 0; i < 1000; i++) {
			pool.submit(() -> {
				for (int j = 0; j < 100000; j++) {
					runnable.run();
				}
			});
		}
		long end = System.currentTimeMillis();
		System.out.println("item = " + item + ": " + (end - begin));
	};
		
	testAtomicLong(biConsumer);
	testLongAddder(biConsumer);
}
	
private static void testAtomicLong(BiConsumer<String, Runnable> item) {
	item.accept("testAtomicLong", () -> atomicLong.incrementAndGet());
}

private static void testLongAddder(BiConsumer<String, Runnable> item) {
	item.accept("testLongAddder", () -> longAdder.add(1));
}

4、使用 LongAccumulator 来计算累加元素的最大值或最小值。

public static void main(String[] args) throws InterruptedException {

	long[] list = new long[] { 
			1,
	 		32453465,
	 		65756, 
	 		3432, 
	 		65875, 
	 		2343245, 
	 		56765, 
	 		24354, 
	 		43654, 
	 		76, 
	 		8978, 
	 		43288888888883l, 
	 		9876666666666l 
	};
	LongAccumulator longAccumulator = new LongAccumulator(Math::max, 0);
	ExecutorService executorService = Executors.newCachedThreadPool();
	Arrays.stream(list).forEach(item -> {
		executorService.submit(() -> {
			longAccumulator.accumulate(item);
		});	
	});

	Thread.sleep(100);
	System.out.println(longAccumulator.get());
}

5、编写一个应用程序,使用多个线程来读取一组文件中的所有单词。使用 ConcurrentHashMap<String,Set< File>> 对象来跟踪每个单词都出现在哪些文件中。使用 merge 方法来更新 map。

private static ExecutorService pool = Executors.newCachedThreadPool();
private static ConcurrentHashMap<String, Set<File>> map = new ConcurrentHashMap<String, Set<File>>();

public static void main(String[] args) throws InterruptedException {
	getDirectory(new File("E:\\Documents\\Downloads\\SampleVideoPlayer"));
	Thread.sleep(5000);
	map.forEach((first, second) -> {
		second.stream().forEach(item -> {
			System.out.println("key = " + first + ", value = " + item
					.getName());
		});
	});
	System.out.println(map.size());
}

private static void getDirectory(File parent) {
	Optional.of(parent).ifPresent(item -> {
	if (item.isDirectory()) {
			File[] childs = item.listFiles();
			Arrays.stream(childs).forEach(child -> getDirectory(child));
		} else if (item.isFile()) {
			getFile(item);
		}
	});
}

private static void getFile(File parent) {
	pool.submit(() -> {
		Optional.of(parent)
				.ifPresent(item -> {
					if (item.isDirectory()) {
						return;
					}
					try {
						String content = new String(Files
								.readAllBytes(Paths.get(item
										.getCanonicalPath())), StandardCharsets.UTF_8);
						String[] contents = content.split("[\\P{L}]+");
						Arrays.stream(contents)
								.forEach(s -> {
									map.merge(s, new HashSet<File>(), (first, second) -> {
										second.add(item);
										first.addAll(second);
										return first;
									});
								});
					} catch (Exception e) {
						e.printStackTrace();
					}
				});
	});
}

6、重复上一个练习,但是这一次请使用 computeIfAbsent。这种方式的好处是什么?

不知道有什么好处。但使用这种方法,Set 里面只有一个值。

private static ExecutorService pool = Executors.newCachedThreadPool();
private static ConcurrentHashMap<String, Set<File>> map = new ConcurrentHashMap<String, Set<File>>();

public static void main(String[] args) throws InterruptedException {
	getDirectory(new File("E:\\Documents\\Downloads\\SampleVideoPlayer"));
	Thread.sleep(5000);
	map.forEach((first, second) -> {
		second.stream().forEach(item -> {
			System.out.println("key = " + first + ", value = " + item
					.getName());
		});
	});
		
	System.out.println(map.size());
}

private static void getDirectory(File parent) {
	Optional.of(parent).ifPresent(item -> {
		if (item.isDirectory()) {
			File[] childs = item.listFiles();
			Arrays.stream(childs).forEach(child -> getDirectory(child));
		} else if (item.isFile()) {
			getFile(item);
		}
	});
}

private static void getFile(File parent) {
	pool.submit(() -> {
		Optional.of(parent)
				.ifPresent(item -> {
					if (item.isDirectory()) {
						return;
					}
					try {
						String content = new String(Files
								.readAllBytes(Paths.get(item
										.getCanonicalPath())), StandardCharsets.UTF_8);
						String[] contents = content.split("[\\P{L}]+");
						Arrays.stream(contents)
								.forEach(s -> {
									map.computeIfAbsent(s, key -> {
										Set<File> result = new HashSet<File>();
										result.add(item);
										return result;
									});
								});
					} catch (Exception e) {
						e.printStackTrace();
					}
				});
	});
}

7、在一个 ConcurrentHashMap<String,Long> 中,找到含有最大值的键(可以自由发挥)。提示:reduceEntries。

public static void main(String[] args) {
	ConcurrentHashMap<String, Long> map = new ConcurrentHashMap<String, Long>();
	map.put("dsfhk", 3245l);
	map.put("dsfhfdgklk", 567l);
	map.put("dsfhdfgfdk", 123l);
	map.put("dsfjhkhlwerhk", 7654l);
	map.put("dswerwefhk", 2345l);
	map.put("dsfhkjhklk", 9976l);
	map.put("dsfcxzcxchk", 6786l);
	map.put("dwtiuertyusfhk", 234l);
	map.put("dsfhwerwerk", 067554l);
	map.put("dsdfgdfhk", 905433l);
	map.put("dsfzcxzvchk", 978032421111111113l);
	map.put("dsvczfhk", 5679343l);
	map.put("dsxvczzhk", 6587670l);
	map.put("dsasafhk", 890543l);
	map.put("dsfasdhk", 324765l);
		
	System.out.println(max(map));
}
	
private static Map.Entry<String, Long> max(ConcurrentHashMap<String, Long> map) {
	return map.reduceEntries(5, item -> {
		return item;
	}, (first, second) -> {
		return first.getValue() > second.getValue() ? first : second;
	});
}

8、在你的计算机上使用多大的数组才能让 Arrays.parallelSort() 的运算速度比 Arrays.sort() 更快。

在我的机器中,Arrays.sort() 一直比 Arrays.parallelSort() 快。

public static void main(String[] args) {
	int[] a = new int[100000000];
	for (int i = 0; i < a.length; i++) {
		a[i] = 100000000 - i;
	}
	long begin = System.currentTimeMillis();
	Arrays.sort(a);
//	Arrays.parallelSort(a);
	long end = System.currentTimeMillis();
	
	System.out.println("time = " + (end - begin));
}

9、你可以使用 parallelPrefix 方法来并行计算斐波那契数字。我们假设第 n 个斐波那契数字是 F n F^n Fn左上角的系数,其中 F = [ 1 1 1 0 ] F = \begin{bmatrix} {1}&amp;{1}\\ {1}&amp;{0}\\ \end{bmatrix} F=[1110]构造一个 2 * 2 矩阵的数组。定义一个含有乘法方法的 Matrix 类,使用 parallelSetAll 方法来构造矩阵数组,并使用 parallelPrefix 方法将它们相乘起来。

public static void main(String[] args) {

	Matrix[] result = new Matrix[10];
	Arrays.parallelSetAll(result, i -> new Matrix());
	Matrix.parallelPrefix(result);
	// 注意
	// |f(n+1), f(n)|
	// |f(n), f(n-1)|
	System.out.println("1");
	Arrays.stream(result).forEach(item -> {
		System.out.println(item.result[0][0]);
	});
}

static class Matrix {
	long[][] result = new long[][] { { 1, 1 }, { 1, 0 } };

	static void parallelPrefix(Matrix[] data) {
		Arrays.parallelPrefix(data, (first, second) -> {
			second.result[0][0] = second.result[0][0] * first.result[0][0] + second.result[0][1] * first.result[1][0];
			second.result[0][1] = second.result[0][0] * first.result[1][0] + second.result[1][0] * first.result[1][1];
			second.result[1][0] = second.result[1][0] * first.result[0][0] + second.result[1][1] * first.result[1][0];
			second.result[1][1] = second.result[1][0] * first.result[0][1] + second.result[1][1] * first.result[1][1];
			return second;
		});
	}
}

10、编写一个程序,要求用户提供一个 URL,然后读取该 URL 的 Web 页面,并显示页面中所有的超链接。可以为每个阶段使用一个 CompletableFuture 对象。不要调用 get 方法。为了防止你的程序过早借宿,可以调用下面的函数ForkJoinPool.commonPool().awaitQuiescence(10,TimeUnit.SECONDS);

不想做。

11、编写一个方法:public static <T> CompletableFuture<T> repeqt(Supplier<T> action, Predicate<T> until),它会异步重复执行 action 函数,知道产生一个可以被 until 函数(同样也是异步的)接受的值为止。你需要使用一个从控制台读取 java.net.PasswordAuthentication 的函数,以及一个模拟验证过程的函数来执行测试,其中验证模拟函数需要先睡眠 1 秒,然后再检查密码是否为“secert”。提示:使用递归。

public static void main(String[] args) {

	repeat(() -> {
		System.out.println("请输入密码:");
		String result = null;
		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
		try {
			result = bufferedReader.readLine();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}, item -> {
		return "secret".equals(item);
	});
		
}

private static <T> CompletableFuture<T> repeat(Supplier<T> action, Predicate<T> until) {
	CompletableFuture<T> result = CompletableFuture.supplyAsync(action);
	result.thenAcceptAsync(t -> {
		try {
			Thread.sleep(1000);
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (until.test(t)) {
			System.out.println("密码正确");
		} else {
			System.out.println("密码错误,请重试。");
			repeat(action, until);
		}
	});
	ForkJoinPool.commonPool().awaitQuiescence(4, TimeUnit.SECONDS);
	return result;
}
目录 第1章 lambda表达式 0 1.1 为什么要使用lambda表达式 2 1.2 lambda表达式的语法 4 1.3 函数式接口 6 1.4 方法引用 8 1.5 构造器引用 10 1.6 变量作用域 10 1.7 默认方法 14 1.8 接口中的静态方法 17 练习 18 第2章 Stream API 20 2.1 从迭代器到Stream操作 22 2.2 创建Stream 23 2.3 filter、map和flatMap方法 25 2.4 提取子流和组合流 26 2.5 有状态的转换 27 2.6 简单的聚合方法 28 2.7 Optional类型 29 2.7.1 使用Optional值 29 2.7.2 创建可选值 30 2.7.3 使用flatMap来组合可选值函数 31 2.8 聚合操作 32 2.9 收集结果 33 2.10 将结果收集到Map中 35 2.11 分组和分片 37 2.12 原始类型流 40 2.13 并行流 42 2.14 函数式接口 44 练习 45 第3章使用lambda编程 48 3.1 延迟执行 50 3.2 lambda表达式的参数 51 3.3 选择一个函数式接口 52 3.4 返回函数 55 3.5 组合 56 3.6 延迟 58 3.7 并行操作 59 3.8 处理异常 60 3.9 lambda表达式和泛型 63 3.10 一元操作 65 练习 67 第4章 JavaFX 72 4.1 Java GUI编程简史 74 4.2 你好,JavaFX! 75 4.3 事件处理 76 4.4 JavaFX属性 77 4.5 绑定 80 4.6 布局 85 4.7 FXML 91 4.8 CSS 95 4.9 动画和特殊效果 97 4.10 不寻常的控件 100 练习 103 第5章新的日期和时间API 106 5.1 时间线 108 5.2 本地日期 110 5.3 日期校正器 113 5.4 本地时间 114 5.5 带时区的时间 115 5.6 格式化和解析 119 5.7 与遗留代码互操作 122 练习 123 第6章并发增强 126 6.1 原子值 128 6.2 ConcurrentHashMap改进 131 6.2.1 更新值 132 6.2.2 批量数据操作 134 6.2.3 Set视图 136 6.3 并行数组操作 137 6.4 可完成的Future 138 6.4.1 Future 138 6.4.2 编写Future 139 6.4.3 Future流水线 139 6.4.4 编写异步操作 141 练习 143 第7章 JavaScript引擎——Nashorn 146 7.1 从命令行运行Nashorn 148 7.2 从Java运行Nashorn 149 7.3 调用方法 150 7.4 构造对象 151 7.5 字符串 153 7.6 数字 153 7.7 使用数组 154 7.8 列表和映射 155 7.9 lambda表达式 156 7.10 继承Java类及实现Java接口 157 7.11 异常 158 7.12 Shell脚本 159 7.12.1 执行Shell命令 159 7.12.2 字符串插值 160 7.12.3 脚本输入 161 7.13 Nashorn和JavaFX 162 练习 164 第8章杂项改进 166 8.1 字符串 168 8.2 数字类 168 8.3 新的数学函数 169 8.4 集合 170 8.4.1 集合类中添加的方法 170 8.4.2 比较器 171 8.4.3 Collections类 173 8.5 使用文件 173 8.5.1 读取文件行的流 173 8.5.2 遍历目录项的流 175 8.5.3 Base64编码 176 8.6 注解 177 8.6.1 可重复的注解 177 8.6.2 可用于类型的注解 179 8.6.3 方法参数反射 181 8.7 其他一些细微的改进 182 8.7.1 Null检查 182 8.7.2 延迟消息 182 8.7.3 正则表达式 183 8.7.4 语言环境 183 8.7.5 JDBC 185 练习 185 第9章你可能错过的Java 7特性 188 9.1 异常处理改进 190 9.1.1 try-with-resources语句 190 9.1.2 忽略异常 191 9.1.3 捕获多个异常 192 9.1.4 更简单地处理反射方法的异常 193 9.2 使用文件 193 9.2.1 Path 194 9.2.2 读取和写入文件 196 9.2.3 创建文件和目录 197 9.2.4
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值