专项攻克——线程

一、线程创建方式

1.1 常见的四种线程创建方式、使用案例

参考博客线程创建常用的四种方式

  1. 继承Thread类创建线程
  2. 实现Runnable接口创建线程【推荐】 场景:临时使用,内部方法,不被外部对象使用
  3. 使用Callable和Future创建线程
  4. 使用线程池例如用Executor框架
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

public class ThreadTest_2 {
    //继承Thread
    public static class MyThread extends Thread {
        @Override
        public void run () {
            System.out.println("1 继承Thread类 方式");
        }
    }

    //实现Runnable
    public static class MyRunnable implements Runnable {
        @Override
        public void run () {
            System.out.println("2.1 实现Runnable接口 方式");
            System.out.println("2.1 " + Thread.currentThread().getName());
        }
    }

    //实现Callable接口
    public static class MyCallable implements Callable {
        @Override
        public Integer call() throws Exception {
            System.out.println("3 实现Callable接口 方式");
            System.out.println("3 " + Thread.currentThread().getName());
            return 200;
        }
    }

    public static void main(String[] args) {
        //1.继承Thread类
        new MyThread().start();

        //2.1 实现Runnable接口(完整版)
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();

        //2.2 实现Runnable接口(缩写版)
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("2.2 实现Runnable接口 方式");
            }
        }).start();

        //3.1 实现Callable接口(完整版)
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
        Thread threadFuture = new Thread(futureTask, "threadFuture完整版");
        threadFuture.start();
        try {
            System.out.println(futureTask.get());//get()方法会阻塞,直到子线程执行结束才返回
        } catch (Exception e){
            System.out.println(e);
        }

        //3.2 实现Callable接口(缩写版)
        new Thread(new FutureTask<Integer>(new MyCallable()), "threadFuture缩写版").start();

        // ###############  线程池  ############### 
        //4.1 线程池方式——固定线程数
        ExecutorService executorService1 = Executors.newFixedThreadPool(5);
        executorService1.submit(myRunnable);

        //4.2 线程池方式——单1线程
        ExecutorService executorService2 = Executors.newSingleThreadExecutor();
        executorService2.submit(myRunnable);

        //4.3 线程池方式——缓存短期线程
        ExecutorService executorService3 = Executors.newCachedThreadPool();
        executorService3.submit(myRunnable);

        //4.4 线程池方式——调度型线程
        ExecutorService executorService4 = Executors.newScheduledThreadPool(5);
        executorService4.submit(myRunnable);

        //4.4 线程池方式——多任务队列线程
        ExecutorService executorService5 = Executors.newWorkStealingPool(5);
        executorService5.submit(myRunnable);
    }
}

输出结果
在这里插入图片描述

1.2 区别总结

差别:

  1. Callable规定(重写)的方法是call(),call()可抛出异常,任务执行后可返回值;Runnable规定(重写)的方法是run(),run()不能抛出异常,任务不能返回值。
  2. 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果;
  3. 继承Thread类:线程类无法再继承其他父类(Java单继承性质)。
  4. 前三种的线程如果创建关闭频繁会消耗系统资源影响性能;而使用线程池可以重复使用线程

二、Thread和Runnable源码

可以注意到:

  • Runnable 和 Thread 都使用了注解@FunctionalInterface,该注解提示编译器去检查接口是否仅包含一个抽象方法,即,是否符合函数式编程的定义。

2.1 源码——Runnable接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

2.2 源码——Thread类

public class Thread implements Runnable {
    ...

    private volatile String name;
    private int            priority;
    private Thread         threadQ;
    private long           eetop;
    private boolean     single_step;
    private boolean     daemon = false;
    private boolean     stillborn = false;
    private Runnable target;
    private ThreadGroup group;
    private ClassLoader contextClassLoader;
    private AccessControlContext inheritedAccessControlContext;
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

    /* 重要! */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * 重要! This map is maintained by the InheritableThreadLocal class.*/
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

    
    private long stackSize;
    private long nativeParkEventPointer;

    /* Thread ID */
    private long tid;
    /* For generating thread ID */
    private static long threadSeqNumber;
    private volatile int threadStatus = 0;
    
    private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }

    volatile Object parkBlocker;

    private volatile Interruptible blocker;
    private final Object blockerLock = new Object();

    void blockedOn(Interruptible b) {
        synchronized (blockerLock) {
            blocker = b;
        }
    }

    /** 最低优先级 */
    public final static int MIN_PRIORITY = 1;

   /** 默认优先级 */
    public final static int NORM_PRIORITY = 5;

    /** 最大优先级 */
    public final static int MAX_PRIORITY = 10;

    public static native Thread currentThread();
    
	public static native void yield();
	public static native void sleep(long millis) throws InterruptedException;
	
	public static void sleep(long millis, int nanos) throws InterruptedException {
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException("nanosecond timeout value out of range");
        }

        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        sleep(millis);
    }
    
    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) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        
        g.checkAccess();

        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 (inheritThreadLocals && 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();
    }

	...

	public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }
    
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
	
	...
	

}

三、等待多个线程执行完

背景:最近做了一个需求,要求返回某个企业所有“品牌入驻”任务的完成情况,这些入驻完成情况需要调用其他服务器的接口服务来检测,有如下两种做法:

  1. 直接顺序执行:在一条线程里挨个检测“所有入驻任务”完成情况,此时接口服务调用是按照顺序执行的。
    【缺点:任务较多时,执行时间会增大。】
  2. 多线程:让每个线程去检测“入驻任务”完成情况,而主线程等待所有任务完成后再组合返回。
    疑问:怎么让某个线程等待它开启的所有线程执行完后再执行呢?

