- 队列
主要遵循先进先出、后进后出的原则 - 进程与线程的区别?
答:进程是所有线程的集合,每一个线程是进程中的一条执行路径,线程只是一条执行路径。 - 为什么要用多线程?
答:提高程序效率 - 多线程的3大特性
1. 原子性
一个、多个操作 要么全部执行且执行的过程不会被任何因素打断,要么就都不执行。
2. 可见性
多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
3. 有序性
在并发时,程序的执行可能会出现乱序。给人的直观感觉就是:写在前面的代码,会在后面执行。有序性问题的原因是 因为程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致 - 多线程创建方式?
答:继承Thread或实现Runnable 接口 - 是继承Thread类好还是实现Runnable接口好?
答:Runnable接口好,因为实现了接口还可以继续继承。继承Thread类不能再继承。 - 你在哪里用到了多线程?
答:主要能体现到多线程提高程序效率。 举例:分批发送短信 - 什么是多线程安全?
答:当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。做读操作是不会发生数据冲突问题。 - 如何解决多线程之间线程安全问题?
答:使用多线程之间同步或使用锁(lock)。 - 为什么使用线程同步或使用锁能解决线程安全问题呢?
答:将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。被包裹的代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。 - 什么是多线程之间同步?
答:当多个线程共享同一个资源,不会受到其他线程的干扰。 - 什么是同步代码块?
答:就是将可能会发生线程安全问题的代码,给包括起来。只能让当前一个线程进行执行,被包裹的代码执行完成之后才能释放所,让后才能让其他线程进行执行。 - 多线程同步的分类?
使用同步代码块
private Object mutex = new Object();// 自定义多线程同步锁 public void sale() { synchronized (mutex) { if (trainCount > 0) { try { Thread.sleep(10); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票."); trainCount--; } }
使用同步函数,在方法上修饰synchronized 称为同步函数
静态同步函数public synchronized void sale() { if (trainCount > 0) { try { Thread.sleep(40); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票."); trainCount--; } }
方法上加上static关键字,使用synchronized 关键字修饰 为静态同步函数 。静态的同步函数使用的锁是 该函数所属字节码文件对象 - 同步代码块与同步函数区别?
答:同步代码使用自定锁(明锁)同步函数使用this锁
- 同步函数与静态同步函数区别? 例如: 一个静态方法和一个非静态静态怎么实现同步?
同步函数使用this锁
静态同步函数使用字节码文件,也就是类.class - 什么是多线程死锁?
答: 同步中嵌套同步,无法释放锁的资源
解决办法:同步中尽量不要嵌套同步 - Wait()与Notify ()区别?
Wait让当前线程有运行状态变为等待状态,和同步一起使用Notify 唤醒现在正在等待的状态,和同步一起使用
- Wait()与sleep()区别?
sleep()不会释放锁的资源、wait()会释放锁的资源
sleep()是时间到了就会被唤醒
Wati()可以用notify()唤醒 - Lock与Synchronized区别?
Lock锁属于手动控制 灵活性好
Synchronized同步锁属于自动控制 不利于扩展 - Condition用法
Condition的功能类似于在传统的线程技术中的,Object.wait()和Object.notify()的功能Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); lock.lock(); condition.await(); // 类似wait lock.unlock(); lock.lock(); condition.signalAll(); // 类似notifyAll lock.unlock();
- 如何停止线程?
使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
使用interrupt方法中断线程。 线程在阻塞状态 - 什么是守护线程
Java中有两种线程,一种是用户线程,另一种是守护线程。
当进程不存在或主线程停止,守护线程也会被停止。
使用setDaemon(true)方法设置为守护线程 - join()方法作用
join作用是让其他线程变为等待,只有当前线程执行完毕后,等待的线程才会被释放。
如: A线程调用了B线程的join()方法时,表示:A等待B方法先执行 - 有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行
public class Test { public static void main(String[] args) throws Exception{ Thread t1 = new Thread(new Runnable() { @Override public void run() { for( int i =0 ; i<10; i++) { System.out.println(Thread.currentThread().getName()+"打印的值--------"+i); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { t1.join(); // 表示t2线程会等待t1线程先执行 } catch (InterruptedException e) { e.printStackTrace(); } for( int i =0 ; i<10; i++) { System.out.println(Thread.currentThread().getName()+"打印的值--------"+i); } } }); Thread t3 = new Thread(new Runnable() { @Override public void run() { try { t2.join(); // 表示t3线程会等待t2线程先执行 } catch (InterruptedException e) { e.printStackTrace(); } for( int i =0 ; i<10; i++) { System.out.println(Thread.currentThread().getName()+"打印的值--------"+i); } } }); t1.start(); t2.start(); t3.start(); } }
- 线程生命周期
线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。
新建状态:调用Thread t1 = new Thread(new Thread01());
就绪状态:调用t1.start();方法 的等待cpu执行资源
运行状态:抢到cpu执行权,执行run()方法体
阻塞状态:调用wait()、sleep()方法。
死亡状态:方法执行完毕后 - 线程三大特性
多线程有三大特性,原子性、可见性、有序性
原子性:保证数据一致性,线程安全。
可见性:对另一个线程是否课件
有序性:线程之间执行有顺序 - 说说Java内存模型
简称(JMM), JMM决定了一个线程对共享变量写入的时候,是否对另一个线程可见。
JMM定义了线程和主内存之间的抽象关系,线程之间的共享变量存储在主内存,
每个线程都有一个私有内存,私有内存存的是共享变量的副本。
上图:线程程A与线程B之间如要通信的话,必须要经历下面2个步骤:
a、首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
b、然后,线程B到主内存中去读取线程A之前已更新过的共享变量。 - 使用volatile关键字
能够解决A线程修改的共享变量(刷新主内存)对另一个线程可见class Thread01 implements Runnable{ public volatile boolean flag = true; // volatile可以将子线程修改的共享变量即使刷新主内存 @Override public void run() { System.out.println("子线程开始......"+flag); while (flag){ } System.out.println("子线程结束....."+flag); } public void setFlag(boolean flag){ this.flag = flag; } } public class Test { public static void main(String[] args) throws Exception { Thread01 th = new Thread01(); Thread t1 = new Thread(th); t1.start(); Thread.sleep(3000); th.setFlag(false); System.out.println("主线程获取flag---"+th.flag); } }
- volatile和synchronized的区别
volatile能保证线程的可见性,不能保证原子性
synchronized能保证线程的可见性,也能保证原子性
volatile的性能优于synchronized - AtomicInteger原子类
public class Sample2 { private static AtomicInteger count = new AtomicInteger(0); public static void increment() { count.getAndIncrement(); } }
AtomicIntege不需要添加synchronized,就可以使用线程安全的方式操作加减,适合高并发情况下的使用
- 什么是ThreadLocal
ThreadLocal的实例代表了一个线程局部的变量,每条线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量。它采用采用空间来换取时间的方式,解决多线程中相同变量的访问冲突问题。 - 什么是线程池?
因为创建和销毁线程都是非常消耗系统资源的,线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。 - 线程池的作用
降低资源消耗。 通过重复利用已创建的线程,降低线程创建和销毁造成的资源消耗
提交响应速度。当有任务时,不需要等到线程创建。
方便线程的管理。使用线程池可以进行统一分配、调优和监控 - 线程池的使用场景
线程池是为突然大量爆发的线程设计的,使用线程池创建好的线程,减少了创建和销毁线程所需的时间,从而提高效率。
如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜。),况且我们还不能控制线程池中线程的开始、挂起、和中止。 - 线程池配置
主要判断你的业务场景,IO密集和CPU密集
IO密集 阻塞: 即请求、数据库操作、循环等属于IO密集
意思是该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的
最大线程数一般设置:cpu核数乘以2(线程的等待时间和cpu运行时间都是1秒的话)
如果你的线程等待时间很大则最大线程数的个数一般设置:(线程等待时间与线程CPU时间之比 + 1)* CPU数目
CPU密集
意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。
最大线程数一般设置:cpu核数
线程等待时间所占比例越高,需要越多线程。 - 线程池的四种创建方式
1 、创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
ExecutorService executorService = Executors.newCachedThreadPool();
2、创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
ExecutorService executorService = Executors.newFixedThreadPool(3);
3、创建一个定长线程池,支持定时及周期性任务执行
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
4、创建一个单线程化的线程池,唯一的工作线程来执行任务,保证任务按照指定顺序(FIFO, LIFO, 优先级)执行。
ExecutorService executorService = Executors.newSingleThreadExecutor(); - 说说JDK1.5并发包
名称
作用
Lock
锁
Executors
线程池
ReentrantLock
一个可重入的互斥锁定 Lock,功能类似synchronized,但要强大的多。
Condition
Condition的功能类似于在传统的线程技术中的,Object.wait()和Object.notify()的功能,
ConcurrentHashMap
分段HasMap
AtomicInteger
原子类
BlockingQueue
BlockingQueue 通常用于一个线程生产对象,而另外一个线程消费这些对象的场景
ExecutorService
执行器服务
- 什么是死锁
同步中嵌套同步,导致无法释放 - 锁的种类
自旋锁
互斥锁
可重入锁
悲观锁
乐观锁 - 乐观锁
本质没有锁,效率高,无阻赛,无等待
经常用于数据数库的update和delete操作, 在设计表的时候加个version字段,用于更新、删除时的判断条件
select number, version from table; // 先拿到数据和版本号
update table set number = 123, version = version+1 where id = #{id} and version = #{version}; // 更新时加上判断
上述问题 :若果有2个线程都拿到了同样的版本号,来做update时,就看谁走的看谁先执行。先执行的同时修改version - 悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,
所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,
都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。 - cas无锁机制
cas本身使用的还是乐观锁机制,每次修改时都会将期望值与更新值作比较,如果相等表示没被其他线程该过。
缺点: 如果一个线程将它的值修改后又改回来原来的值,它也认为没被改过。 - SpringBoot默认线程配置
默认核心线程数:8
最大线程数:Integet.MAX_VALUE
队列使用LinkedBlockingQueue
容量是:Integet.MAX_VALUE
空闲线程保留时间:60s - 未完待续......
2020-多线程面试题整理
最新推荐文章于 2021-11-24 19:00:32 发布