Java知识及热点面试题总结(二)
1、什么是死锁(deadlock)?
两个线程或两个以上线程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是这些线程都陷入了无限的等待中。如何避免线程死锁?只要破坏产生死锁的四个条件中的其中一个就可以了。破坏互斥条件:这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互 斥访问)。破坏请求与保持条件:一次性申请所有的资源。破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放
2、线程生命周期(状态)
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。
在线程的生命周期中,它要经过新建(
New
)、就绪(
Runnable
)、运行(
Running
)、阻塞
(
Blocked
)和死亡(
Dead
)5 种状态。尤其是当线程启动以后,它不可能一直"霸占"着 CPU 独自
运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换
1.新建状态(NEW)
当程序 使用 new 关键字创建了一个线程之后 ,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值2.就绪状态(RUNNABLE):
当线程对象 调用了 start()方法之后 ,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。3.运行状态(RUNNING):
如果处于 就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体 ,则该线程处于运行状态。4.阻塞状态(BLOCKED):
阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。 直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状态。阻塞的情况分三种:等待阻塞( o.wait-> 等待对列):运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue)中。同步阻塞 (lock-> 锁池 )运行(running)的线程在获取对象的同步锁时,若该 同步锁被别的线程占用 ,则 JVM 会把该线程放入锁池(lock pool)中。其他阻塞 (sleep/join)运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。5.线程死亡(DEAD)
线程会以下面三种方式结束,结束后就是死亡状态。正常结束1. run()或 call()方法执行完成,线程正常结束。异常结束2. 线程抛出一个未捕获的 Exception 或 Error。调用 stop3. 直接调用该线程的 stop()方法来结束该线程— 该方法通常容易导致死锁 ,不推荐使用。
3、sleep 与 wait 区别
1. 对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于 Object 类中的。2. sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是 他的监控状态依然保持着 ,当指定的时间到了又会自动恢复运行状态。3. 在调用 sleep()方法的过程中, 线程不会释放对象锁 。4. 而当 调用 wait()方法的时候,线程会放弃对象锁 ,进入等待此对象的 等待锁定池 ,只有针对此 对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
4、start 与 run 区别
1. start() 方法来启动线程,真正实现了多线程运行。这时无需等待 run( )方法体代码执行完毕,可以直接继续执行下面的代码。2. 通过调用 Thread 类的 start( )方法来启动一个线程, 这时此线程是处于 就绪状态 ,并没有运行。3. 方法 run( )称为线程体,它包含了要执行的这个线程的内容,线程就进入了 运行状态,开始运行 run 函数当中的代码 。 run( ) 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。
5、线程基本方法
线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等。
sleep():强迫一个线程睡眠N毫秒。
join(): 等待线程终止。
setName(): 为线程设置一个名称。
wait(): 强迫一个线程等待。
notify(): 通知一个线程继续运行。
.......
线程等待( wait )调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回,需要注意的是调用 wait()方法后, 会释放对象的锁 。因此,wait 方法一般用在同步方法或同步代码块中。线程睡眠( sleep )sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占有的锁 ,sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法会导致当前线程进入 WATING 状态join( )等待其他线程终止join() 方法,等待其他线程终止 ,在当前线程中调用一个线程的 join() 方法,则当前线程转为阻塞 状态,回到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待 cpu 的宠幸。为什么要用 join() 方法?很多情况下,主线程生成并启动了子线程,需要用到子线程返回的结果,也就是需要主线程需要在子线程结束后再结束,这时候就要用到 join() 方法。
6、volatile 关键字的作用(变量可见性、禁止重排序)
Java 语言提供了一种稍弱的同步机制,即 volatile 变量,用来确保将变量的更新操作通知到其他线程。volatile 变量具备两种特性,volatile 变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取 volatile 类型的变量时总会返回最新写入的值。变量可见性其一是保证该变量对所有线程可见,这里的可见性指的是当一个线程修改了变量的值,那么新的 值对于其他线程是可以立即获取的 。禁止重排序volatile 禁止了指令重排。比 sychronized 更轻量级的同步锁在访问 volatile 变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此 volatile 变量是一 种比 sychronized 关键字更轻量级的同步机制。volatile 适合这种场景:一个变量被多个线程共享,线程直接给这个变量赋值。当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到 CPU 缓存中。如果计算机有 多个 CPU,每个线程可能在不同的 CPU 上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。
7、进程和线程的区别
进程是包含线程的 . 每个进程至少有一个线程存在,即主线程。进程和进程之间不共享内存空间 . 同一个进程的线程之间共享同一个内存空间 .进程是系统分配资源的最小单位,线程是系统调度的最小单位。
8、线程的创建
JavaEE——多线程-CSDN博客
详细内容请看博客