多线程理解(二)

多线程理解(一)
线程安全、锁
并发工具类

线程组

为了便于对某些具有相同功能的线程进行管理,可以把线程归属到某一个线程组中,线程组中既可以有线程对象,也可以有线程组,组中也可以有线程;

public class Test6 {
    public static void main(String[] args) throws InterruptedException {
    	//自定义线程组挂在main线程组下
    	//ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();  取得main主线程所在的线程组
        //ThreadGroup group = new ThreadGroup(mainGroup,"自定义线程组");
        ThreadGroup group = new ThreadGroup("自定义线程组");		//不指定所属的线程组则自动归到当前线程对象所属的线程组中
        new Thread(group,()-> System.out.println("---")).start();
        new Thread(group,()-> System.out.println("+++")).start();
        System.out.println(group.activeCount());		//打印2,线程组中活动的线程数量
        System.out.println(group.activeGroupCount());	//打印1,取得当前线程组对象中的子线程组数量
        System.out.println(group.getName());			//线程组的名称
    }
}

根线程组

System.out.println(Thread.currentThread().getThreadGroup().getParent().getName());  //打印system
System.out.println(Thread.currentThread().getThreadGroup().getParent().getParent().getName()); //报空指针异常

结果说明JVM的根线程组是system,再取父线程组则出现空异常;

批量停止线程组中的线程

通过将线程归属到线程组中,当调用线程组ThreadGroup的interrupt()方法时可以中断该组中所有正在运行的线程:

public class Test6 implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
        ThreadGroup group = new ThreadGroup(mainGroup,"自定义线程组");
        Test6 test6 = new Test6();
        for (int i = 0; i < 10; i++) {
            new Thread(group,test6).start();
        }
        Thread.sleep(100);
        group.interrupt();
    }
    @Override
    public void run() {
        for (int i = 0; i < 1000000; i++) {
            if (Thread.currentThread().isInterrupted()){
                System.out.println(Thread.currentThread().getName()+"已被中断");
                break;
            }else {
                System.out.println(Thread.currentThread().getName()+","+i);
            }
        }
    }
}

实现线程组中一个线程异常停止所有线程

class MyThreadGroup extends ThreadGroup{
    public MyThreadGroup(String name) {
        super(name);
    }
    public MyThreadGroup(ThreadGroup parent, String name) {
        super(parent, name);
    }
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        super.uncaughtException(t, e);
        this.interrupt();
    }
}
public class Test6 implements Runnable {
    private String s;
    public Test6(String s) {
        this.s = s;
    }
    @Override
    public void run() {
        int num = Integer.parseInt(s);
        while (!Thread.currentThread().isInterrupted()){
            System.out.println(Thread.currentThread().getName()+"执行任务中");
        }
    }
    public static void main(String[] args){
        MyThreadGroup threadGroup = new MyThreadGroup("我的线程组");
        Test6 test6 = new Test6("2");
        for (int i = 0; i < 4; i++) {
            new Thread(threadGroup,test6).start();
        }
        new Thread(threadGroup,new Test6("a")).start();
    }
}

需要注意的是,使用自定义的线程组并重写uncaughtException()方法处理组内线程中断行为时,每个线程对象中的run()方法内部不要有异常catch语句,如果有catch语句,则public void uncaughtException(Thread t, Throwable e)方法不会执行!

线程池

线程池就是创建若干个可执行的线程放入一个池(容器)中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务;

为什么要使用线程池?

因为 Java 中创建一个线程,需要调用操作系统内核的 API,操作系统要为线程分配一系列的资源,成本很高,所以线程是一个重量级的对象,应该避免频繁创建和销毁。使用线程池就能很好地避免频繁创建和销毁!

线程池构造函数参数

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数
  • keepAliveTime:保持存活时间
  • unit:存活时间单位,秒、分钟、小时、天等等
  • workQueue:任务存储队列
  • threadFactory:当线程池需要新的线程的时候,会使用threadFactory来生成新的线程
  • handler:线程池无法接受新提交的任务的拒绝策略

在这里插入图片描述
核心线程数就像是工厂正式工,非核心线程数则类似工厂临时工;

