邯郸网站建设有哪些,客户案例 网站设计,网站规划的解释,推广方案模板1.Java中实现多线程有几种方法
创建线程的常用的几种方式#xff1a;
继承Thread类 实现Runnable接口 #xff08;重写run方法#xff0c;无返回值#xff09; 实现Callable接口#xff08; JDK1.5,重写call方法#xff0c;可以自定义返回值 #xff09; 线程池方…1.Java中实现多线程有几种方法
创建线程的常用的几种方式
继承Thread类 实现Runnable接口 重写run方法无返回值 实现Callable接口 JDK1.5,重写call方法可以自定义返回值 线程池方式创建
2.线程的几个基本状态
新建状态New新创建了一个线程对象。
就绪状态Runnable线程对象创建后其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中变得可运行等待获取CPU的使用权。
运行状态Running就绪状态的线程获取了CPU执行程序代码。
阻塞状态Blocked阻塞状态是线程因为某种原因放弃CPU使用权暂时停止运行。直到线程进入就绪状态才有机会转到运行状态。阻塞的情况分三种
等待阻塞运行的线程执行wait()方法JVM会把该线程放入等待池中。(wait会释放持有的锁) 同步阻塞运行的线程在获取对象的同步锁时若该同步锁被别的线程占用则JVM会把该线程放入锁池中。 其他阻塞运行的线程执行sleep()或join()方法或者发出了I/O请求时JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时线程重新转入就绪状态。注意,sleep是不会释放持有的锁
死亡状态Dead线程执行完了或者因异常退出了run()方法该线程结束生命周期。
3.如何停止一个正在运行的线程
使用退出标志使线程正常退出也就是当run方法完成后线程终止。
使用stop方法强行终止但是不推荐这个方法因为stop和suspend及resume一样都是过期作 废的方法。
使用interrupt方法中断线程。
4.notify()和notifyAll()有什么区别
notify可能会导致死锁而notifyAll则不会 任何时候只有一个线程可以获得锁也就是说只有一个线程可以运行synchronized 中的代码 使用notifyall,可以唤醒 所有处于wait状态的线程使其重新进入锁的争夺队列中而notify只能唤醒一个。 wait() 应配合while循环使用不应使用if务必在wait()调用前后都检查条件如果不满足必须调 用notify()唤醒另外的线程来处理自己继续wait()直至条件满足再往下执行。 notify() 是对notifyAll()的一个优化但它有很精确的应用场景并且要求正确使用。不然可能导致死锁。正确的场景应该是 WaitSet中等待的是相同的条件唤醒任一个都能正确处理接下来的事 项如果唤醒的线程无法正确处理务必确保继续notify()下一个线程并且自身需要重新回到 WaitSet中.
5.sleep()和wait() 有什么区别
对于sleep()方法我们首先要知道该方法是属于Thread类中的。而wait()方法则是属于Object类 中的。 sleep()方法导致了程序暂停执行指定的时间让出cpu该其他线程但是他的监控状态依然保持着当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中线程不会释放对象 锁。 当调用wait()方法的时候线程会放弃对象锁进入等待此对象的等待锁定池只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
6.Thread 类中的start() 和 run() 方法有什么区别
start()方法被用来启动新创建的线程而且start()内部调用了run()方法这和直接调用run()方法的 效果不一样。当你调用run()方法的时候只会是在原来的线程中调用没有新的线程启动start() 方法才会启动新线程。
7.为什么wait, notify 和 notifyAll这些方法不在thread类里面
明显的原因是JAVA提供的锁是对象级的而不是线程级的每个对象都有锁通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中线 程正在等待的是哪个锁就不明显了。简单的说由于waitnotify和notifyAll都是锁级别的操作所 以把他们定义在Object类中因为锁属于对象。 synchronized 和 ReentrantLock 有什么不同
用法不同synchronized 可以用来修饰普通方法、静态方法和代码块而 ReentrantLock 只能用于代码块。
获取锁和释放锁的机制不同synchronized 是自动加锁和释放锁的而 ReentrantLock 需要手动加锁和释放锁。
锁类型不同synchronized 是非公平锁而 ReentrantLock 默认为非公平锁也可以手动指定为公平锁。
响应中断不同ReentrantLock 可以响应中断解决死锁的问题而 synchronized 不能响应中断。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockInterrupt {static Lock lockA new ReentrantLock();static Lock lockB new ReentrantLock();public static void main(String[] args) throws InterruptedException {// 线程 1先获取 lockA 再获取 lockBThread t1 new Thread(() - {try {// 先获取 LockAlockA.lockInterruptibly();// 休眠 10 毫秒TimeUnit.MILLISECONDS.sleep(100);// 获取 LockBlockB.lockInterruptibly();} catch (InterruptedException e) {System.out.println(响应中断指令);} finally {// 释放锁lockA.unlock();lockB.unlock();System.out.println(线程 1 执行完成。);}});// 线程 2先获取 lockB 再获取 lockAThread t2 new Thread(() - {try {// 先获取 LockBlockB.lockInterruptibly();// 休眠 10 毫秒TimeUnit.MILLISECONDS.sleep(100);// 获取 LockAlockA.lockInterruptibly();} catch (InterruptedException e) {System.out.println(响应中断指令);} finally {// 释放锁lockB.unlock();lockA.unlock();System.out.println(线程 2 执行完成。);}});t1.start();t2.start();TimeUnit.SECONDS.sleep(1);// 线程1执行中断t1.interrupt();}
}
底层实现不同synchronized 是 JVM 层面通过监视器实现的而 ReentrantLock 是基于 AQS 实现的。
8.谈谈Lock锁底层实现原理
底层基于AQSCASLockSupport锁实现
9.AQS是什么
AQS抽象的队列同步器是整个JUC体系的基石通过内置的FIFO队列来完成资源获取线程的排队工作并通过一个int类型的变量表示持有锁的状态当有线程获取不到锁时就将线程加入该队列中通过CAS自旋和LockSupport.part()的方式维护state变量达到并发同步的效果。
10.AQS的核心原理
AQS使用一个violatile的int类型的成员变量来表示同步状态通过内置的FIFO队列来完成资源获取的排队工作将每条要抢占资源的线程封装成一个Node节点来完成锁的分配通过CAS完成对state值的修改核心就是stateCLH带头节点的双端队列
11.ReentrantLock获取锁的过程
公平锁当一个线程在尝试获得锁时如果锁的state0且等待队列为空则获得锁否则进入队列尾当持有资源的线程释放锁时唤醒队首线程 非公平锁当一个线程在尝试获得锁时直接尝试CAS成功则占有锁否则进入队列尾当持有资源的线程释放锁时唤醒队首线程如果队首线程获得锁成功则弹出否则不弹出
12.有三个线程T1,T2,T3,如何保证顺序执行
在多线程中有多种方法让线程按特定顺序执行你可以用线程类的join()方法在一个线程中启动另一个线程另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2T2调用T1)这样T1就会先完成而T3最后完成。
13.Thread类中的yield方法有什么作用
Yield方法可以暂停当前正在执行的线程对象让其它有相同优先级的线程执行。它是一个静态方法 而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU执行yield()的线程有可 能在进入到暂停状态后马上又被执行。
14.线程池创建需要的那几个核心参数的含义
ThreadPoolExecutor 最多包含以下七个参数
corePoolSize线程池中的核心线程数 maximumPoolSize线程池中最大线程数 keepAliveTime闲置超时时间 unitkeepAliveTime 超时时间的单位时/分/秒等 workQueue线程池中的任务队列 threadFactory为线程池提供创建新线程的线程工厂 rejectedExecutionHandler线程池任务队列超过最大值之后的拒绝策略
15.ThreadPoolExecutor 有哪些常用的方法
ThreadPoolExecutor有如下常用方法
submit()/execute()执行线程池 shutdown()/shutdownNow()终止线程池 isShutdown()判断线程是否终止 getActiveCount()正在运行的线程数 getCorePoolSize()获取核心线程数 getMaximumPoolSize()获取最大线程数 getQueue()获取线程池中的任务队列 allowCoreThreadTimeOut(boolean)设置空闲时是否回收核心线程这些方法可以用来终止线程池、线程池监控等。
16.Java线程池中submit() 和 execute()方法有什么区别
两个方法都可以向线程池提交任务execute()方法的返回类型是void它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象它定义在ExecutorService接口中它扩展了 Executor接口其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些 方法。
17.常用的线程池有哪些
newSingleThreadExecutor创建一个单线程的线程池此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 newFixedThreadPool创建固定大小的线程池每次提交一个任务就创建一个线程直到线程达到线程池的最大大小。 newCachedThreadPool创建一个可缓存的线程池此线程池不会对线程池大小做限制线程池大小完全依赖于操作系统或者说JVM能够创建的最大线程大小。 newScheduledThreadPool创建一个大小无限的线程池此线程池支持定时以及周期性执行任务的需求。 newSingleThreadExecutor创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
18.线程池中的workQueue有哪几种
1、LinkedBlockingQueue一个无界缓存等待队列。当前执行的线程数量达到corePoolSize的数量时剩余的元素会在阻塞队列里等待。所以在使用此阻塞队列时maximumPoolSizes就相当于无效了每个线程完全独立于其他线程。生产者和消费者使用独立的锁来控制数据的同步即在高并发的情况下可以并行操作队列中的数据。 2、SynchronousQueue无缓冲等待队列是一个不存储元素的阻塞队列会直接将任务交给消费者必须等队列中的添加元素被消费后才能继续添加新的元素。拥有公平FIFO和非公平(LIFO)策略使用SynchronousQueue阻塞队列一般要求maximumPoolSizes为无界(Integer.MAX_VALUE)避免线程拒绝执行操作。 3、ArrayBlockingQueue一个有界缓存等待队列可以指定缓存队列的大小当正在执行的线程数等于corePoolSize时多余的元素缓存在ArrayBlockingQueue队列中等待有空闲的线程时继续执行当ArrayBlockingQueue已满时加入ArrayBlockingQueue失败会开启新的线程去执行当线程数已经达到最大的maximumPoolSizes时再有新的元素尝试加入ArrayBlockingQueue时会报错。 4、DelayedWorkQueue其特点是内部元素并不是按照放入的时间排序而是会按照延迟的时间长短对任务进行排序内部采用的是“堆”的数据结构。之所以线程池 ScheduledThreadPool 和 SingleThreadScheduledExecutor 选择 DelayedWorkQueue是因为它们本身正是基于时间执行任务的而延迟队列正好可以把任务按时间进行排序方便任务的执行。
17.shutdownNow() 和 shutdown() 两个方法有什么区别
shutdownNow() 和 shutdown() 都是用来终止线程池的它们的区别是使用 shutdown() 程序不会报错也不会立即终止线程它会等待线程池中的缓存任务执行完之后再退出执行了 shutdown() 之后就不能给线程池添加新任务了shutdownNow() 会试图立马停止任务如果线程池中还有缓存任务正在执行则会抛出 java.lang.InterruptedException: sleep interrupted 异常。
18.线程池的工作原理
提交任务后先判断当前池中线程数是否小于 corePoolSize如果小于则创建新线程执行这个任务。 否则判断线程池任务队列是否已满如果没有满则添加任务到任务队列。 否则判断当前池中线程数是否大于 maximumPoolSize如果大于则执行预设拒绝策略。 否则创建一个线程执行该任务直至线程数达到maximumPoolSize达到后执行预设拒绝策略。
18.线程池创建之后会立即创建核心线程么
并不会立即创建核心线程而是等到有任务提交时才会开始创建线程除非调用了prestartCoreThread/prestartAllCoreThreads 事先启动核心线程。 prestartCoreThread 启动一个核心线程使其空闲等待工作。这会覆盖仅在执行新任务时启动核心线程的默认策略。如果所有核心线程都已启动此方法将返回 false。如果线程已启动则返回 true。 csharp复制代码public boolean prestartCoreThread() { return workerCountOf(ctl.get()) corePoolSize addWorker(null, true); }
prestartAllCoreThreads启动所有核心线程使它们空闲等待工作。这会覆盖仅在执行新任务时启动核心线程的默认策略。返回启动的线程数。 csharp复制代码public int prestartAllCoreThreads() { int n 0; while (addWorker(null, true)) n; return n; }
19.核心线程永远不会销毁么
其实核心线程只是一个动态概念在jdk中并没有给线程打上core标记。而在jdk1.6之前线程池会尽量保证会有corePoolSize个线程存活即使这些线程已经闲置了很长的时间这样会造成一部分资源浪费于是在1.6开始jdk提供了一个allowCoreThreadTimeOut方法用于控制核心线程是否被销毁。 注意 这种策略和corePoolSize0是有区别的
corePoolSize0在一般情况下只使用一个线程消费任务只有当并发请求特别多、等待队列都满了之后才开始用多线程。 allowsCoreThreadTimeOuttrue corePoolSize1在一般情况下就开始使用多线程corePoolSize 个当并发请求特别多等待队列都满了之后继续加大线程数。但是当请求没有的时候允许核心线程也终止。
综合来看其实corePoolSize0的效果基本等同于allowsCoreThreadTimeOuttruecorePoolSize1只是实现细节不同。
20.线程池的优点作用
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 提高响应速度。当任务到达时任务可以不需要等到线程创建就能立即执行。 提高线程的可管理性。线程是稀缺资源如果无限制的创建不仅会消耗系统资源还会降低系统的稳定性使用线程池可以进行统一的分配调优和监控。
21.线程池核心线程数怎么设置呢
分为CPU密集型和IO密集型
CPU这种任务消耗的主要是 CPU 资源可以将线程数设置为 NCPU 核心数1比 CPU 核心数多出 来的一个线程是为了防止线程偶发的缺页中断或者其它原因导致的任务暂停而带来的影响。一旦任务暂停CPU 就会处于空闲状态而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。 IO密集型 这种任务应用起来系统会用大部分的时间来处理 I/O 交互而线程在处理 I/O 的时间段内不会占 用 CPU 来处理这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中我们 可以多配置一些线程具体的计算方法是 核心线程数CPU核心数量*2。
22.线程池为什么需要使用阻塞队列
因为线程若是无限制的创建可能会导致内存占用过多而产生OOM并且会造成cpu过度切换。 创建线程池的消耗较高。