线程 *多线程间变量的共享 1.方法体内部定义的局部变量不共享。 在运行时动态生成的。每个线程都有一个自己的堆栈,用于保存运行时的数据。 最容易理解的就是递归调用时候,每次的入栈出栈操作。每次调用时,变量a都是在运行时堆栈上保存的,方法结束变量也就释放了。 2.成员变量需要看变量指向的是否为同一个对象。
- 创建线程的方式及实现
- 继承Thread类创建线程类
- 通过Runable接口创建线程类
- 通过Callable和FutureTask创建线程 a. 创建Callable接口的实现类,并实现call()方法; b. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值; c. 使用FutureTask对象作为Thread对象的target创建并启动新线程; d. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
- 通过线程池创建线程 创建线程的三种方式的对比 采用实现Runnable、Callable接口的方式创见多线程时,优势是:可以继承其他类。 共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:编程稍微复杂。访问当前线程,则必须使用Thread.currentThread()方法。
使用继承Thread类的方式创建多线程时优势是: 编写简单,如果需要访问当前线程,直接使用this即可获得当前线程。
劣势是:线程类已经继承了Thread类,所以不能再继承其他父类
- sleep() 、join()、yield()有什么区别 1、sleep()方法
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 让其他线程有机会继续执行,但它并不释放对象锁。也就是如果有Synchronized同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常
比如有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完成后,低优先级的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。 总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。
2、yield()方法
yield()方法和sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也和sleep()方法不同。
3、join()方法
Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前,B不能工作。(并行变串行,同步)
Thread t = new MyThread();
t.start();
t.join();
保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。例如主线程调A线程。 同步:各个事件之间有先后依赖关系。
- 说说 CountDownLatch 原理 .闭锁CountDownLatch做减计数,而栅栏CyclicBarrier则是加计数。 2.CountDownLatch是一次性的,CyclicBarrier可以重用。 3.CountDownLatch强调一个线程等多个线程完成某件事情。CyclicBarrier是多个线程互等,等大家都完成。 4.鉴于上面的描述,CyclicBarrier在一些场景中可以替代CountDownLatch实现类似的功能。
另外,值得一提的是,CountDownLatch和CyclicBarrier在创建和启动线程时,都没有明确提到同时启动全部线程,事实上这在技术上是不大可能,不必要,不提倡的。
- ThreadLocal 原理分析 ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。(不需要共享,且能用良好性能)。
ThreadLocal调用set方法为当前线程的成员变量threadlocals赋值,key为当前对象this,键为要保存的副本。 1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;
3)在进行get之前,必须先set,否则会报空指针异常;
如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。
因为在上面的代码分析过程中,我们发现如果没有先set的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。
-
讲讲线程池的实现原理 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。
假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。 一个线程池包括以下四个基本组成部分: 1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务; 2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务; 3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等; 4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。 线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目,看一个例子: 假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目,而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。
代码实现中并没有实现任务接口,而是把Runnable对象加入到线程池管理器(ThreadPool),然后剩下的事情就由线程池管理器(ThreadPool)来完成了
-
线程池的几种方式与使用场景 newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。 newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
-
线程的生命周期 生命周期开始:当Thread对象创建完成时
线程的生命周期结束:①当run()方法中代码正常执行完毕
②线程抛出一个未捕获的异或错误时。
线程整个生命周期的五个阶段:
新建状态,就绪状态,运行状态,阻塞状态,死亡状态。
新建状态: 当Thread对象创建完成时, 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。但此时它还不能运行,仅仅由虚拟机为其分配了内存,它保持这个状态直到程序 start() 这个线程。
就绪状态: 当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列(可运行池里)中,此时它具备了运行的条件,能否获得CPU的使用权开始运行,还需要等待系统的调度。
运行状态: 如果就绪状态的线程获得CPU的使用权,开始执行 run()中的线程执行体,此时线程便处于运行状态。当使用完系统分配的时间后,系统就会剥夺该线程占用的CPU资源,让其他线程获得执行的机会。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态: 一个正在线程的线程在某种特殊形况下,如执行了耗时的输入/输出操作时,sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种: 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。,若想再此进入就绪状态就需要使用notify()方法唤醒该线程。 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。 其他阻塞:通过调用线程的 sleep() 、或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 方法时间到了之后,join() 新加的线程运行结束后, I/O 处理完毕,线程重新转入就绪状态。
死亡状态: 一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
几个同一个类型的线程实例并发执行用内存中的同一个run方法(有各自栈,) 1.继承方式创建线程共享资源要显示传入 2.接口方式runnable实例默认共享(多线程对共享对象的引用)