乐趣区

关于java:面试补习-多线程知识梳理

一、基本概念

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)进群学习~

退出移动版