Java个人学习之旅(第二十天)

多线程补充:

1.线程创建方式

1. 实现Callable接口

实现Callable接口对比实现Runnable接口的优点:

  • 可以有返回值
  • 可以抛出异常
  • 支持泛型的返回值
  • 需要借助FutureTask类,比如获取返回结果

Future接口:

说明:

  1. 可以取消的异步计算
  2. 一些方法:
    • cancel​(boolean mayInterruptIfRunning) 尝试取消执行此任务
    • done​() 此任务转换到状态 isDone (无论是正常还是通过取消)调用的受保护方法
    • get​() 等待计算完成,然后检索其结果
    • get​(long timeout, TimeUnit unit) 如果需要等待最多在给定的时间计算完成,然后检索其结果(如果可用)。
    • isCancelled​() 如果此任务在正常完成之前被取消,则返回 true
    • isDone​() 如果此任务完成,则返回 true
    • set​(V v) 将此Future的结果设置为给定值,除非此未来已被设置或已被取消

说明:
3. 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
4. FutrueTask是Futrue接口的唯一的实现类
5. FutureTask 同时实现了Runnable, Future接口。它既可以作为 Runnable被线程执行,又可以作为Future得到Callable的返回值

FutureTask类

说明:
可以取消的异步计算

一些方法:

  1. cancel​(boolean mayInterruptIfRunning) 尝试取消执行此任务
  2. done​() 此任务转换到状态 isDone (无论是正常还是通过取消)调用的受保护方法
  3. get​() 等待计算完成,然后检索其结果
  4. get​(long timeout, TimeUnit unit) 如果需要等待最多在给定的时间计算完成,然后检索其结果(如果可用)。
  5. isCancelled​() 如果此任务在正常完成之前被取消,则返回 true
  6. isDone​() 如果此任务完成,则返回 true
  7. set​(V v) 将此Future的结果设置为给定值,除非此未来已被设置或已被取消

代码示例,求10,8,5的阶乘:

class CalFaci implements Callable<Integer>{

	int num;

	public CalFaci(int num) {
		super();
		this.num = num;
	}

	@Override
	public Integer call() throws Exception {
		int sum = 1;
		
		for(int i=1;i <= num;i++){
			sum *= i;
		}
		return sum;
	}
}

public class CallableDemo1 {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		
		FutureTask<Integer>  ft1 = new FutureTask<Integer>(new CalFaci(10));
		FutureTask<Integer>  ft2 = new FutureTask<Integer>(new CalFaci(8));
		FutureTask<Integer>  ft3 = new FutureTask<Integer>(new CalFaci(5));
		
		new Thread(ft1).start();
		new Thread(ft2).start();
		new Thread(ft3).start();
		
		System.out.println(ft1.get());
		System.out.println(ft2.get());
		System.out.println(ft3.get());
		
	}
}

运行结果:
在这里插入图片描述

求从键盘输入的三个数中的最大值:

class MaxNumber implements Callable<Integer>{

	int num1;
	int num2;
	int num3;
	
	public MaxNumber(int num1, int num2, int num3) {
		super();
		this.num1 = num1;
		this.num2 = num2;
		this.num3 = num3;
	}

	@Override
	public Integer call() throws Exception {
		
		return (num1 > num2 ? (num1 > num3 ? num1 : num3) : (num2 > num3 ? num2 : num3));
	}	
} 

public class CallableDemo2 {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		
		Scanner sc = new Scanner(System.in);
		
		System.out.println("请输入三个数字,以回车键分隔");
		FutureTask<Integer> ft = new FutureTask<Integer>(new MaxNumber(sc.nextInt(),sc.nextInt(),sc.nextInt()));
		
		new Thread(ft).start();
		
		System.out.println(ft.get());
	}
}

在这里插入图片描述

2. 使用线程池

说明:
提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。

优点:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没任务时最多保持多长时间后会终止

ExecutorService接口:

  1. ExecutorService service = Executors.newFixedThreadPool​(int nThreads)

  2. submit​(Runnable task) 提交一个可运行的任务执行,并返回一个表示该任务的Future

  3. isShutdown​() 如果这个执行者已被关闭,则返回 true

  4. isTerminated​() 如果所有任务在关闭后完成,则返回 true

  5. shutdown​() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务

  6. shutdownNow​() 尝试停止所有主动执行的任务,停止等待任务的处理,并返回正在等待执行的任务列表

代码示例:
计算1-100的整数之和,计算1-200的整数之和:

class Add implements Runnable {

	int num;

	public Add(int num) {
		super();
		this.num = num;
	}

