一、基本概念

1.1、过程

过程是零碎资源分配的最小单位。由 文本区域数据区域堆栈 组成。

  • 文本区域存储处理器执行的代码
  • 数据区域存储变量和过程执行期间应用的动态分配的内存;
  • 堆栈区域存储着流动过程调用的指令和本地变量。

波及问题: cpu抢占内存调配(虚拟内存/物理内存),以及过程间通信

1.2、线程

线程是操作系统可能进行运算调度的最小单位。

一个过程能够包含多个线程,线程共用过程所调配到的资源空间

波及问题: 线程状态并发问题

1.3、协程

子例程: 某个主程序的一部分代码,也就是指某个办法,函数。

维基百科:执行过程相似于 子例程 ,有本人的上下文,然而其切换由本人管制。

1.4、常见问题

  • 1、过程和线程的区别

    过程领有本人的资源空间,而线程须要依赖于过程进行资源的调配,能力执行相应的工作。过程间通信须要依赖于 管道,共享内存,信号(量)和音讯队列等形式。线程不平安,容易导致过程解体等
  • 2、什么是多线程

    线程是运算调度的最小单位,即每个处理器在某个工夫点上只能解决一个线程任务调度。在多核cpu 上,为了进步咱们cpu的使用率,从而引出了多线程的实现。通过多个线程工作并发调度,实现工作的并发执行。也就是咱们所说的多线程工作执行。

二、Thread

2.1、应用多线程

2.1.1、继承 Thread 类

class JayThread extends Thread{    @Override    public void run(){        System.out.println("hello world in JayThread!");    }}class Main{    public static void main(String[] args){        JayThread t1 = new JayThread();        t1.start();    }}

2.1.2、实现 Runnable 接口

class JayRunnable implements Runnable{        @Override    public void run(){        System.out.println("hello world in JayRunnable!")    }}class Main{    public static void main(String[] args){        JayRunnable runnable = new JayRunnable();        Thread t1 = new Thread(runnable);        t1.start();    }}

2.1.3、实现 Callable 接口

class JayCallable implements Callable<String> {    @Override    public String call() throws Exception {        System.out.println("run in JayCallable " + Thread.currentThread().getName());        return "Jayce";    }}class Main{    public static void main(String[] args) {         Thread.currentThread().setName("main thread");         ThreadPoolExecutor executor =new ThreadPoolExecutor(10,20,60, TimeUnit.SECONDS,new     ArrayBlockingQueue<>(10));         Future<String> future = executor.submit(new JayCallable());         try {                future.get(10, TimeUnit.SECONDS);         }catch (Exception e){                System.out.println("工作执行超时");            }       }}

2.1.4、常见问题

  • 1、应用多线程有哪些形式

罕用的形式次要由上述3种,须要留神的是 应用 ,而不是创立线程,从实现的代码咱们能够看到,Java 创立线程只有一种形式, 就是通过 new Thread() 的形式进行创立线程。

  • 2、Thread(),Runnable()Callable()之间的区别

Thread 须要继承,重写 run() 办法,对拓展不敌对,一个类即一个线程工作。

Runnbale 通过接口的形式,能够实现多个接口,继承父类。须要创立一个线程进行装载工作执行。

Callable JDK1.5 后引入, 解决 Runnable 不能返回后果或抛出异样的问题。须要联合 ThreadPoolExecutor 应用。

  • 3、Thread.run()Thread.start() 的区别

Thread.run()

    public static void main(String[] args){        Thread.currentThread().setName("main thread");        Thread t1 = new Thread(()->{            System.out.println("run in "+Thread.currentThread().getName());        });        t1.setName("Jayce Thread");        t1.run();    }

输入后果:

Thread.start()

    public static void main(String[] args){        Thread.currentThread().setName("main thread");        Thread t1 = new Thread(()->{            System.out.println("run in "+Thread.currentThread().getName());        });        t1.setName("Jayce Thread");        t1.start();    }

输入后果:

start() 办法来启动线程,使当前任务进入 cpu 期待队列(进入就绪状态,期待cpu分片),获取分片后执行run办法。run() 办法执行,会被解析成一个一般办法的调用,间接在以后线程执行。

