多线程

214 阅读6分钟

1. 进程和线程的区别

进程是资源分配的最小单位,线程是cpu调度的最小单位

线程不能看做独立应用,而进程可以看做独立应用

进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径

线程没有独立的地址空间,多进程程序比多线程程序健壮

进程切换比线程切换开销大。

2. Thread的start和run方法区别

start:通过jvm的startThread创建一个线程,去调用run方法 run:普通方法,还是主线程执行

3. 如何给run方法传参

①构造函数传参②成员变量传参③回调函数传参

4.如何处理线程的返回值

①主线程等待法 (等待直到满足条件,没等到就sleep)

②使用Thread.join阻塞当前线程以等待子线程处理完毕。

③通过callable接口实现:通过futuretask or 线程池获取

4.如何停止线程

已经废弃:stop、suspend、resume

使用:调用interrupt()方法,通知线程应该中断了。 1、如果线程处于被阻塞状态,那么线程立即退出被阻塞状态,并抛出一个InterruptedExecption异常

2、如果正常状态,那么会将该线程中断标志设置为true,线程还是正常运行,不受影响。

5.线程的生命周期

1、新建(new):创建后尚未启动线程状态。

2、运行(Runnable):包含Running和Ready

3、无限期等待(waiting):不会被分配cpu执行时间,需要显示被唤醒

没有设置timeout参数的Object.wait()

没有设置timeout参数的Thread.join()

LockSupport.park()

4、限期等待(timed waiting):在一定时间后由系统自动唤醒 Thread.sleep()

设置timeout参数的Object.wait()

设置timeout参数的Thread.join()

LockSupport.parkNanos()

LockSupport.parkUntil() 5、阻塞(Blocked):等待获取排他锁

6、结束(Terminated):线程已经结束执行

6.wait 什么情况下会唤醒

1、另一个线程调用这个对象的notify方法,且刚好唤醒的是本线程

2、另一个线程调用这个对象的notifyAll方法

3、过了wait的超时时间,如果传入0就是永久等待。

4、线程自身调用了interrupt方法

6.sleep和wait的区别

sleep是Thread类的方法,wait是object类的方法。

sleep可以在任何地方使用

wait只能在synchronized方法或synchronized代码块中使用

本质区别:sleep只会让出cpu,不会导致锁行为改变,wait不仅让出cpu,而且会释放锁。

7.notify和notifyAll的区别

锁池 等待池

notifyAll会让所有处于等待池线程全部进入锁池去竞争获取锁机会。

notify只会随机选取一个处于等待池中线程进入锁池去竞争获取锁机会。

8.join

新线程加入了我们,所以我们等他执行完再出发。

9.volatile

volatile是一种同步机制,比synchronized和Lock相关类更轻量,因为使用它并不会发生上下文切换等开销大的行为。

如果一个变量被修饰成volatile,那么jvm就知道这个变量可能会被并发修改。

开销小,作用也小,场景有限。

volatile的使用场合

不适用:a++; 适用:1、自始至终只被各个线程赋值(因为赋值自身是原子性的,而volatile又保证了可见性,所以就保证了线程安全)

2、作为刷新之前变量的触发器

可见性:读一个volatile变量之前,需要先使相应的本地缓存失效,这样就必须到主内存读 取最新值,写一个volatile属性会立即刷到主内存。

禁止重排序:禁止指令重排序优化。例:解决单例双重锁乱序问题。

10.synchronized

锁的不是代码,是对象

对象锁

1、同步代码块 (Synchronized(this) synchronized(类实例对象))

2、同步非静态方法:锁的是当前对象的实例对象

类锁

1、同步代码块 synchronized(类.class) 2、 同步静态方法

类锁和对象锁互不干扰 ,理解不同对象。

10.synchronized和ReentrantLock的区别

一个是关键字,一个是对象

后者可设置获取锁等待时间,避免死锁,而且可以获取各种锁的信息。

11.JMM

java内存模型它是一种规范;是关键字和工具类的原理;重点:重排序、可见性、原子性。

并发过程中如何处理可见性、原子性、有序性的问题?

并发过程中两个关键问题?

a、线程之间如何通信? wait、notify、notifyAll

1、共享内存 隐式通信

2、消息传递 显示通信

b、线程之间如何同步?(操作的顺序)

在共享内存的并发模型中,同步是显示做的,如synchronize。

在消息传递的并发模型中,由于消息的发送必须在消息接收之前,所以同步是隐式。

什么是重排序?重排序好处能提高处理速度

重排序的三种情况:编译器优化(包括jvm、jit编译器等)、cpu指令重排、内存的“重排序”(线程a的修改线程b看不到,引出可见性问题)。

为什么有可见性问题?

cpu有多级缓存,导致读的数据过期

  • 高速缓存的容量比主内存小,但是速度仅次于寄存器,所以在cpu和主内存之间就多了cache层。
  • 线程间的对于共享变量的可见性问题不是由多核引起的,而是由多缓存引起的。
  • 如果所有核心都只用一个缓存,那么也就不存在内存可见性的的问题了。
  • 每个核心都会将自己需要的数据读到独占缓存中,数据修改后也是写入缓存中,然后等待刷入到主缓存中,所以会导致有些核心读取到的是一个过期的值。

java中原子性操作

单例模式

Happens-Before规则

1、单线程规则

2、锁操作

3、volatile变量

4、线程启动

5、线程join

6、传递性

7、中断

1. 线程池

1、为什么要使用线程池?

  • 降低消耗资源
  • 提高线程的可管理性

2、juc的三个Executor接口

  • Executor:运行新任务的简单接口,将任务提交和任务执行细节解耦。
  • ExecutorService:具备管理执行器和任务生命周期的方法,提交任务机制更完善。
  • ScheduledExecutorService:支持future和定期执行任务。

3、ThreadPoolExecutor的构造函数

corePoolSize:核心线程数量

maximumPoolSize:线程不够用时能够创建最大的线程数。

workQueue:任务等待队列

keepAliveTime:抢占的顺序不一定看运气

threadFactory:创建新线程

handler:线程池的饱和策略

4、线程池的状态

RUNNING:能接受新提交的任务,并且也能处理阻塞队列中的任务

SHUTDOWN:不再接受新提交的任务,但可以处理存量任务

STOP:不再接受新提交任务,也不处理存量任务

TIDYING:所有任务已终止

TERMINATED:terminated()方法执行完后进入该状态

5、线程池的大小如何选定

cpu密集型:线程数=按照核数或者核数+1

i/o密集型:线程数=cpu核数*(1+平均等待时间/平均工作时间)