	@Override
	public void run() {
		int sum = 0;
		while (true) {
			if (num > 0) {
				sum += num--;
			} else {
				System.out.println(sum);
				break;
			}
		}
	}

}

public class PoolDemo1 {

	public static void main(String[] args) {
		//初始化线程池,线程容量为2
		ExecutorService service = Executors.newFixedThreadPool(2);

		Add a1 = new Add(100);
		Add a2 = new Add(200);

		service.submit(a1);
		service.submit(a2);
	}
}

运行结果:
在这里插入图片描述

3. ThreadLocal类

这个类提供线程局部变量。 这些变量与其正常的对应方式不同,因为访问一个的每个线程(通过其get或set方法)都有自己的独立初始化的变量副本

方法:

  1. set​(T value) 将当前线程的此线程局部变量的副本设置为指定的值。
  2. get​() 返回当前线程的此线程局部变量的副本中的值。
  3. remove​() 删除此线程局部变量的当前线程的值。
  4. initialValue​() 返回此线程局部变量的当前线程的“初始值”。
  5. withInitial​(Supplier<? extends S> supplier) 创建线程局部变量。

代码示例:

public class ThreadLocalDemo {
	public static void main(String[] args) {
		
		ThreadLocal<String> local = new ThreadLocal<String>();
		
		new Thread() {

			@Override
			public void run() {
				//设置值,该值只在当前线程有用
				local.set("你这瓜保熟吗?");
				
				try {
					//稍微睡一下,不急
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				//在当前线程访问local的值
				String str = local.get();
				//当前线程可以访问哇
				System.out.println(str);
			}
			
		}.start();
		
		try {
			Thread.sleep(1000);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		String str;
		//判断在main函数是否访问得到
		if (local.get() == null) {
			//Main访问不到哇
			str = "我还能卖你生瓜蛋子?";
		}else {
			str = "你故意找茬是吧?";
		}
		
		System.out.println(Thread.currentThread().getName() + "访问:" + str);
	}
}

运行结果:
在这里插入图片描述

4.线程安全与线程不安全的单例模式

  1. 线程不安全的单例模式(懒汉式):
class Singleton{
	
	//延时加载,懒汉式,线程不安全
	private static Singleton singleton = null;
	
	private Singleton(){
		
	}
	
	public static Singleton getInstance() {
		if (singleton == null) {
			singleton = new Singleton();
		}
		return singleton;
	}
}

//线程类
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++) {
			//线程内部获取实例化对象
			Singleton s = Singleton.getInstance();
			m.put(s, "a:" + i);
		}
		
		System.out.println(m);
	}
}

public class SingletonNoSafe {
	public static void main(String[] args) {
		
		for(int i=0;i < 100;i++){
			//不断启动线程去获取单例对象
			T t = new T();
			t.start();
		}
	}
}

这种写法是有可能获取两个不相同的对象的,就不符合单例模式的原则了

运行结果:
在这里插入图片描述

  1. 线程安全的单例模式(懒汉式):
class Singleton2 {
	// 延迟加载,懒汉模式.线程安全
	private static Singleton2 singleton = null;

	private Singleton2() {

	}

	//二重判断确保对象是单例的
	public static Singleton2 getInstance() {
		if (singleton == null) {
			//如果对象为空,则加锁
			synchronized (Singleton2.class) {
				//获得锁的线程再次判断对象是否为空
				if (singleton == null) {
					singleton = new Singleton2();
				}
			}
		}
		return singleton;
	}
}

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++) {
			//线程内部获取实例化对象
			Singleton2 s = Singleton2.getInstance();
			m.put(s, "a:" + i);
		}
		System.out.println(m);
	}
}

public class SingletonSafe {
	public static void main(String[] args) {
		for (int i = 0; i < 100; i++) {
			T2 t = new T2();
			t.start();
		}
	}
}

运行结果必定是只有一个相同对象的:
在这里插入图片描述

  1. 线程安全的单例模式(饿汉式)
class Singleton3{
	
	//即时加载,饿汉式,保证单例
	private static Singleton3 singleton = new Singleton3();
	
	private Singleton3() {
		
	}
	
	public static Singleton3 getInstance() {
		return singleton;
	}
}

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++) {
			//线程内部获取实例化对象
			Singleton3 s = Singleton3.getInstance();
			m.put(s, "a:" + i);
		}
		System.out.println(m);
	}
}

public class SingletonHungry {
	public static void main(String[] args) {
		for(int i=0;i < 100;i++){
			T3 t = new T3();
			t.start();
		}
	}
}

运行结果也只会返回相同的对象

在这里插入图片描述

多线程实现的方式有很多种,这里也没有记录完全,特定的场景会用到不同的线程类,还需到多多钻研

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值