线程的一些简单问题

1、首先说一下什么是线程,进程与线程的区别

进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程,线程的划分尺度小于进程,使得多线程程序的并发性提高。

进程在执行过程中拥有独立的内存单元,而多个线程共享内存,减少切换次数,从而极大的提高了程序的运行效率,每个独立的进程有一个程序运行的入口,顺序执行序列和程序的出口,但线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。多线程的意义在于一个应用程序中,有多个执行部分可以同时执行,但操作系统并没有将多个线程看作多个独立的应用。

进程拥有独立的地址空间,一个进程崩溃以后,在保护模式下不会对其它进程产生影响,而线程只是进程中的不同执行路径,线程有自己的堆栈和局部变量,但线程之间没有独立的地址空间,一个线程死掉等于整个进程死掉,多进程的程序要比多线程的程序健壮。

线程是进程的一个实体,是CPU调度和分派的基本单位,是比程序更小的能独立运行的内存单元,同一进程中的多个线程可以并发执行。

笔试遇到的一个笔试题目:

在计算机操作系统中,信号量可以来保证两个或多个关键代码段不被并发调用,而在进入一个关键代码段之前,线程必须获取到一个信号量,现假设4个进程共享同一程序段,而且每次最多允许3个进程进入该程序段,则信号量变化范围-1~3,因为,允许3个进程同时进入,信号量s设为3,当3个线程进入后s=0,第4个再进s=-1  所以是-1~3。

后台线程:为其他线程提供服务的线程,也叫守护线程

前台线程:接受后台线程服务的线程

线程的五种状态以及状态的转换:创建、就绪、运行、阻塞、死亡。

创建多线程的方式:

  1. 继承Thread类创建线程类,重写run()方法
    1. 调用步骤,创建继承类的对象t,执行t.start()
  2. 通过Runnable接口创建线程类,重写run()方法
    1. 调用步骤,创建实现类的对象r,创建Thread对象传入参数(r),通过start()方法启动
  3. 通过Callable和Future创建线程,重写call()方法
    1. 创建线程池
    2. 创建接收结果的列表组合
    3. 创建线程对象
    4. 将线程对象提交到线程池中,并返回结果接收
    5. 将返回结果加入结果集合
    6. 关闭线程池
  4. 基于线程池的execute(),创建临时线程
    1. 创建线程池
    2. 调用线程池的execute()方法
    3. 用匿名内部类的方法创建Runnable对象,重写run()方法

线程池相关问题:

在执行一个异步任务或并发任务时,往往是通过直接new Thread()方法来创建新的线程,这样做弊端较多,更好的解决方案是合理地利用线程池,线程池的优势很明显,如下:

降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;

提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;

方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;

更强大的功能,线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。

java通过Executors提供四种线程池,分别为:

newCachedThreadPool:创建一个可缓存的无界线程池,如果线程池长度超过处理需要,可灵活回收空线程,若无可回收,则新建线程。当线程池中的线程空闲时间超过60s,则会自动回收该线程,当任务超过线程池的线程数则创建新的线程,线程池的大小上限为Integer.MAX_VALUE,可看作无限大。

newFixedThreadPool:创建一个指定大小的线程池,可控制线程的最大并发数,超出的线程会在LinkedBlockingQueue阻塞队列中等待

newScheduledThreadPool:创建一个定长的线程池,可以指定线程池核心线程数,支持定时及周期性任务的执行

newSingleThreadExecutor:创建一个单线程化的线程池,它只有一个线程,用仅有的一个线程来执行任务,保证所有的任务按照指定顺序(FIFO,LIFO,优先级)执行,所有的任务都保存在队列LinkedBlockingQueue中,等待唯一的单线程来执行任务。

创建线程池,在构造一个新的线程池时,必须满足下面的条件:

corePoolSize(线程池基本大小)必须大于或等于0;当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,才会根据是否存在空闲线程,来决定是否需要创建新的线程。除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。

maximumPoolSize(线程池最大大小)必须大于或等于1,必须大于或等于corePoolSize;当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,才会根据是否存在空闲线程,来决定是否需要创建新的线程。除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。

keepAliveTime(线程存活保持时间)必须大于或等于0;默认情况下,当线程池的线程个数多于corePoolSize时,线程的空闲时间超过keepAliveTime则会终止。但只要keepAliveTime大于0,allowCoreThreadTimeOut(boolean) 方法也可将此超时策略应用于核心线程。另外,也可以使用setKeepAliveTime()动态地更改参数。

workQueue(任务队列)不能为空;用于传输和保存等待执行任务的阻塞队列。可以使用此队列与线程池进行交互:

如果运行的线程数少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。

如果运行的线程数等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。

如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

threadFactory(线程工厂)不能为空,默认为DefaultThreadFactory类,用于创建新线程。由同一个threadFactory创建的线程,属于同一个ThreadGroup,创建的线程优先级都为Thread.NORM_PRIORITY,以及是非守护进程状态。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号);

handler(线程饱和策略)不能为空,默认策略为ThreadPoolExecutor.AbortPolicy。当线程池和队列都满了,则表明该线程池已达饱和状态。

