Java多线程(六)线程组和线程池

线程组和线程池

线程组
1.线程组介绍
    java中用线程组(ThreadGroup)来同时管理一批线程,对线程组做的操作相当于对里面所有的线程都做了同样的操作,线程所属的线程组在运行过程中无法修改。

    可能大家听到更多的是线程池的概念,但其实只要使用过线程,那么你就已经使用过了ThreadGroup,只是你自己并不知道而已,我们看下Thread类的构造方法:

	public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

    可以看到,这个构造方法只需要指定target对象,我们再具体看下init()里面的操作:

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name.toCharArray();

    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        /* Determine if it's an applet or not */

        /* If there is a security manager, ask the security manager
           what to do. */
        if (security != null) {
            g = security.getThreadGroup();
        }

        /* If the security doesn't have a strong opinion of the matter
           use the parent thread group. */
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }

    /* checkAccess regardless of whether or not threadgroup is
       explicitly passed in. */
    g.checkAccess();

    /*
     * Do we have the required permissions?
     */
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }

    g.addUnstarted();

    this.group = g;
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;

    /* Set thread ID */
    tid = nextThreadID();
}

    我们主要看方法中对ThreadGroup g参数的处理:

hread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
    /* Determine if it's an applet or not */

    /* If there is a security manager, ask the security manager
       what to do. */
    if (security != null) {
        g = security.getThreadGroup();
    }

    /* If the security doesn't have a strong opinion of the matter
       use the parent thread group. */
    if (g == null) {
        g = parent.getThreadGroup();
    }
}
    可以看到,虽然我们在创建线程实例的时候并没有指定线程所属的线程组,但是在调用Thread类的构造方法创建实例的时候会进行判断,如果没有指定线程组,会使用默认的线程组,如果是在main方法中的创建的线程,那么它们的默认线程组就是main,简单写个测试类看下:
package com.ljw.Thread.ThreadGroup;

/**
 * Created by liujiawei on 2018/7/10.
 */
public class TestThreadGroup {
    public static void main(String[] args) {
        B b = new B();
        Thread t = new Thread(b);
        System.out.println("main线程所属线程组:" + Thread.currentThread().getThreadGroup().getName());
        System.out.println("main线程中的t线程所属线程组:" + t.getThreadGroup().getName());


    }
}
    运行结果:



2.线程组使用
首先介绍一下几个常用的方法:
ThreadGroup类提供了两个公共的构造方法:
 public ThreadGroup(String name) {
    this(Thread.currentThread().getThreadGroup(), name);
}
public ThreadGroup(ThreadGroup parent, String name) {
    this(checkParentAccess(parent), parent, name);
}
    ThreadGroup是一个类似树的结构,如果创建ThreadGroup实例的时候没有指定parent,那么就使用当前线程所属的线程组作为parent。


    同时Thread类也提供了几个构造方法用来指定线程的所属线程组:
public Thread(ThreadGroup group, Runnable target, String name) {
    init(group, target, name, 0);
}
public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}
public Thread(ThreadGroup group, Runnable target) {
    init(group, target, "Thread-" + nextThreadNum(), 0);
}
    主要就是三个参数ThreadGroup group,Runnable target和String name,不同参数构成不同的构造方法,最终调用的都是同一个方法来创建实例。


ThreadGroup类同时还提供几个常用方法来操作线程组中的线程:
  • 获取parent:(system是最顶级的线程组)
public final ThreadGroup getParent()
  • 设置最大优先级:
public final void setMaxPriority(int pri)

  • 获取优先级:
public final int getMaxPriority()
  • 中断所有线程:
public final void interrupt()

  • 返回活动线程的数量:
public int activeCount()

  • 设置为后台线程:
public final void setDaemon(boolean daemon)

  • 是否是后台线程:
public final boolean isDaemon()

思考一个问题,我们知道线程是可以设置优先级的,如果所属的线程组也设置了优先级,以哪个为准呢?

我们看下ThreadGroup中setMaxPriority()方法的源码:

public final void setMaxPriority(int pri) {
    int ngroupsSnapshot;
    ThreadGroup[] groupsSnapshot;
    synchronized (this) {
        checkAccess();
        if (pri < Thread.MIN_PRIORITY || pri > Thread.MAX_PRIORITY) {
            return;
        }
        maxPriority = (parent != null) ? Math.min(pri, parent.maxPriority) : pri;
        ngroupsSnapshot = ngroups;
        if (groups != null) {
            groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
        } else {
            groupsSnapshot = null;
        }
    }
    for (int i = 0 ; i < ngroupsSnapshot ; i++) {
        groupsSnapshot[i].setMaxPriority(pri);
    }
}
线程组最大优先级的设定:
•系统线程组的最大优先级默认为Thread.MAX_PRIORITY
•创建线程组的时候其最大优先级默认为父线程组(如果未指定父线程组,则其父线程组默认为当前线程所属线程组)的最大优先级

•可以通过setMaxPriority更改最大优先级,但无法超过父线程组的最大优先级


3.异常处理
线程的异常处理也是比较重要的一块,如果异常得不到,线程获取的资源就会一直不释放。所以Thread类提供了一个内部接口用来处理线程遇到的异常:
public interface UncaughtExceptionHandler {
    /**
     * Method invoked when the given thread terminates due to the
     * given uncaught exception.
     * <p>Any exception thrown by this method will be ignored by the
     * Java Virtual Machine.
     * @param t the thread
     * @param e the exception
     */
    void uncaughtException(Thread t, Throwable e);
}
    我们在创建完Thread类的实例以后,可以通过调用setUncaughtExceptionHandler()方法,通过内部实现类的形式定义对异常的处理,这是Thread类对异常的处理,我们上文也介绍了线程组ThreadGroup是用来管理一组线程并同时对他们进行操作的。那么ThreadGroup中有没有方法可以对线程组中的线程抛出未处理的异常做处理呢?
    答案是肯定的,ThreadGroup类提供了uncaughtException(thread,throwable)方法
