java面试宝典
1.线程的几种基本状态:1新建状态:使用new关键字创建一个线程,新创建的线程将属于新建状态,在创建线程时主要是为线程分配内存并初始化其成员变量的值。2就绪状态:新建的线程对象在调用start方法后就转为就绪状态,此时JVM完成了方法调用栈和程序计数器的创建,等待该线程的调度和运行。3运行状态:就绪状态的线程在竞争到cpu的使用权并开始执行run方法的线程执行体后,会转为运行状态,处于运行状态的线程的主要任务就是执行run方法中的逻辑代码。4阻塞状态:运行中的线程主动或被被动的放弃CPU的使用权并暂停运行,此时该线程将转为阻塞状态,直到再次进入就绪状态,才有机会再次竞争到CPU使用权并转换为运行状态。
2.线程阻塞状态的3种情况:1等待阻塞:在运行状态的线程调用wait方法时,JVM会把该线程放入等待队列,线程转为阻塞状态。2同步阻塞:在运行状态的线程尝试获取正在被其他线程占用的对象同步锁时,JVM才会把该线程放入锁池,转为阻塞状态。3其他阻塞:运行状态的线程执行sleep,join方法或者发出I/O请求时,JVM会把该线程转为阻塞状态。知道sleep状态超时,join等待线程终止或超时,I/O处理完毕时,线程重新转为可运行状态。
3.Java中线程创建的四种方法:1继承Thread类2实现Runnable接口3通过Callable和Future接口实现有返回值的线程4基于线程池
4.线程的基本方法:1 wait方法 调用wait方法的线程会进入WAITING状态,只有等到其他线程的通知或被中断后才会返回。调用wait方法会释放对象锁。2 sleep方法 调用sleep方法会导致当前线程休眠,但不会释放当前占有的锁。3 interrupt方法 用于让线程发出一个终止通知信号,会影响线程内部的一个中断标识位,这个方法本身并不会因为调用了interrupt方法而改变状态。4 join方法 用于等待其他线程终止,如果在当前线程中调用一个线程的join方法,当前线程会转为阻塞方法,等到另一个线程结束,当前线程再由阻塞状态转为就绪状态。 5 notify方法 Object类有个notify方法,用于唤醒在此对象监视器上等待的一个线程,notifyAll方法用于唤醒监视器上等待的所有线程。6 setDaemon方法:用于定义一个守护线程,也叫作服务线程,该线程是后台线程,有一个特性即为用户线程提供公共服务,在没有用户线程可服务时,会自行离开。
5.Sleep方法和yield方法有什么区别:sleep方法给其他线程运行机会时,不考虑线程的优先级,因此会给低优先级的线程以运行的机会,yield方法只会给相同优先级或者更高优先级的线程以运行的机会。
6.同步方法和同步代码块的区别:同步方法锁住的是类的实例或者当前类的class对象,同步代码块可以选择以什么来加锁,比同步方法更加细粒度,我们可以选择只同步会发生同步问题的部分代码。
7.CountDownLatch和CyclicBarrier关键字:CountDownLatch类位于java.util.Concurrent包下,是一个同步工具类,运行一个或者多个线程一直等待其他线程的操作执行完毕后再执行相关操作。CyclicBarrier是一个同步工具,可以实现让一组线程等待至某个线程之后再全部同时执行。在所有等待的线程都被释放之后,CyclicBarrier可以被重用,CyclicBarrier的运行状态叫作Barrier状态,在调用await方法后,线程就处于Barrier状态。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest {
final static CountDownLatch latch=new CountDownLatch(2);
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
System.out.println("子线程1正在执行");
try {
Thread.sleep(3000);
System.out.println("线程1执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
new Thread(){
@Override
public void run() {
System.out.println("子线程2正在执行");
try {
Thread.sleep(3000);
System.out.println("线程2执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
System.out.println("等待两个线程执行完毕");
try {
latch.await();
System.out.println("两个线程已经执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest {
public static void main(String[] args) {
int N=4;
CyclicBarrier barrier=new CyclicBarrier(N);
for(int i=0;i<N;i++){
new BusinessThread(barrier).start();
}
}
static class BusinessThread extends Thread{
private CyclicBarrier cyclicBarrier;
public BusinessThread(CyclicBarrier cyclicBarrier){
this.cyclicBarrier=cyclicBarrier;
}
@Override
public void run() {
try {
Thread.sleep(5000);
System.out.println("当前线程完成了准备工作");
cyclicBarrier.await();//当所有线程都到达这个状态后,才会并发执行以后的代码
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
8什么是AQS:AQS是一个抽象的队列同步器,通过维护一个共享资源状态和一个先进先出的线程等待队列来实现一个多线程访问共享资源的同步框架。AQS为每个共享资源都设置了一个共享资源锁,线程在需要访问共享资源时,首先要获取共享资源锁,如果获取到了共享资源锁,便可以在当前线程中使用该共享资源,如果获取不到,则将该线程放入线程共享队列,等待下一次的资源调度。许多同步类的实现都依赖AQS,例如常用的ReentrantLock,Semaphore.CountDownLatch。
9Semaphore:指信号量,用于控制同时访问某些资源的线程个数。具体做法是通过acquire()获取一个许可,如果没有许可,则等待,在许可使用完毕以后,通过release()释放该许可,以便其他线程使用。
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
public static void main(String[] args) {
int printNumber=5; //设置线程数
Semaphore semaphore=new Semaphore(2);//设置并发数
for(int i=0;i<printNumber;i++){
new Worker(i,semaphore);
}
}
static class Worker extends Thread{
private int num;
private Semaphore semaphore;
public Worker(int num,Semaphore semaphore){
this.num=num;
this.semaphore=semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("员工"+this.num+"占用一个打印机");
Thread.sleep(1000);
System.out.println("员工打印完成");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}