多线程有什么用?
现在我们的电脑的CPU都是多核心的,如果我们还是采用单线程的方式去编程,那么我们电脑上的CPU核心只有一个会被利用上,其余都都会被闲置。为了提高cpu核心的利用率,我们可以采用多线程编程。让每个核心在同一时刻都只能有一个线程在上面运行。好比,你雇佣了4个工人,如果你只交代了一个任务,那么这四个工人中,只会有一个工人在工作,其他三个就被闲置了,是资源的浪费。如果我们同时布置四个任务,或者更多的任务,那么这四个工人就都会被利用上。也就提高了我们的任务效率了。
线程和进程有什么区别?
- 一个线程只能属于一个进程;而一个进程可以拥有多个线程(至少只有一个);线程依赖于进程而存在。
- 进程是系统资源分配的最小单位,而线程是CPU调度的最小单位。
- 进行拥有独立的内存单元,而多个线程共享进行的内存资源。
- 进程编程调试简单可靠,但是创建、切换、销毁开销大;而线程编程则恰恰相反。
- 进程之间不会相互影响,一个进程的宕机,并不会影响到其他进程;但是一个进程中的一个线程的挂掉,可能就是导致其他线程也会挂掉。
Java实现多线程有哪几种方式?
- 继承Thread类实现多线程。
- 实现Runnable接口实现多线程。
- 使用ExecutorServicde、Callable、Future实现有返回值的多线程。
启动线程方法start()和run()有什么区别?
只有调用了start()方法,才是真正开启多线程,此时多个线程的run()体内的代码会同时执行。但是如果只是执行run(),那么定义的多个run()体内的代码还只是在同一个线程中按顺序挨个执行。
一个线程的生命周期有哪几种状态?它们之间如何流转的?
- NEW:就是刚刚创建的线程,还没有被调用。
- RUNNABLE:即已经进入可以运行的状态,或者已经正在运行的线程。
- BLOCKED:还没获取到锁,被阻塞的线程。例如碰到synchronized关键字。
- WAITING:表示线程处于无限等待的状态,调用了Object.wait()、Thread.sleep()、Thread.join()方法,等待唤醒
- TIMED_WAITING:表示线程处于有限时间等待的状态的,调用了Object.wait(long millions)、Thread.sleep(long millions)、Thread.join(long millions)方法
TERMINATED:表示线程已经执行完毕了。需要注意的是,线程一旦进行RUNNABLE,就无法回到NEW状态了。一旦进入了TERMINATED状态,就无法回到任何其他状态了。
推荐文章:https://learn.lianglianglee.c...
Java多线程sleep和wait的区别?
- 使用方面:sleep属于Thread类的方法,而wait属于Object类的方法;sleep()可以在任意地方使用,而wait只能在同步方法或代码块中使用。
- CUP及锁资源释放:
sleep()、wait()都会暂定当前线程,让出CPU的执行时间。但是sleep()并不会释放锁,而wait()方法会释放锁资源。 - 异常捕获方面:sleep需要捕获或抛出异常,而wait()方法则不需要。
Java实现多线程同步的几种方法
- 同步方法:用synchronized关键字修饰的方法。
- 同步代码块:用synchronized关键字修饰的代码块。
- 同步变量:使用volatile修饰的变量
- 重入锁。
什么是线程死锁,如何避免线程死锁?
一个线程或者多线程同时被阻塞,他们中的一个或者多个都在等待某个资源的释放。由于线程被无限期的阻塞,程序不能正常的结束。
Java多线程之间如何进行通信的?
- 多个线程通过synchronized关键字这种方式来实现线程间的通信
- while轮询配合volatile关键字的方式
- wait/notify机制
线程怎样拿到返回结果?
实现Callable接口,结合ExecutorService、future拿到返回结果
violatile关键字的作用?
- 保证可见性:即一个线程对变量的修改,其他线程可以立即看到。
- 可以禁止指令重排序,保证了原子性。
Java如何保证线程的执行顺序?
使用Thread.join()方法
怎么控制同一时间只有3个线程运行?
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
为什么要使用线程池?
过多的线程创建和销毁会大量的浪费系统资源,这个时候我们可以采用线程池的方式来复用已经创建的线程。而不是创建一个线程,用完就丢了。这样可以极大的节省系统资源。
Java中常见的几种线程池?
- newCachedThreadPool:可缓存的线程池,如果线程池不够用则自动扩容,如果线程池超过线程所需的长度,则自动回收。
- newFixedThreadPool:固定长度的线程池,指定设定一个固定长度的线程池。如果说执行的任务超过线程池的长度,则进入等待队列中。
- newScheduledThreadPool:支持定时周期执行任务的固定长度的线程池。
- newSingleThreadExector:单线程化的线程池,此线程池里面只有一个线程,保证所有的任务都按照先进先出的顺序执行。
线程池启动线程 submit()和 execute()方法有什么不同?
- execute()是没有返回值的,但是性能会更好一些。
- 如果需要得到线程的结果,则需要调用submit()方法,还可以捕获异常。
CyclicBarrier 和 CountDownLatch 的区别?
CyclicBarrier和CountDownLatch带个类都是在java.util.concurrent包下的,都可以表示代码运行到某个点,但是两者还是有区别的:
- CyclicBarrier的某个线程运行到某个点上之后,改线程即停止运行,知道所有的线程都到达了这个点,所有线程才重新运行;CountDownLatch则不是,某线程运行到某个点之后,只是给某个数-1而已,该线程继续运行。
- CylicBarrier只能唤起一个任务,而CountDownLatch可以唤起多个任务。
- CycliBarrier可重用,而CountDownLatch不可重用,计数值为0改CountDownLatch就不可再用了。
什么是活锁、饥饿、无锁、死锁?
- 死锁:多个线程都占有资源的锁,但谁都不释放,谁都无法获得资源。
- 活锁:多个线程都能拿到资源,但是拿到资源后,谁都不执行,而是都相互释放的情况。
- 饥饿:线程是分优先级的,当高优先级的线程一直能拿到资源,而低优先级的拿不到。
- 无锁:即没有对资源进行锁定,即所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。
什么是原子性、可见性、有序性?
- 原子性:指一个线程的操作是不能被其他线程所打断的,同一时间只能有一个线程对一个常量进行操作。
- 可见性:某一个线程修改了一个共享变量的值,要保证其他线程可以立即看到此共享变量修改后的值。
- 有序性:JVM和操作系统为了优化程序、提高CPU的处理能力,会进行指令重排序,有序性就是保证代码的执行顺序不能被指令重排所打乱。
什么是守护进程?有什么用?
守护线程是区别于用户线程,用户线程即我们手动创建的线程,而守护线程是程序运行的时候在后台提供一种通用服务的线程。垃圾回收线程就是典型的守护线程。当用户线程停止时,其对应的守护线程也会停止。
一个线程运行时发生异常会怎样?
如果异常没有被捕获改线程将会停止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然终端情况的一个内嵌接口。当一个未捕获异常将造成线程终端的时候JVM会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法进行处理。
线程yield()方法有什么用?
yeild()方法可以让当前正在执行的线程进入暂停状态,转而让拥有相同优先级的线程获取执行机会。但是yeild()只能保证当前线程放弃cpu使用权,但是不能保证其他线程一定可以获得cpu使用权。有可能执行了yeild得线程刚停止,下一秒就又开始被执行了。
什么是重入锁?
就是指一个锁可以被同一个对象反复获取。
Synchronized 有哪几种用法?
- 用在类上
- 用在方法上
- 用在代码块上
Fork/Join 框架是干什么的?
将大任务自动分散为小任务,并发执行这些分散得到的小任务,最后合并小任务的执行结果。
线程数过多会造成什么异常?
- 线程的生命周期开销非常高
- 消耗过多的 CPU 资源
- 降低稳定性
Java线程安全的集合和线程不安全的集合都分别有哪些?
线程安全
- Vector
- HashTable
- ConcurrentHashMap
Stack
线程不安全
- HashMap
- ArrayList
- LinkedList
- HashSet
- TreeSet
- TreeMap
谈谈HashMap的线程安全上的使用问题
首先HashMap本身是线程不安全的,所以我们在平时使用HashMap的时候要注意一下他的线程安全问题:
- 当我们在方法内使用定一个HashMap来使用,这个时候方法内的局部变量属于当前线程的变量,其他的线程是无论如何都访问不到的,所以是不存在线程安全问题的。
- 但如果我们在一个单例对象中定一个成员变量HashMap,这个时候就存在线程安全的问题了。当多个线程同时访问这个单例对象的成员变量HashMap,就可能导致HashMap值出现异常。
什么是CAS算法,它存在哪方面问题?
CAS算法即:compare and swap(比较与交换)算法。
CAS算法有三个重要的概念:
- 内存值V
- 旧的预期值A
将要修改得到的新值U
只有当旧的预期值A等于内存值V的时候,才会将A修改为修改值U,其他情况都不做任何操作。
CAS存在的问题主要有两点:- ABA问题:即一个线程将内存值V修改为B,然后另一个线程又将内存值V修改为A。在给旧的预期值A赋值的比较环节时,进行内存值V与旧的预期值A比较时,发现内存值V没有变化还是等于A。但是其实已经被修改过了。这就是ABA问题。解决办法:给变量加上版本号,每次修改都加上+1,再进行赋值前的比较环节,加上对版本的比较,如果发现版本号不一致了,就不给予赋值操作。
- 循环时间太长,会导致开销太大。CAS算法需要不断地自旋来读取最新的内存值,长时间读取不到就会造成不必要的CPU开销。
怎么检测一个线程是否拥有锁?
使用java.lang.Thread类下的静态holdsLock()方法,如果当前线程拥有某个对象的锁时,它会返回true。
如何排查多线程死锁问题?
使用java自带的jstack命令进行排查
- 使用
ps -ef | grep java
命令找到我们对应项目的java进程,记录其进程pid号 - 使用
top -H -p pid号
,查找我们项目java进程中cpu使用率最高的线程,记录其线程pid号 - 将刚才得到的线程pid号转换为16进程的,然后执行
jstack pid | grep '线程pid16进制号'
找到对应的堆栈报错信息,接下来只要分析错误信息即可。
线程同步需要注意什么?
- 尽量缩小同步的范围,增加系统吞吐量
- 防止死锁,注意加锁的顺序。
线程 wait()方法使用有什么前提?
要在同步块中使用。
Fork/Join 框架使用有哪些要注意的地方?
- 如果任务拆解的很深,那么系统中线程数据堆积的会过多,将严重导致系统性能的下降。
- 如果函数的调用栈很深的话,会导致栈内存的溢出。
保证“可见性”有哪几种方式?
- Viotatile:当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
- synchronized和Lock:另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
Lock 接口与 synchronized 关键字的区别
- 实现:synchronized 关键字基于 JVM 层面实现,JVM 控制锁的获取和释放。Lock 接口基于 JDK 层面,手动进行锁的获取和释放;
- 使用:synchronized 关键字不用手动释放锁,Lock 接口需要手动释放锁,在 finally 模块中调用 unlock 方法;
- 锁获取超时机制:synchronized 关键字不支持,Lock 接口支持;
- 获取锁中断机制:synchronized 关键字不支持,Lock 接口支持;
- 释放锁的条件:synchronized 关键字在满足占有锁的线程执行完毕,或占有锁的线程异常退出,或占有锁的线程进入 waiting 状态才会释放锁。Lock 接口调用 unlock 方法释放锁;
- 公平性:synchronized 关键字为非公平锁。Lock 接口可以通过入参自行设置锁的公平性。
能谈谈ThreadLocal呢?
ThreadLocal又被称为线程局部变量,主要是为了解决多线程并发产生的数据不一致问题。ThreadLocal为每一个线程都提供了一个变量副本,避免了多个线程同时访问同一个变量的问题。这样做会增加内存的占用,但是降低了多线程并发控制的难度。
谈谈ReadWriteLock?
ReadWriteLock是一个读写锁的父接口,而ReentrantReadWriteLocak是其接口的一个具体实现类,它实现了锁的读写分离。读锁是共享的,但是写锁是独占的。多个线程同时进行读操作是并不会有什么影响,但是多线个线程同时进行写操作时会进行加锁,只允许同时只有一个线程对资源进行操作。
FutureTask是什么?
FutureTask是一个异步运算的任务,FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果结果进行等待获取、判断是否已经完成、取消任务等操作。
不可变对象对多线程有什么帮助?
不可变对象保证了对象的内存可见性,对不可变对象的读取不需要进行额外的同步手段,提升了代码执行效率。
多线程上下文切换是什么意思?
多线程的上下文切换是指cpu控制权由一个已经正在运行的线程切换到另外一个就绪等待获取cpu执行权的线程的过程。
Java 中用到了什么线程调度算法?
抢占式。一个线程用完cpu之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。
Thread.sleep(0)的作用是什么?
Thread.sleep(0)可以手动触发一次cpu时间片的分配动作,因为java采用的抢占式的线程调度算法,所以可能会出现某个优先级高的线程常常能够拿到cpu的执行权,但是某些优先级低的线程可能会根本拿不到cpu的执行权。为了让那些优先级低的线程也能够至少执行一次,我们可以通过调用Thread.sleep(0)来平衡cpu时间片的分配。
Java 内存模型是什么,哪些区域是线程共享的,哪些是不共享的?
线程共享的只有两个:堆和方法区
- 堆:目的是用来存放创建的所有对象实例.
- 方法区:用来存储类、常量、静态变量等数据信息.
线程不共享的有:栈、本地方法栈、程序计数栈
- 程序技术器:用来保存当前线程所执行的字节码位置
- 栈:每个方法创建的时候同时也会创建一个栈,用来存放一些关于方法的信息,例如局部变量、方法出入口等。
- 本地方法栈:与栈类似,只不过它存放的是关于调用native方法的相关信息。
什么是乐观锁和悲观锁?
乐观锁和悲观锁是两种思想,用于解决并发场景下的数据竞争问题。
乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。
悲观锁:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。
除了CAS,版本号机制也可以用来实现乐观锁。版本号机制的基本思路是在数据中增加一个字段version,表示该数据的版本号,每当数据被修改,版本号加1。当某个线程查询数据时,将该数据的版本号一起查出来;当该线程更新数据时,判断当前版本号与之前读取的版本号是否一致,如果一致才进行操作。
常用的手段有分布式锁和 sync 关键字。
首先乐观锁和悲观锁只是两种思想,目的是用来解决并发场景下的数据竞争问题。
乐观锁
乐观锁和悲观锁是两种概念,主要用于解决并发场景的下的数据不一致的问题。
- 乐观锁:乐观锁在操作数据时非常乐观,它会倾向于认为不会出现多个线程同时修改一个数据的情况。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。
悲观锁的实现方式是加锁,加锁即可以是对代码块加锁(如使用Java的synchronized关键字),也可以对数据加锁(如mysql中的排它锁) 悲观锁:悲观锁在操作数据时比较悲观,会倾向于认为很可能会出现多个线程同时修改一个数据的情况。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。
乐观锁的实现方式主要有两种:CAS机制和版本号机制。- CAS机制:比较与交换,内存值A,预期的旧值U,预期修改的新值B,只有等内存值A与U相同时,才会执行修改为B的操作。但会出现ABA问题,即A值先被修改为了B,最后又被修改为了A。表明上值没有变,但是其实值已经被修改了。
- 版本号机制:添加一个版本号的字段,每次修改都+1,只有版本号与修改之前一致,才执行操作。
同步方法和同步块,哪种更好?
同步块,这意味着同步块之外得到代码是异步执行的,这比同步整个方法更提升代码的效率。
同步的范围越小越好。
什么是自旋锁?
如果一个线程在获取锁的时候,发现锁已经被其他线程占用了。那么这个线程就会采用循环等待的方式,等待其他线程释放锁资源。在循环等待的过程中,会一直不断的判断锁是否成功被获取得,拿到锁立即退出循环。
Runnable 和 Thread 用哪个好?
实现Runnable接口比继承Thread类的方式更好:
- 可以避免由于Java单继承带来的局限性。
- 可以实现业务执行逻辑和数据资源的分离。
- 可以与线程池配合使用,从而管理线程的生命周期。
Java 中 notify 和 notifyAll 有什么区别?
notify方法是随机唤醒对象的等待池中的一个线程,进入锁池;notifyAll()唤醒对象的等待池中的所有线程,进入锁池。
为什么wait/notify/notifyAll/这些方法不在thread类中?
Java提供的锁是对象级的,而不是线程级的。每个对象都有锁,通过线程获得,如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在thread类中,线程正在等待的是哪个锁就不明显了。简单的锁,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
为什么wait和notify方法要在同步块中调用?
主要是因为java api强制要求这样做,如果你不这么做,你的代码会抛出IlegalMonitorStateException异常。还有一个原因是为了避免wait和notify之间产生竞太条件。
为什么你应该在循环中检查等待条件?
处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。因此,当一个车等待线程醒来时,不能认为它原来的等待状态仍然是有效的,在notify()方法调用之后和等待线程醒来之前这段时间紧他可能会改变。这就 是在循环中使用wait()方法效果更好的原因,你可以在eclipse中创建模板调用wait和notify试一试。
Java中堆和栈有什么不同?
功能不同:
- 栈内存用来存储局部变量和方法调用。
- 而堆内存用来存储Java中的对象,无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。
共享性不同:
- 栈内存是线程私有的。
- 堆内存是所有线程共享的。
异常错误不同:
- 栈空间不足时抛出异常:java.lang.StackOverFlowError。
- 堆空间不足时抛出异常:java.lang.OutOfMemoryError。
- 空间大小:
栈的空间大小远远小于堆的。
什么是阻塞式方法?
阻塞式方法是指在方法结果返回之前,当前方法的线程会被挂起,一直等待方法结果的返回,期间是不做其余事情的。例如ServerSocket类的accept()方法就是一个阻塞式方法,它会一直等待客户端的连接回应。除了阻塞式方法,还有非阻塞式方法,即异步方法。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。