public void uncaughtException(Thread t, Throwable e) {
    if (parent != null) {
        parent.uncaughtException(t, e);
    } else {
        Thread.UncaughtExceptionHandler ueh =
            Thread.getDefaultUncaughtExceptionHandler();
        if (ueh != null) {
            ueh.uncaughtException(t, e);
        } else if (!(e instanceof ThreadDeath)) {
            System.err.print("Exception in thread \""
                             + t.getName() + "\" ");
            e.printStackTrace(System.err);
        }
    }
}
    我们可以在创建完ThreadGroup的实例重写这个方法来处理异常:
ThreadGroup group = new ThreadGroup("线程组"){
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("线程组的异常处理机制处理了" + t.getName() + "的异常");
    }
};
说了这么多我们看一下代码实例:
两个实现了Runnable接口类:

A:

package com.ljw.Thread.ThreadGroup;

/**
 * Created by liujiawei on 2018/7/10.
 */
public class A implements Runnable{


    @Override
    public void run() {
        int a = 5 / 0;
    }
}

B:

package com.ljw.Thread.ThreadGroup;

/**
 * Created by liujiawei on 2018/7/10.
 */
public class B implements Runnable{

    @Override
    public void run() {
        int a = 5/0;
    }


}
测试类:
package com.ljw.Thread.ThreadGroup;

/**
 * Created by liujiawei on 2018/7/10.
 */
public class TestThreadGroup {
    public static void main(String[] args) {
        ThreadGroup group = new ThreadGroup("线程组"){
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("线程组的异常处理机制处理了" + t.getName() + "的异常");
            }
        };

        A a = new A();
        B b = new B();

        Thread threadA = new Thread(group,a,"A线程");
        Thread threadB = new Thread(group,b,"B线程");

        threadA.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("这是" + t.getName() + "的异常处理");
            }
        });


        threadA.start();
        threadB.start();
    }
}

    我们对线程实现了内部处理异常,对b没有做处理,看下运行结果:


从结果中我们可以看到,a线程的未处理的异常被自己处理,b线程中的异常则是被线程组给处理了。



我们总结一下线程组对于线程中未处理而抛出的异常的处理机制:
(1) 如果线程组的父类有异常处理机制,那么优先选择父类的异常处理机制;
(2) 线程组中的线程如果有自己的异常处理,优先选择自身的异常处理;

(3) 如果异常对象是ThreadDeath对象,则不做处理,否则将异常跟踪栈的信息打印到System.err错误输出流,并结束线程。



线程池(简单入门)

1.线程池介绍
创建一个线程集合,当需要使用线程的时候去这个集合中取出线程执行,用完再放回去。
2.线程池的好处
(1) 控制系统中线程并发的数量;
(2)节省创建线程的成本;
3.线程池的基本使用方法

线程池最核心的类就是ThreadPoolExecutor类,他提供了四个构造方法用来创建线程池实例:

	public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
可以看到前面三个构造方法最后调用的都是最后一个,我们看下这个方法的几个参数分别代表什么:
  • corePoolSize:核心池大小;
  • maxmunPoolSize:线程池最大执行数;
  • keepAliveTime:线程没有任务执行时保持多久终止;
  • unit:keepAliveTime的单位,使用枚举TimeUnit中定义的时间常量;
  • workQueue:阻塞队列,用来存储等待执行的任务
  • threadFactory:线程工厂,用来创建线程;
  • handler:拒绝任务时的策略,有:
  1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
  2. ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
  3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
  4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 




一般不直接通过ThreadPoolExecutor的构造方法创建线程池,而是通过调用Executors中的提供的几个静态方法来创建线程池实例,返回一个ExecutorService对象,主要有以下几种线程池:
newFixedThreadPool(int nThreads)

    创建指定线程数量的线程池

newCachedThreadPool()

    创建一个弹性缓存线程池,初始时线程数量为0,只有通过execute或者submit提交任务时,才会创建线程

newScheduledThreadPool

    创建一个定时器线程池,在执行延迟后执行任务

newSingleThreadExecutor()
   创建一个单线程线程池,每次只有一个线程任务,其他任务会等待完成,用来保证执行的顺序



我们简单写个测试类进行实践:
线程类A:
package com.ljw.Thread.ThreadPool;

/**
 * Created by liujiawei on 2018/7/9.
 */
public class A implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "==" + i);
        }
    }
}

测试类:

package com.ljw.Thread.ThreadPool;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by liujiawei on 2018/7/9.
 */
public class TestThreadPool {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        Thread threadA =  new Thread(new A(),"A");
        Thread threadB =  new Thread(new A(),"B");

        threadPool.submit(threadA);
        threadPool.submit(threadB);

        threadPool.shutdown();


    }
}

运行结果:

可以看到两个线程的执行体都成功执行了。

线程池工作流程:


我们总结下使用线程池执行线程任务的步骤:
(1) 通过Executors的静态方法创建一个ExecutorService对象,这个对象代表线程池;
(2)创建线程实例;
(3)通过submit()方法提交线程任务;
(4)通过shutdown()方法关闭线程池。






评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值