2.2、线程状态

线程状态,也称为线程的生命周期, 次要能够分为: 新建就绪运行死亡梗塞等五个阶段。

图片援用 芋道源码

2.2.1 新建

新建状态比拟好了解, 就是咱们调用 new Thread() 的时候所创立的线程类。

2.2.2 就绪

就绪状态指得是:

1、当调用 Thread.start 时,线程能够开始执行, 然而须要期待获取 cpu 资源。区别于 Thread.run 办法,run 办法是间接在以后线程进行执行,沿用其 cpu 资源。

2、运行状态下,cpu 资源应用完后,从新进入就绪状态,从新期待获取 cpu 资源. 从图中能够看到,能够间接调用 Thread.yield 放弃以后的 cpu资源,进入就绪状态。让其余优先级更高的工作优先执行。

2.2.3 运行

步骤2 就绪状态中,获取到 cpu资源 后,进入到运行状态, 执行对应的工作,也就是咱们实现的 run() 办法。

2.2.4 完结

1、失常工作执行实现,run() 办法执行结束

2、异样退出,程序抛出异样,没有捕捉

2.2.5 阻塞

阻塞次要分为: io期待,锁期待,线程期待 这几种形式。通过上述图片能够直观的看到。

io期待: 期待用户输出,让出cpu资源,等用户操作实现后(io就绪),从新进入就绪状态。

锁期待:同步代码块须要期待获取锁,能力进入就绪状态

线程期待: sleep()join()wait()/notify() 办法都是期待线程状态的阻塞(能够了解成以后线程的状态受别的线程影响)

二、线程池

2.1 池化技术

池化技术,次要是为了缩小每次资源的创立,销毁所带来的损耗,通过资源的反复利用进步资源利用率而实现的一种技术计划。常见的例如: 数据库连接池,http连接池以及线程池等。都是通过池同一治理,反复利用,从而进步资源的利用率。

应用线程池的益处:

  • 升高资源耗费:通过反复利用已创立的线程升高线程创立和销毁造成的耗费。
  • 进步响应速度:当工作达到时,工作能够不须要的等到线程创立就能立刻执行。
  • 进步线程的可管理性:线程是稀缺资源,如果无限度的创立,不仅会耗费系统资源,还会升高零碎的稳定性,应用线程池能够进行对立的调配,调优和监控。

2.2 线程池创立

2.2.1 Executors (不倡议)

Executors 能够比拟快捷的帮咱们创立相似 FixedThreadPool ,CachedThreadPool 等类型的线程池。

// 创立繁多线程的线程池public static ExecutorService newSingleThreadExecutor();// 创立固定数量的线程池public static ExecutorService newFixedThreadPool(int nThreads);// 创立带缓存的线程池public static ExecutorService newCachedThreadPool();// 创立定时调度的线程池public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);// 创立流式(fork-join)线程池public static ExecutorService newWorkStealingPool();

存在的弊病:

FixedThreadPool 和 SingleThreadExecutor :容许申请的队列长度为 Integer.MAX_VALUE ,可能沉积大量的申请,从而导致OOM。CachedThreadPool 和 ScheduledThreadPool :容许创立的线程数量为 Integer.MAX_VALUE ,可能会创立大量线程,从而导致OOM。

2.2.2 ThreadPoolExecuotr

构造函数:

       public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue,                              ThreadFactory threadFactory,                              RejectedExecutionHandler handler)

几个外围的参数:

  • 1、corePoolSize: 外围线程数
  • 2、maximumPoolSize: 最大线程数
  • 3、keepAliveTime: 线程闲暇存活工夫
  • 4、unit: 工夫单位
  • 5、workQueue: 期待队列
  • 6、threadFactory: 线程工厂
  • 7、handler: 回绝策略

与上述的 ExecutorService.newSingleThreadExecutor 等多个api进行比照,能够比拟容易的辨别出底层的实现是依赖于 BlockingQueue 的不同而定义的线程池。

