程序员社区

Java面试题之多线程(二)

史上最全Java面试题汇总,Java面试手册PDF下载

线程的sleep()方法和yield()方法有什么区别?

答:
① sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
③ sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
④ sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。

请说出与线程同步以及线程调度相关的方法。

答:

  • wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
  • sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
  • notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
  • notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

举例说明同步和异步。

答:如果系统中存在临界资源(资源数量少于竞争资源的线程数量的资源),例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就必须进行同步存取(数据库操作中的排他锁就是最好的例子)。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。事实上,所谓的同步就是指阻塞式操作,而异步就是非阻塞式操作。

不使用stop停止线程?

当run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。

使用自定义的标志位决定线程的执行情况

public class SafeStopThread implements Runnable{  
   private volatile boolean stop=false;//此变量必须加上volatile  
   int a=0;  
   @Override  
    public void run() {  
        // TODO Auto-generated method stub  
        while(!stop){  
               synchronized ("") {  
                    a++;  
                    try {  
                        Thread.sleep(100);  
                    } catch (Exception e) {  
                        // TODO: handle exception  
                    }  
                    a--;  
                    String tn=Thread.currentThread().getName();  
                    System.out.println(tn+":a="+a);  
                }  
        }  
      //线程终止  
     public void terminate(){  
         stop=true;  
      }  
  public static void main(String[] args) {  
       SafeStopThread t=new SafeStopThread();  
       Thread t1=new Thread(t);  
       t1.start();  
       for(int i=0;i<5;i++){   
           new Thread(t).start();  
       }  
     t.terminate();  
   }  
} 

Java中如何实现线程?各有什么优缺点,比较常用的是那种,为什么?

在语言层面有两种方式。java.lang.Thread 类的实例就是一个线程但是它需要调用java.lang.Runnable接口来执行,由于线程类本身就是调用的Runnable接口所以你可以继承java.lang.Thread 类或者直接调用Runnable接口来重写run()方法实现线程。

Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口好了。

如何控制某个方法允许并发访问线程的大小?

Semaphore两个重要的方法就是semaphore.acquire() 请求一个信号量,这时候的信号量个数-1(一旦没有可使用的信号量,也即信号量个数变为负数时,再次请求的时候就会阻塞,直到其他线程释放了信号量)semaphore.release()释放一个信号量,此时信号量个数+1

public class SemaphoreTest {  
    private Semaphore mSemaphore = new Semaphore(5);  
    public void run(){  
        for(int i=0; i< 100; i++){  
            new Thread(new Runnable() {  
                @Override  
                public void run() {  
                    test();  
                }  
            }).start();  
        }  
    }  

    private void test(){  
        try {  
            mSemaphore.acquire();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println(Thread.currentThread().getName() + " 进来了");  
        try {  
            Thread.sleep(1000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println(Thread.currentThread().getName() + " 出去了");  
        mSemaphore.release();  
    }  
}  

在Java中什么是线程调度?

线程调度是指系统为线程分配处理器使用权的过程。
主要调度方式有两种,分别是协同式线程调度和抢占式线程调度。

协同式线程调度:线程的执行时间由线程本身控制,当线程把自己的工作执行完了之后,主动通知系统切换到另一个线程上。

  • 好处是切换操作对于线程自己是可知的,没什么线程同步问题。
  • 坏处是线程执行时间不可控,可能会一直阻塞然后系统崩溃。

抢占式线程调度:每个线程由系统分配执行时间,不由线程本身决定。线程的执行时间是系统可控的,不会有一直阻塞的问题。

Java使用抢占式调度

Java中用到的线程调度算法是什么?

抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

线程类的构造方法、静态块是被哪个线程调用的?

线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。

在实现Runnable的接口中怎么样访问当前线程对象,比如拿到当前线程的名字?

Thread t = Thread.currentThread();
String name = t.getName();
System.out.println("name=" + name);

什么是线程池?为什么要使用它?为什么使用Executor框架比使用应用创建和管理线程好?

创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。

为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。

Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)。

常用的线程池模式以及不同线程池的使用场景?

以下是Java自带的几种线程池:
1、newFixedThreadPool 创建一个指定工作线程数量的线程池。
每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

2、newCachedThreadPool 创建一个可缓存的线程池。
这种类型的线程池特点是:

  • 1).工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE),这样可灵活的往线程池中添加线程。

  • 2).如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。

3、newSingleThreadExecutor创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行(我觉得这点是它的特色)。

单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

4、newScheduleThreadPool 创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。

在Java中Executor、ExecutorService、Executors的区别?

Executor 和 ExecutorService 这两个接口主要的区别是:

  • ExecutorService 接口继承了 Executor 接口,是 Executor 的子接口
  • Executor 和 ExecutorService 第二个区别是:Executor 接口定义了 execute()方法用来接收一个Runnable接口的对象,而 ExecutorService 接口中的 submit()方法可以接受Runnable和Callable接口的对象。
  • Executor 和 ExecutorService 接口第三个区别是 Executor 中的 execute() 方法不返回任何结果,而 ExecutorService 中的 submit()方法可以通过一个 Future 对象返回运算结果。
  • Executor 和 ExecutorService 接口第四个区别是除了允许客户端提交一个任务,ExecutorService 还提供用来控制线程池的方法。比如:调用 shutDown() 方法终止线程池。

Executors 类提供工厂方法用来创建不同类型的线程池。

比如: newSingleThreadExecutor() 创建一个只有一个线程的线程池,newFixedThreadPool(int numOfThreads)来创建固定线程数的线程池,newCachedThreadPool()可以根据需要创建新的线程,但如果已有线程是空闲的会重用已有线程。

如何创建一个Java线程池?

Java通过Executors提供四种线程池,分别为:

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

Thread 类中的start() 和 run() 方法有什么区别?

start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。

当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。

Java线程池中submit() 和 execute()方法有什么区别?

两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

Java中notify 和 notifyAll有什么区别?

notify()方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它才有用武之地。而notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。

当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争

优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。

为什么wait, notify 和 notifyAll这些方法不在thread类里面?

一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。

如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。

简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

为什么wait和notify方法要在同步块中调用?

主要是因为Java API强制要求这样做,如果你不这么做,你的代码会抛出IllegalMonitorStateException异常。还有一个原因是为了避免wait和notify之间产生竞态条件。

最主要的原因是为了防止以下这种情况


// 等待者(Thread1)
while
赞(0) 打赏
未经允许不得转载:IDEA激活码 » Java面试题之多线程(二)

一个分享Java & Python知识的社区