101、为什么要使用线程池?
线程池的作用就是限制系统中执行线程的数量。根据系统的环境情况,可以手动的或者自动的设置线程数量,少了浪费系统资源,多了造成系统拥挤效率不高。使用线程池控制线程数量,其他线程排队等候,一个任务执行完毕,再从队列中取出最靠前的任务开始执行。若队列中没有等待的线程,线程池的这一资源处于等待,当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了,,否则进入等待队列。
总结:
- 使用线程池减少了创建和销毁线程的次数,每个工作线程都可以被重复的利用,可以执行多个任务。
- 可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存而把服务器累趴下。(每个线程需要大约1MB的内存,线程开的越多,消耗的内存也就越大,最后死机)
102、synchronized和volatile关键字的区别?
一旦一个共享变量(类的成员变量、类的静态变量)被volatile修饰之后,那么这个贡献变量就具备了如下两层语义:
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了这个变量的值后,这个新的值对其他线程来说是立刻可见的。
- 禁止进行指令重排。
volatile的本质是在告诉JVM,当前变量在寄存器(线程的工作内存)的值是不确定的,你要使用这个变量的值的话就要从主内存中去读取。synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
总结:volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、类级别上。volatile仅能实现变量修改的可见性,并不能保证原子性;synchronized则可以保证变量修改的可见性和原子性。volatile不会造成线程的阻塞,synchronized可能会造成线程阻塞。volatile修饰的变量不会被编译器优化,synchronized修饰的变量可以被编译器优化。
103、简述一下线程池的执行流程(启动策略)。
- 线程池刚创建的时候,里面没有一个线程。任务队列是作为参数传进来的,即使任务队列里有任务存在,线程池也不会立即去执行他们。
- 当调用execute()方法添加一个任务时候,线程池会做如下的判断:a、如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;b、如果正在运行的线程数量大于或者等于corePoolSize,那么将这个任务放入队列。c、如果这个时候队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要继续创建线程执行这个任务;d、如果队列满了,而且正在运行的线程数量大于或者等于maximumPoolSize,那么线程池就会抛出异常,告诉调用者我不能再接受任务了。
- 当一个线程完成任务后,它会从队列中取下一个任务来执行。
- 当一个线程无事可做,超过一定时间(keepAliveTime)时,线程池会做判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被会停掉,所以线程池的所有任务都完成后,它的数量会最终收缩到corePoolSize的大小。
104、什么是死锁?
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
105、什么情形下会导致死锁?遇到死锁怎么解决?
(1) 因为系统资源不足。
(2) 进程运行推进顺序不合适。
(3) 资源分配不当等。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
死锁的必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
死锁的解除与预防:理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。
106、Java中多线程之间怎么通信的?
- 同步:这里讲的同步是指多个线程通过synchronized关键字这种方式来实现线程间的通信。这种方式,本质上就是“共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。
- while轮询方式
- wait/notify机制
- 管道通信:就是使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信
107、请说出同步线程及线程调度相关的方法。
wait():使一个线程处于等待状态(阻塞),并且释放所持有的对象的锁;
sleep():是一个正在运行的线程处于休眠状态,是一个静态方法,调用此方法时候需要处理InterruptedException异常;
notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个处于等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
notifyAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让他们竞争,只有获得锁的线程才能进入就绪状态。
108、启动一个线程是调用run()方法还是start()方法?
启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行,这并不意味着该线程会立即执行。run()方法是线程启动后要进行回调(callback)的方法。
109、用户线程和守护线程有什么区别?
当我们在Java程序中创建一个线程,它就被称为用户线程。一个守护线程是在后台执行并且不会阻止JVM终止的线程。当没有用户线程在运行的时候,JVM关闭程序并且退出。一个守护线程创建的子线程依然是守护线程。
110、什么是线程调度器和时间分片?
线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。