生活中小例子:当工厂工作量突然加大时,正式工工人已经无法完成这么大的工作量的时候,工厂会再外招一批临时工,临时工加正式工的和就是最大线程数,等这批任务结束后,临时工是要辞退的(存活时间就是keepAliveTime设置的值),而正式工则会一直留下;

任务队列有如下3种常见类型:

  1. SynchronousQueue:它的容量是0(即SynchronousQueue不存储任何元素),每个 put 必须等待一个 take,反之亦然;
  2. LinkedBlockingQueue:无界队列;
  3. ArrayBlockingQueue:有界队列;

无界队列:没有设置固定大小的队列。这些队列的特点是可以直接入列,直到溢出(超过 Integer.MAX_VALUE),相当于无界;
有界队列:有设置固定大小的队列;

拒绝策略:

  1. AbortPolicy:直接抛出RejectedExecutionException异常
  2. DiscardPolicy:丢弃任务
  3. DiscardOldestPolicy:丢弃旧任务,以便腾出空间保存新提交的任务
  4. CallerRunsPolicy:如果线程池已经饱和,则谁提交的任务谁去跑,比如主线程提交的任务,这个时候就会交给主线程去执行;

使用线程池

1、newFixedThreadPool()

拥有固定线程数的线程池,不管提交的任务多还是少,都创建固定数量的线程;

public class Test7
{
    public static void main(String[] args){
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(new Task());
        }
    }

    static class Task implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

运行程序,查看控制台打印语句我们发现就算我们提交了100个任务,线程池也只会创建固定的10个线程来执行任务;

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

查看源码,我们知道这个线程池是将核心线程数与最大线程数设置为一样;

由于LinkedBlockingQueue是无界队列,没有容量上限。所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致OOM;

2、newSingleThreadExecutor()

只有一个线程的线程池,固定创建一个线程;

public class Test7
{
    public static void main(String[] args){
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Task());
        }
    }

    static class Task implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

运行程序,查看控制台打印语句我们发现创建的线程都是同一个;

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

查看源码,我们知道这个线程池是将核心线程数与最大线程数都设置为1;

和newFixedThreadPool一样,这个也会导致同样的问题,也就是当请求堆积的时候,可能会占用大量的内存。

3、newCachedThreadPool()

可缓存线程池;

ExecutorService executorService = Executors.newCachedThreadPool();

如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程;当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

查看源码,我们发现线程池为无限大,这也可能会出现占用大量的内存导致OOM的问题;

4、newScheduledThreadPool()

支持定时及周期性任务执行的线程池;

public class Test7
{
    public static void main(String[] args){
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
        //
        scheduledThreadPool.schedule(new Task(),3L,TimeUnit.SECONDS);
        scheduledThreadPool.scheduleAtFixedRate(new Task(),1,3,TimeUnit.SECONDS);
    }

    static class Task implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

5、ThreadPoolExecutor()

由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活【消耗内存等】;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险,所以我们使用线程池应首选ThreadPoolExcutor;

ThreadPoolExecutor threadPool= new ThreadPoolExecutor(2, 4, 3,
        TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
        new ThreadPoolExecutor.DiscardOldestPolicy());

线程池状态

ThreadPoolExecutor 使用 runState变量对线程池的生命周期进行控制,有以下5种:

  • RUNNING:接收新的任务并对任务队列里的任务进行处理;
  • SHUTDOWN:不再接收新的任务,但是会对任务队列中的任务进行处理;
  • STOP:不接收新任务,也不再对任务队列中的任务进行处理,并中断正在处理的任务;
  • TIDYING:所有任务都已终止,线程数为0,在转向TIDYING状态的过程中,线程会执行terminated()钩子方法,钩子方法是指在本类中是空方法,而在子类中进行具体实现的方法;
  • TERMINATED:terminated()方法执行结束后会进入这一状态,表示线程池已关闭;

运行状态的转化条件和转化关系如下所示:

在这里插入图片描述

线程池核心方法

1、shutdown()

当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。

public class Test7
{
    public static void main(String[] args){
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        threadPool.execute(new Task());

        threadPool.shutdown();

        threadPool.execute(new Task());
    }