次要由以下几种的阻塞队列:

  • 1、ArrayBlockingQueue,队列是有界的,基于数组实现的阻塞队列
  • 2、LinkedBlockingQueue,队列能够有界,也能够无界。基于链表实现的阻塞队列 对应了: Executors.newFixedThreadPool()的实现。
  • 3、SynchronousQueue,不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作将始终处于阻塞状态。对应了:Executors.newCachedThreadPool()的实现。
  • 4、PriorityBlockingQueue,带优先级的无界阻塞队列

回绝策略次要有以下4种:

  • 1、CallerRunsPolicy : 在调用者线程执行
  • 2、AbortPolicy : 间接抛出RejectedExecutionException异样
  • 3、DiscardPolicy : 工作间接抛弃,不做任何解决
  • 4、DiscardOldestPolicy : 抛弃队列里最旧的那个工作,再尝试执行当前任务

2.3 线程池提交工作

往线程池中提交工作,次要有两种办法,execute()submit()

1、 execute()

无返回后果,间接执行工作

public static void main(String[] args) {    ExecutorService executor = Executors.newFixedThreadPool(2);    executor.execute(() -> System.out.println("hello"));}

2、submit()

submit() 会返回一个 Future 对象,用于获取返回后果,罕用的api 有 get()get(timeout,unit) 两种形式,罕用于做限时解决

public static void main(String[] args) throws Exception {    ExecutorService executor = Executors.newFixedThreadPool(2);    Future<String> future = executor.submit(() -> {        System.out.println("hello world! ");        return "hello world!";    });    System.out.println("get result: " + future.get());}

三、线程工具类

3.1 ThreadlLocal

ThreadLocal,很多中央叫做线程本地变量,也有些中央叫做线程本地存储,其实意思差不多。可能很多敌人都晓得ThreadLocal为变量在每个线程中都创立了一个正本,那么每个线程能够拜访本人外部的正本变量。

3.2 Semaphore

Semaphore ,是一种新的同步类,它是一个计数信号. 应用示例代码:

 // 线程池        ExecutorService exec = Executors.newCachedThreadPool();        // 只能5个线程同时拜访        final Semaphore semp = new Semaphore(5);        // 模仿20个客户端拜访        for (int index = 0; index < 50; index++) {            final int NO = index;            Runnable run = new Runnable() {                public void run() {                    try {                        // 获取许可                        semp.acquire();                        System.out.println("Accessing: " + NO);                        Thread.sleep((long) (Math.random() * 6000));                        // 拜访完后,开释                        semp.release();                        //availablePermits()指的是当前信号灯库中有多少个能够被应用                        System.out.println("-----------------" + semp.availablePermits());                     } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            };            exec.execute(run);        }        // 退出线程池        exec.shutdown();

3.3 CountDownLatch

能够了解成是一个栅栏,须要等所有的线程都执行实现后,能力持续往下走。

CountDownLatch 默认的构造方法是 CountDownLatch(int count) ,其参数示意须要缩小的计数,主线程调用 #await() 办法通知 CountDownLatch 阻塞期待指定数量的计数被缩小,而后其它线程调用 CountDownLatch#countDown() 办法,减小计数(不会阻塞)。期待计数被缩小到零,主线程完结阻塞期待,持续往下执行。

3.4 CyclicBarrier

CyclicBarrierCountDownLatch 有点类似, 都是让线程都达到某个点,能力持续往下走, 有所不同的是 CyclicBarrier 是能够屡次应用的。 示例代码:

          CyclicBarrier barrier;                public TaskThread(CyclicBarrier barrier) {            this.barrier = barrier;        }                @Override        public void run() {            try {                Thread.sleep(1000);                System.out.println(getName() + " 达到栅栏 A");                barrier.await();                System.out.println(getName() + " 冲破栅栏 A");                                Thread.sleep(2000);                System.out.println(getName() + " 达到栅栏 B");                barrier.await();                System.out.println(getName() + " 冲破栅栏 B");            } catch (Exception e) {                e.printStackTrace();            }        }

四、总结

最初贴一个新生的公众号 (Java 补习课),欢送各位关注,次要会分享一下面试的内容(参考之前博主的文章),阿里的开源技术之类和阿里生存相干。 想要交换面试教训的,能够增加我的集体微信(Jayce-K)进群学习~