学习Java第三十天--多线程之线程池和同步锁

半城伤御伤魂 提交于 2020-03-16 18:21:05

14.4 线程池

14.4.1 线程池概念

现有问题:

  • 线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出;
  • 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降;

线程池:

  • 线程容器,可设定线程分配的数量上限;
  • 将预先创建的线程对象存入池中,并重用线程池中的线程对象 避免频繁的创建和销毁;

14.4.2 线程池原理

在这里插入图片描述

  • 将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程;

14.4.3 获取线程池

常用的线程池接口和类(所在包java.util.concurrent)

  • Executor:线程池的顶级接口
  • ExecutorService:线程池接口,可通过submit(Runnable task)提交任务代码;
  • Executors工厂类:通过此类可以获得一个线程池;
  • 通过newFixedThreadPool(int nThreads) 获取固定数量的线程池。参数:指定线程池中线程的数量;
  • 通过newCachedThreadPool() 获得动态数量的线程池,如不够则创建新的,没有上限;

14.4.4 Callable接口

public interface Callable<V>{
	public V call() throws Exception;
}
  • JDK5加入,与Runnable接口类似,实现之后代表一个线程任务;
  • Callable具有泛型返回值、可以声明异常;

14.4.5 Future接口

  • 概念:异步接收ExecutorService.submit() 所返回的状态结果,当中包含了call()的返回值;
  • 方法:V get() 以阻塞形式等待Future中的异步处理结果(call()的返回值)

14.4.6 线程的同步

同步:

  • 形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续;
    在这里插入图片描述
  • 注:单条执行路径;

14.4.7 线程的异步

异步:

  • 形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行;
    在这里插入图片描述
  • 注:多条执行路径;

14.4.8 Lock接口

  • JKD5加入,与synchronized比较,显示定义,结构更灵活;
  • 提供更多实用性方法,功能更强大、性能更优越;
  • 常用方法:
    void lock() //获取锁,如锁被占用,则等待;
    boolean tryLock() //尝试获取锁(成功返回true。失败返回false,不阻塞)
    void unlock() //释放锁

14.4.8 重入锁

  • ReentrantLock:Lock接口的实现类,与synchronized一样具有互斥锁功能;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestLocks {

	public static void main(String[] args) {
		Test obj = new Test();
		Thread t1 = new Thread(new MyTask(obj));
		Thread t2 = new Thread(new MyTask2(obj));
		
		t1.start();
		t2.start();
	}
}
class Test{
	//接口引用指向实现类对象  
	Lock lock = new ReentrantLock();
	
	//1.使用Lock,需要明确的写上锁和释放锁
	//2.为了避免拿到锁的线程在运行期间出现异常,导致程序终止,没有释放锁,应用try{}finally{}来保证无论执行正确与否,最终都会释放锁
	public void method(){
		System.out.println(Thread.currentThread().getName()+"进入到上锁的方法里");
		try {
			lock.lock();//显示的协商在此次获得锁
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}finally {
			System.out.println(Thread.currentThread().getName()+"退出了上锁的方法");
			lock.unlock();//释放此处锁
		}
	}
}
class MyTask implements Runnable{
	Test obj;
	public MyTask(Test obj) {
		this.obj = obj;
	}
	public void run() {
		obj.method();
	}
}
class MyTask2 implements Runnable{
	Test obj;
	public MyTask2(Test obj) {
		this.obj = obj;
	}
	public void run() {
		obj.method();
	}
}

输出结果:

Thread-0进入到上锁的方法里
Thread-1进入到上锁的方法里
Thread-0退出了上锁的方法
Thread-1退出了上锁的方法

14.4.9 读写锁

  • ReentrantReadWriteLock:
    一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁;
    支持多次分配读锁,使多个读操作可以并发执行;
  • 互斥规则:
    写-写:互斥、阻塞;
    读-写:互斥,读阻塞写、写阻塞读;
    读-读:不互斥、不阻塞;
    在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率;

14.4.10 ReentrantReadWriteLock

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

public class TestReadWroteLock {

	public static void main(String[] args) {
		Student stu = new Student();//共享资源对象
		
		ExecutorService es = Executors.newFixedThreadPool(20);
				
		WriteTask1 write = new WriteTask1(stu);//写线程任务
		ReadTask read = new ReadTask(stu);//读线程任务
		
		//执行的两个赋值的线程任务
		long start = System.currentTimeMillis();//获取开始时间,毫秒值
		es.submit(write);
		es.submit(write);
		
		//执行两个读线程任务
		for(int i = 0 ; i < 10 ; i++) {
			es.submit(read);
		}
		
		//停止线程池,但不停止已提交的任务,等已提交任务都执行完毕
		es.shutdown();
		
		//询问线程池任务结束了吗
		while(true) {
			if(es.isTerminated() == true) {//任务都执行完毕
				System.out.println("任务执行完毕");
				break;
			}
		}
		long end = System.currentTimeMillis();//结束时间
		System.out.println(end - start+"毫秒");
	}

}
class WriteTask1 implements Callable{
	Student stu;
	public WriteTask1(Student stu) {
		this.stu = stu;
	}
	public Object call() throws Exception{
		stu.setAge(100);
		return null;
	}
}
class ReadTask implements Callable{
	Student stu;
	public ReadTask(Student stu) {
		this.stu = stu;
	}
	public Integer call() throws Exception{
		return stu.getAge();
	}
}
class Student{
	private int age;
//	Lock lock = new ReentrantLock();//写和读的操作都加锁,效率过低
	ReentrantReadWriteLock rrw1 = new ReentrantReadWriteLock();//有两把锁
	ReadLock readlock = rrw1.readLock();//读锁
	WriteLock writelock = rrw1.writeLock();//写锁
	//赋值--写操作
	public void setAge(int age) {
		writelock.lock();
		try {
			Thread.sleep(1000);//每写入一次,休眠1秒
			this.age = age;
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally {
			writelock.unlock();
		}
	}
	
	//取值--读操作
	public int getAge() {
		readlock.lock();
		try {
			try {
				Thread.sleep(1000);//每写入一次,休眠1秒
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return this.age;
		}finally {
			readlock.unlock();
		}
	}
}

输出结果:

任务执行完毕
3016毫秒
  • 运行结果:互斥锁运行时间20096毫秒,读写锁运行时间3016毫秒。(2次写各占1秒,18次读共占1秒)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!