Callable接口及线程池

线程池

线程容器,可设定线程分配的数量上限。

将预先创建线程对象存入池中,并重用线程池中的线程对象。

避免频繁的的创建和销毁。

常用的线程池接口和类

Executor :线程池的顶级接口

ExecutorService :线程池接口,可通过submit(Runnable task)提交任务代码。

Executors工厂类:通过此类可获得一个线程池。

​ 通过newFixedThreadPool(int nThreads)获取固定数量的线程池。

​ 通过newCachedThreadPool()获得动态数量的线程池,如果不够 则创建新的,没有上限。

public class TestThreadPool {
	public static void main(String[] args) {
		//线程池接口(引用)    --->     Executors工具类(工厂类)
		ExecutorService es = Executors.newFixedThreadPool(4);
		
		Runnable task = new MyTask();
		
		es.submit(task); //submit()提交任务
		es.submit(task);
		es.submit(task);
		es.submit(task);
	}
}
//创建接口实现类
class MyTask implements Runnable{
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + " - " +i);
		}
	}
}

Callable接口

JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。

有泛型返回值,可以声明异常。

Future接口

概念:

​ 异步接收ExecutorService.submit()所返回的状态结果,当中包含了call()的返回值。

方法:

​ V get()以阻塞形式等待Future的异步处理结果(call()的返回值)

使用

线程池执行Callable接口,Callable返回值由Future接收,Future当中的get() 方法就可以得到异步返回值。

可以解决并发下的一些统计工作。

public class TestCallable {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		System.out.println("程序开始");
		//创建线程池
		ExecutorService ex = Executors.newFixedThreadPool(3);
		//接口引用指向实现接口
		Callable<Integer> task1 = new MyTask1();
		Callable<Integer> task2 = new MyTask2();
		
		Future<Integer> f1 = ex.submit(task1);	//得到Callable线程接口的返回值
		Future<Integer> f2 = ex.submit(task2);
		
		Integer result1 = f1.get(); //以阻塞形式等待Future中的异步处理结果(call的返回值)
		Integer result2 = f2.get(); //在没有返回值以前,get无限期等待
		
		System.out.println(result1 + result2); //输出5050
	}
}
//Mytask1类遵从Callable接口实现call()方法
class MyTask1 implements Callable<Integer>{
	@Override
	public Integer call() throws Exception {
		Thread.sleep(1000); //休眠1秒,观察Future接口get方法
		Integer sum = 0;
		for (int i = 1; i <= 50; i++) {
			sum += i;
		}
		return sum; //返回计算的sum值
	}
}
//Mytask2类遵从Callable接口实现call()方法
class MyTask2 implements Callable<Integer>{
	@Override
	public Integer call() throws Exception {
		Thread.sleep(1000);
		Integer sum = 0;
		for (int i = 51; i <= 100; i++) {
			sum += i;
		}
		return sum; //返回计算的sum值
	}
}

同步和异步的区别:

同步:

形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。

异步:

形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行。

Lock接口

提供更多实用性的方法,功能更强大,性能更优越。

常用方法:

void lock() - 获取锁,如锁被占用,则等待。

boolean tryLock() - 尝试获取锁(成功返回true,失败返回false,不阻塞)

void unlock() - 释放锁

重入锁

ReentrantLock:Lock接口的实现类,与synchronized一样具有互斥锁功能。

Lock locker = new ReentrantLock(); //创建重入锁对象
locker.lock();	//开启锁
try{
    锁住代码块
}finally{
    //考虑可能会出现异常,释放锁必须放入finally代码块中,避免无法释放
    locker.unlock();	//释放锁
}

读写锁

ReentrantReadWriteLock:

  • 一种支持一写多读的同步锁,读写分离,可分别分配读锁,写锁。

  • 支持多次分配读锁,使多个读操作可以并发执行。

互斥规则:

写 - 写:互斥,阻塞。

读 - 写:互斥,读阻塞写,写阻塞读。

读 - 读:不互斥,不阻塞。

在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。

//读写锁
class Student {
    //创建读写锁对象
    ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    //创建读锁
    ReentrantReadWriteLock.ReadLock readLock =rwl.readLock();
    //创建写锁
    ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock();

    private int value;  //属性
    //读方法
    public int getValue() throws InterruptedException {
        readLock.lock();    //开启读锁
        try {
            Thread.sleep(1000); //休眠1秒,观察读的时间
            return value;
        } finally {
            readLock.unlock();  //关闭读锁
        }
    }
    //写方法
    public void setValue(int value) throws InterruptedException {
        writeLock.lock();   //开启写锁
        try {
            Thread.sleep(1000);
            this.value = value;
        } finally {
            writeLock.unlock(); //关闭写锁
        }
    }
}

最后总结:多线程相关的新旧对比

posted @ 2020-07-18 17:20  筋肉猩  阅读(368)  评论(0编辑  收藏  举报