3.1 CountDownLatch 计数法

原因:

  • countDownLatch.await()会阻塞当前线程,直到countDownLatch的值等于0
  • countDownLatch不可能重新初始化或者修改CountDownLatch对象内部计数器的值,一个线程调用countdown方法happen-before另外一个线程调用await方法
import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest_3 {
    public static void main(String[] args) {
        int total = 5;
        final CountDownLatch countDownLatch = new CountDownLatch(total);
        for (int i = 0; i < total; i++) {
            new Thread(new Runnable(){
                @Override
                public void run(){
                    System.out.println(Thread.currentThread().getName() + 
                    	",countDownLatch当前值" + countDownLatch.getCount());
                    countDownLatch.countDown();
                }
            }).start();
        }

        try {
            countDownLatch.await();
            System.out.println("全部线程执行完成");
        } catch (InterruptedException e) {
            System.out.println(e);
        }
    }
}

结果输出
在这里插入图片描述

3.2 CyclicBarrier 同步屏障

  • cyclicBarrier.await()会阻塞当前线程,直到cyclicBarrier的值等于0 即所有线程都到到了await状态,才会继续执行。
  • 有两种需求,一种是不需要汇总,一种需要汇总。如下。

3.2.1 到达屏障不汇总

  • 当最后一个线程到达时,不进行汇总,屏障直接消除,所有线程继续执行。
    在这里插入图片描述
  • 案例:
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierTest_4 {
    public static void main(String[] args) {
        int total = 5;
        final CyclicBarrier cyclicBarrier = new CyclicBarrier(total);
        for (int i = 0; i < total; i++) {
            new Thread(new Runnable(){
                @Override
                public void run(){
                    try {
                        System.out.println(Thread.currentThread().getName() + "前");
                        cyclicBarrier.await();//到达屏障
                        System.out.println("全部线程都到达了屏障");
                        System.out.println(Thread.currentThread().getName() + "后");
                    } catch (Exception e) {
                        System.out.println(e);
                    }
                }
            }).start();
        }

        try {
            System.out.println(Thread.currentThread().getName() + ",cyclicBarrier当前值" + cyclicBarrier.getNumberWaiting());
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

输出结果:
在这里插入图片描述

3.2.2 到达屏障进行汇总

  • 当所有线程到达屏障时,先释放主线程,再进行汇总操作,汇总完后,屏障直接消除,所有线程继续执行
  • 案例:
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierTest_4 {
    public static void main(String[] args) {
        int total = 5;
        final CyclicBarrier cyclicBarrier = new CyclicBarrier(total, new Runnable() {
            @Override
            public void run(){
                System.out.println(Thread.currentThread().getName() + ",全部线程都到达了屏障");
            }
        });

        for (int i = 0; i < total; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(Thread.currentThread().getName() + "前");
                        cyclicBarrier.await();//到达屏障
                        System.out.println(Thread.currentThread().getName() + "后");
                    } catch (Exception e) {
                        System.out.println(e);
                    }
                }
            }).start();
        }

        try {
            System.out.println(Thread.currentThread().getName() + 
            	",cyclicBarrier当前值" + cyclicBarrier.getNumberWaiting());
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

输出结果
在这里插入图片描述

四、怎么知道设置多少线程合适?

  • 通常,使用固定线程池限制最大线程数。

  • 在并发编程领域,提升性能的本质,就是提升硬件的利用率: (1)CPU利用率、(2)I/O利用率。

  • 线程数设置多少合适?如下:

    1. 对于CPU密集型场景:如圆周率计算、视频高清解码等,全靠CPU运算能力这种计算密级型任务越多,消耗在任务切换的时间就越多。
      (1). 理论上最合适的是,线程数量=CPU核数。
      (2). 但是在工程上通常设置,线程数量=CPU核数+1。

      原因:当线程偶尔发生内存页失效或其他现象导致阻塞时,这个额外的线程可以顶上,从而保证CPU的利用率。==

    2. 对于 I/O 密集型场景:如网络IO、硬盘IO、Web应用等,这类任务消耗CPU小,大部分时间都在等待IO操作完成。任务越多,CPU效率越高。
      单核最佳线程数=1+(I/O耗时/CPU耗时),针对多核CPU,目前见过两种比较合理的公式:
      (1). 多核最佳线程数=CPU核数×100%×[1+(I/O耗时/CPU耗时)]
      (2). 多核最佳线程数=CPU核数×目标CPU利用率×(1+平均等待时间/平均工作时间)

      案例:假设有n个任务,每个任务I/O耗时10s,每个任务CPU耗时1s。如果CPU的核数是4核,则最佳线程数=4x(1+10/1)=44。
      可以理解为,在一个核上,10个任务同时执行的耗时与一个任务的执行耗时是相同的,都是10s。

CPU与I/O理解案例:假设程序按照CPU计算和I/O操作交叉执行的方式运行,而且CPU计算和I/O操作的耗时比例是1:1。

  1. 如下图,如果只有一个线程,执行CPU计算时,I/O设备空闲;执行I/O操作时,CPU空闲。所以CPU的利用率和I/O设备的利用率都是50%
    在这里插入图片描述
  2. 如果有两个线程,如下图所示,当线程A执行CPU计算的时候,线程B执行I/O操作;当线程A执行I/O操作的时候,线程B执行CPU计算,这样CPU的利用率和I/O设备的利用率就都达到了100%
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

攻城有术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值