Java —— 多线程笔记 三、线程通信 与 线程组、线程异常

来源:互联网 发布:上海大剧院 座位 知乎 编辑:程序博客网 时间:2024/05/18 02:23

一、线程通信

1、传统线程通信(wait()、notify()、notifyAll() 配合 synchronized)

基础介绍:

wait()、notify()、notifyAll()三个方法属于Object 类,而非Thread 类。这三个方法必须由同步监视器对象调用,其作用是使获得当前同步监视器的线程等待(wait,会释放同步锁),或唤醒(notify、notifyAll)在该同步监视器上等待的线程。notify()方法只唤醒当个线程,当有多个线程在此监视器上等待时,随机唤醒其中一个,而notifyAll()方法会唤醒在此监视器上等待的所有线程,所有线程被唤醒后随机竞争CPU资源。

理解同步监视器:

(1)、同步监视器是类的实例,而不是类;

(2)、同步监视器的作用是使操作该同步监视器的线程,同一时刻只有一个;

(3)、对于同步方法,同步监视器为类的实例;对于同步代码块,同步监视器为synchronized 关键字后括号内的对象。

监视器使用:

对于同一时刻只能有一个线程操作的方法或对象,将方法定义为同步的(此时该方法所在类的实例作为监视器),将对象作为同步监视器(synchronized 同步块)。

wait、notify、notifyAll方法使用:

监视器的使用解决了同步访问某对象或某对象方法的问题,但未解决多线程工作的逻辑先后问题,而wait、notify、notifyAll正是解决此问题的。如存取钱,若存钱(线程A)与取钱(线程B)分别为一个线程,操作的是同一个账户(Account类的对象),则规定同一时刻取钱与存钱不能同时进行,则可将Account 类的存取钱方法定义为synchronized 的(此时Account 的实例作为监视器),或将Account 的对象在存取钱同步块中作为同步监视器。当还未存钱时,可以设置flag先让取钱线程wait,检测到存钱后,再对其进行notify。

Account 类实例作为同步监视器:

class Account{public synchronized boolean saveMoney(){//存}public synchronized boolean fetchMoney(){//取}}
直接在外部的存取钱方法中将Account 对象作为监视器:

//account 为某个账户synchronized(account){//对账户进行操作}

当然,synchronized 的功能也可用Lock 锁接口的实现类实现。


2、使用Condition 控制线程通信

基础介绍:

算是对第1点的补充,当不使用synchronized 达到同步目的,而是使用Lock 实现时,这此处不存在隐式的同步监视器,便不能通过监视器调用wait、notify、notifyAll方法达到通信目的,这时便可以使用Condition 类来协调。

Condition 类与Lock 相关类定义再同一个包,基础了解如下:

Lock lock = new ReentrantLock();Condition condition = lock.newCondition();condition.await();//相当于wait()condition.signal();//相当于notify()condition.signalAll();//相当于notifyAll()

理解:

相当于用Lock 替换synchronized 的功能,对应的用Condition 对象替换隐式同步机监视器。


3、使用阻塞队列(Blocking Queue)

基础介绍:

Java 5 提供了一个BlockingQueue 接口,它是Queue 接口的子接口,但其主要用途并不是作为容器,而是作为线程同步工具(java本身已经提供了几个该接口的不同方式的实现类)。控制线程同步的两个方法:

put(E e):尝试把元素放入BlockingQueue 中,如果队列已满,则阻塞当前线程;

take():尝试从队首取出元素,如果队列已空,则同一阻塞线程。

BockingQueue 接口还包含的方法分组:

队尾插入元素:add(抛出异常)、offer(不同返回值)、put(阻塞线程)、offer(指定超时时长)

队首删除元素:remove(抛出异常)、poll(不同返回值)、take(阻塞线程)、poll(指定超时时长)

获取但不删除元素:element(抛出异常)、peek(不同返回值)



二、线程组和未处理的异常

1、线程组

线程组(ThreadGroup 类)用于对一批线程进行分类管理。程序可直接对线程组进行控制,相当于控制该线程组内的所有线程。当创建的线程没有显示指定所属线程组时,其所在线程组为创建它的线程所在线程组,且存在默认线程组。

Thread 对象创建时显示指定其线程组:

Thread(ThreadGroup group,Runnable target);

Thread(ThreadGroup group,Runnable target,String threadName);

Thread(ThreadGroup group,String threadName)。


2、未处理的异常

ThreadGroup 定义了一个方法:

void uncaughtException(Thread t,Throwable e),用于处理该线程组内所有线程抛出的未处理的异常。

Thread 类也提供了设置异常处理器的方法:

static setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):为该线程类的所有线程实例设置未处理异常处理器。

setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):为指定线程实例设置未处理异常处理器。

未处理异常处理优先级(没有则查找下一个):

该线程的未处理异常处理器——>该线程类默认的未处理异常处理器——>如果属于某线程组,使用该组的uncaughtException方法处理未处理的异常。



三、线程池

线程池可有效地控制系统中并发线程的数量,以防并发线程太多导致性能剧烈下降甚至导致JVM 崩溃。

Java 5开始,提供了Executors 工程类来产生线程池,该工厂类包含如下几个静态方法用于创建线程池:

1、newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将被缓存在线程池中;

2、newFixedTheadPool(int nThreads):创建可重用的、固定线程数的线程池;

3、newSingleThreadExecutor():相当于调用上一个方法,传入参数1;

4、newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。参数为池中所保存的线程数目,即使线程是空闲时也被保存在线程池中(使用方式上,似乎和上面几种没多大差别);

5、newScheduledThreadExecutor():相当于上一个方法,传入参数1;

6、newWorkStealingPool(int parallelism):创建持有足够的线程数的线程池来支持给定的并行级别,该方法还会使用多个队列来减少竞争(Java 8新增);

7、newWorkStealingPool():上一个方法的简化版。假设当前机器有4个CPU,则目标并行级别被设置为4,即相当于为前一个方法传入参数4。


上述方法返回 ExecutorService 对象或ScheduledExecutorService 对象。对于前者,提供了3个重载的submit 方法,将一个Runnable 对象提交给线程池;对于后者,提供了4个方法指定延迟或周期性的执行线程任务的方法(2个schedule方法、固定频率执行线程的scheduleAtFixedRate方法、延迟执行线程的scheduleWithFixedDelay方法)。


扩展:Executor 类:执行Runable 对象的类(非同步的执行),该方式可用于替代直接创建线程的方式:

Executor exe = new Executor();exe.execute(runnableObj1);exe.execute(runnableObj2);