Callable和Future创建线程(可以有返回值的线程)
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。 (2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。 (3)使用FutureTask对象作为Thread对象的target创建并启动新线程。 (4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
案例: 习题1:利用Callable和FutureTask完成 10! 8! 5! 类JieChengDemo
package com.gec.练习题;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
class JieCheng implements Callable<Integer>{
private int mun;
public JieCheng(int mun) {
super();
this.mun = mun;
}
@Override
public Integer call() throws Exception {
if(mun == 0 && mun ==1) {
int mun;
}
int mun1=1;
for(int i = 1 ;i<=mun;i++) {
mun1*=i;
}
return mun1;
}
}
public class JieChengDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
JieCheng jie = new JieCheng(10);
JieCheng jie2 = new JieCheng(8);
JieCheng jie3 = new JieCheng(5);
FutureTask<Integer> f = new FutureTask<Integer>(jie);
FutureTask<Integer> f2 = new FutureTask<Integer>(jie2);
FutureTask<Integer> f3 = new FutureTask<Integer>(jie3);
Thread t = new Thread(f);
Thread t2 = new Thread(f2);
Thread t3 = new Thread(f3);
t.start();
t2.start();
t3.start();
Integer num = f.get();
Integer num2 = f2.get();
Integer num3 = f3.get();
System.out.println("10阶乘为:"+num);
System.out.println("8阶乘为:"+num2);
System.out.println("5阶乘为:"+num3);
}
}
运行结果: 注意:要创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
线程池
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。
我们通过一张图来了解线程池的工作原理: 好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
两个习题案例: 1.Callable和FutureTask完成 求 输入的3个数字,返回其中最大的一个!
package com.gec.练习题;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
class Max implements Callable<Integer>{
private int a,b,c;
public Max(int a, int b, int c) {
super();
this.a = a;
this.b = b;
this.c = c;
}
@Override
public Integer call() throws Exception {
int max = a>b ? a:b;
int max2 = max>c ? max:c;
return max2;
}
}
public class MaxDemo {
static Scanner sc = new Scanner(System.in);
public static void main(String[] args) throws InterruptedException, ExecutionException {
System.out.println("请输入三个数,找出最大值:");
int a = sc.nextInt();
int b = sc.nextInt();
int c = sc.nextInt();
Max m = new Max(a,b,c);
ExecutorService service = Executors.newFixedThreadPool(2);
Future<Integer> f = service.submit(m);
System.out.println("最大值为:"+f.get());
}
}
运行结果:
- 用线程池完成 1+2+3+… +100 1+2+3+…+200
package com.gec.练习题;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
class LeiJia implements Callable<Integer>{
private int num;
public LeiJia(int num) {
super();
this.num = num;
}
@Override
public Integer call() throws Exception {
int n = 0;
for(int i= 1;i <= num;i++) {
n+=i;
}
return n;
}
}
public class LeiJiaDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService lei = Executors.newFixedThreadPool(3);
LeiJia j = new LeiJia(100);
LeiJia j2 = new LeiJia(200);
Future<Integer> f = lei.submit(j);
Future<Integer> f1 = lei.submit(j2);
Integer sum1 = f.get();
Integer sum2 = f1.get();
System.out.println("1+2+3+...+100的值:" + sum1);
System.out.println("1+2+3+...+200的值:" + sum2);
}
}
运行结果:
ForkJoinPool
ava7 提供了ForkJoinPool来支持将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果。
ForkJoinPool是ExecutorService的实现类,因此是一种特殊的线程池。
使用方法:创建了ForkJoinPool实例之后,就可以调用ForkJoinPool的submit(ForkJoinTask task) 或invoke(ForkJoinTask task)方法来执行指定任务了。
下面的UML类图显示了ForkJoinPool、ForkJoinTask之间的关系: 案例: 无返回值实战——通过多线程分多个小任务进行打印数据
package com.gec.练习题;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;
class PrintDemo extends RecursiveAction{
public static final int FIFTY = 50;
private int start;
private int end;
public PrintDemo(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if(end - start <= FIFTY) {
for(int i = start;i <end;i++) {
System.out.println(Thread.currentThread().getName() + "i的值:" + i);
}
}else {
int middle = (end + start)/2;
PrintDemo left = new PrintDemo(start,middle);
PrintDemo right = new PrintDemo(middle, end);
left.fork();
right.fork();
}
}
}
public class ForkJoinPoolDemo2 {
public static void main(String[] args) throws InterruptedException {
ForkJoinPool fjp = new ForkJoinPool(32767);
fjp.submit(new PrintDemo(0, 300));
fjp.awaitTermination(2, TimeUnit.SECONDS);
}
}
运行结果:
ThreadLocal
ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:
因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。 ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景
下图可以增强理解: 案例: ThreadLocal的简单使用
package com.gec.练习题;
public class ThreadLocalDemo {
public static void main(String[] args) {
ThreadLocal<String> local = new ThreadLocal<>();
new Thread() {
@Override
public void run() {
local.set("天生我材必有用");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String str = local.get();
System.out.println("str:" + str);
}
}.start();
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",访问 local:" + local.get());
}
}
运行结果: ThreadLocal类的用法非常简单,它只提供了如下3个public方法。 T get():返回此线程局部变量中当前线程副本中的值。 void remove():删除此线程局部变量中当前线程的值。 void set(T value):设置此线程局部变量中当前线程副本中的值。 网上经常说:这是一个线程安全的变量。这是错的!
经典例题:生产者/消费者问题
package com.gec.练习题;
import java.util.ArrayList;
import java.util.List;
public class ProductCusmerDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
long pro = System.currentTimeMillis();
Thread t1 = new Thread() {
@Override
public void run() {
int num = 0;
while(System.currentTimeMillis() - pro < 100) {
synchronized (list) {
if(list.size() > 0) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
list.add("荔枝"+ ++num);
System.out.println(Thread.currentThread().getName() + "正在生产" + "荔枝" + num);
}
}
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
int num = 0;
while(System.currentTimeMillis() - pro < 100) {
synchronized (list) {
if(list.size() == 0) {
list.notify();
}else {
list.remove("荔枝"+ ++num);
System.out.println(Thread.currentThread().getName() + "正在吃" + "荔枝" + num);
}
}
}
}
};
t1.setName("生产者");
t1.start();
t2.setName("消费者");
t2.start();
}
}
运行结果:
单列集合
Vector 有序,可重复,允许null,查询速度快,插入慢,底层用数组实现,扩容是当前容量的一倍,因为同步,比arraylist,linkedlist更慢了 双列集合:HashTable k-v 键值对,因为同步,key不重复,所以操作的效率低 线程安全案例:
package com.gec.线程安全的集合;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
public class XianChenAnQiangDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
List<String> list2 = Collections.synchronizedList(list);
for (int i = 0; i < 100; i++) {
new Thread() {
@Override
public void run() {
list2.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list2);
}
}.start();
}
}
}
运行结果: 线程不安全案例:
package com.gec.线程安全的集合;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
public class XianChenBuAnQiangDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
new Thread() {
@Override
public void run() {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}
}.start();
}
}
}
运行结果: 可以明显看到红色区域报错,代表了线程不安全。 我们一般情况可以查看对象的源代码有没有synchronized同步 比如: 注意:synchronizedListd对象源代码就有synchronized同步
单例
在一个应用,由始至终只有一个对象存在,这就是单例。 懒汉式:
package com.gec.单例;
import java.util.HashMap;
import java.util.Map;
class LanHanShi{
private LanHanShi() {};
private static LanHanShi lhs;
public static LanHanShi getlnstance() {
if(lhs == null) {
synchronized (LanHanShi.class) {
if(lhs == null) {
lhs = new LanHanShi();
}
}
}
return lhs;
}
}
class T extends Thread{
static Map m = new HashMap();
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0 ;i<100;i++) {
LanHanShi lhs = LanHanShi.getlnstance();
m.put(lhs, "a:"+i);
}
System.out.println(m);
}
}
public class LanHanShiDemo {
public static void main(String[] args) {
for(int i = 0 ; i<110;i++) {
T t = new T();
t.start();
}
}
}
运行结果: 饿汉式:
package com.gec.单例;
import java.util.HashMap;
import java.util.Map;
class S2{
private S2() {}
private static S2 s2 = new S2();
public static S2 getInstance() {
return s2;
}
}
class T2 extends Thread{
static Map m = new HashMap();
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
S2 s = S2.getInstance();
m.put(s, "a:" + i);
}
System.out.println(m);
}
}
public class EHanShi {
public static void main(String[] args) {
for(int i = 0;i<110;i++) {
T t = new T();
t.start();
}
}
}
运行结果: 饿汉式和懒汉式最明显的区别是饿汉式以上来就给自己产生实例
利用内部类产生的实例
package com.gec.单例;
import java.util.HashMap;
import java.util.Map;
class S3{
private S3() {}
private static class S3Holder{
private static final S3 s3 = new S3();
}
public static S3 getInstance() {
return S3Holder.s3;
}
}
class T3 extends Thread{
static Map m = new HashMap();
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
S3 s = S3.getInstance();
m.put(s, "a:" + i);
}
System.out.println(m);
}
}
public class NeBuLeiDemo {
public static void main(String[] args) {
for(int i = 0;i<110;i++) {
T t = new T();
t.start();
}
}
}
队列
队列的简单用法:
package com.gec.队列;
import java.util.concurrent.LinkedBlockingQueue;
public class DuiLieDemo {
public static void main(String[] args) {
LinkedBlockingQueue lbk = new LinkedBlockingQueue(3);
System.out.println("empty:" + lbk.isEmpty());
System.out.println("size:" + lbk.size());
System.out.println("包含:" + lbk.contains("a"));
System.out.println("容量:" + lbk.remainingCapacity());
lbk.add(3);
lbk.add(2);
lbk.add("hello");
boolean insertResult = lbk.offer(33);
System.out.println(insertResult == true ?"插入成功":"插入失败");
System.out.println("lbk移除前:" + lbk);
lbk.remove();
System.out.println("lbk移除后:" + lbk);
System.out.println("第一个值:" + lbk.peek());
System.out.println("第一个值:" + lbk.peek());
System.out.println("查找并删除第一个:" + lbk.poll());
System.out.println("查找并删除:" + lbk.poll());
System.out.println("尝试消费没有的元素:" + lbk.poll());
}
}
运行结果:
方法的使用: add 增加一个元素 如果队列已满,则抛出一个IllegalSlabEepeplian异常 remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常 element 返回队列头部的元素 如果队列为空,则抛出一个 NoSuchElementException异常 offer 添加一个元素并返回true 如果队列已满,则返回false poll 移除并返问队列头部的元素 如果队列为空,则返回null peek 返回队列头部的元素 如果队列为空,则返回null put 添加一个元素 如果队列满,则阻塞 take 移除并返回队列头部的元素 如果队列为空,则阻塞
第一次写这么多可能不够详细,可以下载我的笔记看看 https://download.csdn.net/download/weixin_45523942/20817715 参考 https://blog.csdn.net/u010445301/article/details/111322569 https://blog.csdn.net/chengqiuming/article/details/90736235
|