ThreadPoolExecutor.AbortPolicy:处理程序遭到拒绝,则直接抛出运行时异常 RejectedExecutionException。(默认策略)

ThreadPoolExecutor.CallerRunsPolicy:调用者所在线程来运行该任务,此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。

ThreadPoolExecutor.DiscardPolicy:无法执行的任务将被删除。

ThreadPoolExecutor.DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重新尝试执行任务(如果再次失败,则重复此过程)。

unit(存活时间的单位):时间单位,分为7类,从细到粗顺序:NANOSECONDS(纳秒),MICROSECONDS(微妙),MILLISECONDS(毫秒),SECONDS(秒),MINUTES(分),HOURS(小时),DAYS(天);

多线程有什么用?

1)发挥多核CPU 的优势

随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4 核、8 核甚至 16 核的也都不少见,如果是单线程的程序,那么在双核 CPU 上 就浪费了 50%, 在 4 核 CPU 上就浪费了 75%。单核 CPU 上所谓的"多线程"那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快, 看着像多个线程"同时"运行罢了。多核 CPU 上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU 的优势来,达到充分利用CPU 的目的。

2)防止阻塞

从程序运行效率的角度来看,单核 CPU 不但不会发挥出多线程的优势,反而会因为在单核CPU 上运行多线程导致线程上下文的切换,而降低程序整体的效率。但

是单核 CPU 我们还是要应用多线程,就是为了防止阻塞。试想,如果单核 CPU 使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。

3)便于建模

这是另外一个没有这么明显的优点了。假设有一个大的任务 A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务 A 分解成几个小任务,任务B、任务 C、任务 D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。

启动线程方法 start()和 run()有什么区别?

只有调用了 start()方法,才会表现出多线程的特性,不同线程的 run()方法里面的代码交替执行。如果只是调用 run()方法,那么代码还是同步执行的,必须等待一个线程的 run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其 run()方法里面的代码。

线程中的 wait()和 sleep()方法有什么区别?

这个问题常问,sleep 方法和 wait 方法都可以用来放弃 CPU 一定的时间,不同点在于如果线程持有某个对象的监视器,sleep 方法不会放弃这个对象的监视器,wait 方法会放弃这个对象的监视器

怎么终止一个线程?如何优雅地终止线程?

stop 终止,不推荐。

Java 中 notify 和 notifyAll 有什么区别?

notify()方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它才有用武之地。

而 notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。

什么是阻塞式方法?

阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket 的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前

线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。

什么是守护线程?有什么用?

什么是守护线程?与守护线程相对应的就是用户线程,守护线程就是守护用户线程,当用户线程全部执行完结束之后,守护线程才会跟着结束。也就是守护线程必 须伴随着用户线程,如果一个应用内只存在一个守护线程,没有用户线程,守护线程自然会退出。

一个线程运行时发生异常会怎样?

如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler 是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造 成线程中断的时 候 JVM 会 使 用 Thread.getUncaughtExceptionHandler() 来 查 询 线程 的UncaughtExceptionHandler 并 将 线 程 和 异 常 作 为 参 数 传 递 给 handler 的 uncaughtException()方法进行处理。

线程 yield()方法有什么用?

Yield 方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃 CPU 占用而不能保证使其它线程一定 能占用 CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。

提交任务时线程池队列已满会时发会生什么?

当线程数小于最大线程池数 maximumPoolSize 时就会创建新线程来处理,而线程数大于等于最大线程池数 maximumPoolSize 时就会执行拒绝策略。

什么是自旋锁?

自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时才能进入临界区。

Runnable 和 Thread 用哪个好?

Java 不支持类的多重继承,但允许你实现多个接口。所以如果你要继承其他类,也为了减少类之间的耦合性,Runnable 会更好。​​​​​​​

synchronized,viotatile以及java中lock区别

synchronized是关键字(无法判断获取锁的状态),自动释放锁,处理代码少量同步问题。lock是一个java类(可以判断获取锁的状态),手动释放锁(finally中,unlock方法),lock可重入,可判断,可公平,适合大量同步代码同步的问题。synchronized使用更多,可以修饰方法以及代码块,多线程访问阻塞,可以保证可见性及原子性,主要解决多个线程之间访问资源的同步性、原子性、有序性、可见性。Volatile只能用于变量多线程访问不会阻塞,可保证数据可见性,不可以保证原子性,主要针对变量在多个线程之间的可见性、有序性、不能保证线程安全

怎么检测一个线程是否拥有锁?

java.lang.Thread#holdsLock 方法

线程同步需要注意什么?

1、尽量缩小同步的范围,增加系统吞吐量。

2、分布式同步锁无意义,要使用分布式锁。

3、防止死锁,注意加锁顺序。

说几个常用的 Lock 接口实现锁。

ReentrantLock、ReadWriteLock

ThreadLocal 是什么?有什么应用场景?

ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。用来解决数据库连接、Session 管理等。

ReadWriteLock 有什么用?

ReadWriteLock 是一个读写锁接口,ReentrantReadWriteLock 是 ReadWriteLock 接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。

FutureTask 是什么?

FutureTask 表示一个异步运算的任务,FutureTask 里面可以传入一个 Callable 的具1体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值