    static class Task implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

运行程序,将会报以下异常:

在这里插入图片描述

2、isShutdown()

isShutDown当调用shutdown()或shutdownNow()方法后返回为true;

3、isTerminated()

如果关闭后所有任务都已完成(包括正在执行的任务以及任务队列里的任务),则返回 true;

4、awaitTermination()

当前线程阻塞,直到

  • 所有已提交的任务(包括正在跑的和队列中等待的)执行完
  • 超时时间到
  • 线程被中断,抛出InterruptedException

然后返回true(shutdown请求后所有任务执行完毕)或false(已超时);

5、shutdownNow()

执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务;
在这里插入图片描述
ThreadPoolTest.java

//实现Runnable接口来定义一个简单的
class TestThread implements Runnable
{
	public void run()
	{
		for (int i = 0; i < 100 ; i++ )
		{
			System.out.println(Thread.currentThread().getName()
				+ "的i值为:" + i);
		}
	}
}

public class ThreadPoolTest
{
	public static void main(String[] args) 
	{
		//创建一个具有固定线程数(6)的线程池
		ExecutorService pool = Executors.newFixedThreadPool(6);
		//向线程池中提交2个线程
		pool.submit(new TestThread());
		pool.submit(new TestThread());
		//关闭线程池
		pool.shutdown();
	}
}

ThreadLocal

ThreadLocal解决的是变量在不同线程的隔离性,也就是不同线程拥有自己的值,不同线程的值是通过ThreadLocal类进行保存的!

public class Test6{
    public static void main(String[] args) throws InterruptedException {
       ThreadLocal local = new ThreadLocal();
       local.set("线程:"+Thread.currentThread().getName()+",123");
       System.out.println(local.get());

       new Thread(()->{
           local.set("线程:"+Thread.currentThread().getName()+",456");
           System.out.println(local.get());
       }).start();
    }
    //main线程创建了此子线程,但是main线程中的值并没有继承给子线程
    new Thread(()->{System.out.println(local.get());}).start();
}
//控制台打印
线程:main,123
线程:Thread-0,456
null

结果表明ThreadLocal可向每个线程存储自己的私有数据!类ThreadLocal不能实现值继承

ThreadLocal存流程分析

set源代码如下:

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

取得Thread中的ThreadLocal.ThreadLocalMap后,第一次向其存放数据时会调用createMap()方法来创建ThreadLocal.ThreadLocalMap对象,createMap()源码如下:

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
    
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
     table = new Entry[INITIAL_CAPACITY];
     int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
     table[i] = new Entry(firstKey, firstValue);
     size = 1;
     setThreshold(INITIAL_CAPACITY);
 }

key是当前ThreadLocal对象,值就是传入的value。然后被封装进Entry对象中,并放入table[]数组中。

ThreadLocal取流程分析

get源代码如下:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

最终从Entry对象中取得数据!

initialValue()

在第一次调用ThreadLocal类的get()方法时,返回值是Null。怎样使其具有默认值的效果呢?

覆盖initialValue()方法具有初始值,因为ThreadLocal中的initialValue()方法默认返回值是null,需要在子类中进行重写:

public class Test6 extends ThreadLocal{
    @Override
    protected Object initialValue() {
        return "默认值-"+System.currentTimeMillis();
    }
    public static void main(String[] args) throws InterruptedException {
        Test6 test6 = new Test6();
        System.out.println(test6.get());
        new Thread(()->{System.out.println(test6.get());}).start();
    }
}
//控制台打印
默认值-1641889927195
默认值-1641889927262

结果表明子线程和父线程各拥有自己所拥有的值!

InheritableThreadLocal

使用类InheritableThreadLocal可使子线程继承父线程的值:

public class Test6{
    public static void main(String[] args) throws InterruptedException {
        InheritableThreadLocal threadLocal = new InheritableThreadLocal();
        threadLocal.set("123");
        System.out.println(threadLocal.get());
        new Thread(()->{System.out.println(threadLocal.get());}).start();
    }
}
//控制台打印
123
123

值继承特性执行流程分析

InheritableThreadLocal源码如下:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

这三个方法都是对父类ThreadLocal中的同名方法进行重写。

执行InheritableThreadLocal对象中的set()方法其实就是调用ThreadLocal中的set()方法,因为此方法InheritableThreadLocal并没有进行重写。但是getMap(Thread t)与createMap(Thread t, T firstValue)被重写了;

由源码可知,重写后的方法不再向Thread中的ThreadLocal.ThreadLocalMap threadLocals存入数据了,而是向ThreadLocal.ThreadLocalMap inheritableThreadLocals 存入数据,那么子线程如何实现从父线程中的inheritableThreadLocals 对象继承值呢?

这个实现思路就是在创建线程ThreadA时,子线程主动引用父线程main中的inheritableThreadLocals 对象值,源码如下:

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
		
		//父线程的不为null,则对当前线程的inheritableThreadLocals对象变量进行赋值
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

最后一个参数inheritThreadLocals代表当前线程对象是否会从父线程继承值!

接着查看ThreadLocalMap源码:

    private ThreadLocalMap(ThreadLocalMap parentMap) {
        Entry[] parentTable = parentMap.table;
        int len = parentTable.length;
        setThreshold(len);
        table = new Entry[len];

        for (int j = 0; j < len; j++) {
            Entry e = parentTable[j];
            if (e != null) {
                @SuppressWarnings("unchecked")
                ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                if (key != null) {
                    Object value = key.childValue(e.value);
                    Entry c = new Entry(key, value);
                    int h = key.threadLocalHashCode & (len - 1);
                    while (table[h] != null)
                        h = nextIndex(h, len);
                    table[h] = c;
                    size++;
                }
            }
        }
    }

发现子线程将父线程中的table对象以复制的方式赋值给子线程的table数组,这个过程是在创建Thread类对象时发生的,也就是说明当子线程对象创建完毕后,子线程中的数据就是主线程中旧的数据,主线程使用新的数据时,子线程还是使用旧的数据,因为主子 线程使用两个Entry[]对象数组各自存储自己的值!

示例1:改了父线程的值,子线程还是旧值

public class Test6{
    public static void main(String[] args) throws InterruptedException {
        InheritableThreadLocal local = new InheritableThreadLocal();
        local.set("123");
        System.out.println(local.get());

        new Thread(()->{
            for (int i = 0; i < 2; i++) {
                try {
                    System.out.println(local.get());
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        local.set("456");
        System.out.println(local.get());
    }
}
//打印
123--main线程
456--main线程
123--子线程
123---子线程

示例2:改了子线程的值,父线程还是旧值

public class Test6{
    public static void main(String[] args) throws InterruptedException {
        InheritableThreadLocal local = new InheritableThreadLocal();
        local.set("123");
        System.out.println("线程:"+Thread.currentThread().getName()+local.get());

        new Thread(()->{
            for (int i = 0; i < 2; i++) {
                try {
                    if (i == 1){
                        local.set("456");
                    }
                    System.out.println("线程:"+Thread.currentThread().getName()+local.get());
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        Thread.sleep(1000);
        System.out.println("线程:"+Thread.currentThread().getName()+local.get());
    }
}
//打印
线程:main123
线程:Thread-0123
线程:Thread-0456
线程:main123

示例3:子线程可以感应对象属性值的变化

class userInfos{
    private String userName;
    public userInfos(String userName) {
        this.userName = userName;
    }
}
public class Test6{
    public static void main(String[] args) throws InterruptedException {
        InheritableThreadLocal<userInfos> local = new InheritableThreadLocal<>();
        userInfos userInfo = new userInfos("A");
        local.set(userInfo);

        new Thread(()->{
            for (int i = 0; i < 4; i++) {
                try {
                    System.out.println("线程:"+Thread.currentThread().getName()+local.get().getUserName());
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        Thread.sleep(100);
        userInfo.setUserName("B");
    }
}
//打印
线程:Thread-0A
线程:Thread-0A
线程:Thread-0B
线程:Thread-0B

重写childValue()方法实现对继承的值进行加工

在继承的同时还可以对值进行进一步的加工:

public class Test6 extends InheritableThreadLocal{
    @Override
    protected Object childValue(Object parentValue) {
        return parentValue+"----";
    }
    public static void main(String[] args) throws InterruptedException {
        Test6 test6 = new Test6();
        test6.set("123");
        System.out.println(Thread.currentThread().getName()+test6.get());
        new Thread(()-> System.out.println(Thread.currentThread().getName()+test6.get())).start();
    }
}
//打印
main123
Thread-0123----
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值