1.为什么用多线程?
有时候,系统需要处理非常多的执行时间很短的需求,如果每一个请求都开启一个新线程的话,系统就要不断的进行线程的创建和销毁,有时花在创建和销毁线程上的时间会比线程真正执行的时间还长。
而且线程数量太多时,系统不一定能受得了。
使用线程池主要为了解决一下几个问题:
通过重用线程池中的线程,来减少每个线程创建和销毁的性能开销。
对线程进行一些维护和管理,比如定时开始,周期执行,并发数控制。
2.线程池参数什么意思?
比如去火车站买票,有10个售票窗口,但只有5个窗口对外开放,那么对外开放的5个窗口称为核心线程数。
而最大线程数是10个窗口。如果5个窗口都被占用,那么后来的人就必须在后面排队,但后来售票厅越来越多,已经人满为患,就类似线程队列已满,这时候火车站站长下令,把剩下的5个窗口也打开,也就是目前有10个窗口同时运行。后来又来了一批人。10个窗口也处理不过来了,而且售票厅人已经满了,这时候站长就下令封锁入口,不允许其他人再进来,这就是线程异常处理策略,而线程存活时间指的是,允许售票员休息的最长时间,以此限制售票员偷懒的行为。
3.讲一讲线程池中的ThreadPoolExecutor,每个参数是干什么的?
Executor是一个接口,跟线程池有关的基本都要跟它打交道。ThreadPoolExecutor的关系:
![3f604fbbe0570d1108ed04ecc3e63516.png](https://i-blog.csdnimg.cn/blog_migrate/f1909623158c517069362b829c1434c1.png)
Executor接口很简单,只有一个execute方法。
ExecutorService是Executor的子接口,增加了一些常用的对线程的控制方法,之后使用线程池主要也是使用这些方法。
AbstractExecutorService是一个抽象类。ThreadPoolExecutor就是继承了这个类。
ThreadPoolExecutor的参数:
ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(corePoolSize,// 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 闲置线程存活时间
TimeUnit.MILLISECONDS,// 时间单位
new LinkedBlockingDeque<Runnable>(),// 线程队列
Executors.defaultThreadFactory(),// 线程工厂
new AbortPolicy()// 队列已满,而且当前线程数已经超过最大线程数时的异常处理策略
);
corePoolSize:
核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非allowCoreThreadTimeOut设置为true。
maximumPoolSize:
线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。
keepAliveTime:
非核心线程的闲置超时时间,超过这个时间就会被回收。
unit:
指定keepAliveTime的单位,如TimeUnit.SECONDS。当allowCoreThreadTim他Out,设置为ture时对corePoolSize生效。
woreQueue:
线程池中的任务队列,常有的有三种队列:
- SynchronousQueue
- LinkedBlockingDeque
- ArrayBlockingQueue
threadFactory:
线程工厂,提供创建新线程的功能。ThreadFactory是一个接口,只有一个方法:
public interface ThreadFactory {
Thread newThread(Runnable r);
}
通过线程工厂可以对线程的一些属性进行定制。
默认的工厂:
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager var1 = System.getSecurityManager();
this.group = var1 != null?var1.getThreadGroup():Thread.currentThread().getThreadGroup();
this.namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
}
public Thread newThread(Runnable var1) {
Thread var2 = new Thread(this.group, var1, this.namePrefix + this.threadNumber.getAndIncrement(), 0L);
if(var2.isDaemon()) {
var2.setDaemon(false);
}
if(var2.getPriority() != 5) {
var2.setPriority(5);
}
return var2;
}
}
RejectedExecutionHandler:
RejectedExcutionHandler也是一个接口,只有一个方法:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);
}
当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用RejectedExcutionHandler的rejectExecution方法。
4.说下线程池内部使用规则?
线程池的线程规则执行跟任务队列有很大的关系:
下面都假设任务队列没有大小限制:
如果线程数量<=核心线程数量,那么直接启动一个核心线程来执行任务,不会放入队列中。
如果线程数量>核心线程数量,但<=最大线程数,并且任务队列是LinkedBlockingDeque的时候,超过核心线程数量的任务会放在任务队列中排队。
如果线程数量>核心线程数,但<=最大线程数,并且任务队列是SynchronousQueue队列的时候,线程池会创建新的线程执行任务,这些任务也不会被放在任务队列中。这些线程属于非核心线程,在任务完成后,闲置时间达到了超时时间就会清除。
如果线程数量>核心线程数,并且>最大线程数,当任务队列是LinkedBlockingDeque,会将超过核心线程的任务放在任务队列中排队。也就是当任务队列是LinkedBlockingDeque并且没有大小限制的时候,线程池的最大线程数设置是无效的,它的线程数最多不会超过核心线程数。
如果线程数量>核心线程数,并且>最大线程数,当任务队列是SynchronousQueue 的时候,会因为线程池拒绝添加任务而抛出异常。
任务队列大小有限时:
当LinkedBlockingDeque 塞满时,新增的任务会直接创建新线程来执行,当创建的线程数量超过最大线程数量时会抛异常。
SynchronousQueue 没有数量限制,因为它根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会直接抛出异常。
5.用过AtomicInteger吗?怎么用的?
AtomicInteger是Integer类型的原子操作类型,对于全局全量的数值类型操作num++,若没有加synchronized关键字则是线程不安全的,num++,解析为num=num+1,明显,这个操作不具备原子性,多线程操作必然会出现问题。
看代码:
public class AtomicIntegerTest1 {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10000; i++) {
new Thread(() -> count ++ ).start();
}
TimeUnit.SECONDS.sleep(3);
System.out.println("count:" + count);
}
}
执行结果:
![bd30d1b59136fe9510de08a1a2f0813a.png](https://i-blog.csdnimg.cn/blog_migrate/7beaa23930e3196d33748bb7b7b33fb2.jpeg)
要是换成volatile修饰count变量呢?
volatile修饰的变量能够在多线程间保持可见性,能被多个线程同时读但是又能保证只被单个线程写,并且不会读取到过期值(由JMM模型中的happen-before原则决定的)volatile修饰字段的写入操作总是由于读操作,即使多个线程同时修改volatile变量字段,总能获取到最新的值。
但是volatile 仅仅保证变量在线程间保持可见性,却依然不能保证非原子性操作。
/**
* @description: volatile 仅仅保证变量在线程间保持可见性,却依然不能保证非原子性操作。
* @author: liushuai
* @create: 2020-04-17 18:36
**/
public class AtomicIntegerTest2 {
public static volatile int count =0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10000; i++) {
new Thread(() -> count++).start();
}
TimeUnit.SECONDS.sleep(2);
System.out.println("volatile count:" + count);
}
}
执行结果:
![7d0034ade6963db4b128681189673ee2.png](https://i-blog.csdnimg.cn/blog_migrate/ff64970d4052017f4c5754a3636af197.jpeg)
atomicnteger常用方法:
public final int getAndSet(int newValue) //给AtomicInteger设置newValue并返回加oldValue
public final boolean compareAndSet(int expect, int update) //如果输入的值和期望值相等就set并返回true/false
public final int getAndIncrement() //对AtomicInteger原子的加1并返回当前自增前的value
public final int getAndDecrement() //对AtomicInteger原子的减1并返回自减之前的的value
public final int getAndAdd(int delta) //对AtomicInteger原子的加上delta值并返加之前的value
public final int incrementAndGet() //对AtomicInteger原子的加1并返回加1后的值
public final int decrementAndGet() //对AtomicInteger原子的减1并返回减1后的值
public final int addAndGet(int delta) //给AtomicInteger原子的加上指定的delta值并返回加后的值
6.用过ThreadLocal吗?怎么用的?
早在JDK1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁编写出优美的多线程程序。
ThreadLocal很容易让人望文生义,想当然的认为是一个"本地线程"。
其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为为ThreadLocalVariable更容易让人理解一些。
ThreadLocal为变量在每个线程中都创建了一个副本,那个每个线程可以访问自己内部的局部变量。
ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量各不干扰,在高并发的场景下,可以实现无状态的调用,特别适用于各个线程依赖不同的变量值完成操作的场景。
class ConnectionManager {
private static Connection connect = null;
public static Connection openConnection() {
if(connect == null){
connect = DriverManager.getConnection();
}
return connect;
}
public static void closeConnection() {
if(connect!=null)
connect.close();
}
}
假设有这样一个数据库连接管理类,这段代码在单线程中使用是没有任何问题的,但是如果再多线程中使用呢?
很显然,在多线程中使用会存在线程安全问题:
- 第一,这里面的2个方法都没有进行同步,很可能在openConnection方法中会多次创建connect;
- 第二,由于connect是共享变量,那么必然在调用connect的地方需要使用到同步来保证线程安全,因为很可能一个线程在使用connect进行数据库操作,而另外一个线程调用closeConnection关闭连接。
所以出于线程安全的考虑,必须将这段代码的两个方法进行同步处理,并且在调用connect的地方需要进行同步处理。
这样将会大大影响程序执行效率,因为一个线程在使用connect进行数据库操作的时候,其它线程只有等待。
那么大家来仔细分析一下这个问题,这地方到底需不需要将connect变量进行共享呢?事实上,是不需要的。假如每个线程中都有一个connect变量,各个线程之间对connect变量的访问实际上是没有依赖关系的,即一个不需要关心其它线程是否对这个connect进行了修改的。
到这里,可能会有朋友想到,既然不需要在线程之间共享这个变量,可以直接这样处理,在每个需要使用数据库连接的方法中具体使用时才创建数据库连接,然后在方法调用完毕再释放这个连接。比如下面这样:
class ConnectionManager {
private Connection connect = null;
public Connection openConnection() {
if(connect == null){
connect = DriverManager.getConnection();
}
return connect;
}
public void closeConnection() {
if(connect!=null)
connect.close();
}
}
class Dao{
public void insert() {
ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.openConnection();
//使用connection进行操作
connectionManager.closeConnection();
}
这样处理确实也没有任何问题,由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不尽严重影响程序执行效率,还可能导致服务器压力巨大。
那么这种情况下使用ThreadLocal是再适合不过的了,因为ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程颞部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会影响程序执行性能。
但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。
![041eb12217a8549e0e83530a23e0462c.png](https://i-blog.csdnimg.cn/blog_migrate/162624d37e5d3a8ea58fc16a424b713f.png)
从上面的结构图,我们已经看见ThreadLocal的核心机制:
- 每个Thread线程内部都有一个Map
- Map里面存在线程本地对象key和线程变量副本(value)
但是,Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。
所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
ThreadLocal类提供了如下几个核心方法:
public T get()
public void set(T value)
public void remove()
- get()方法用于获取当前线程的副本变量值。
- set()方法用于保存当前线程的副本变量值。
- initialValue()为当前线程初始化副本变量值。
- remove()方法移除当前线程的副本变量值。
7、程序、进程、线程的区别是什么?举个现实的例子说明。
程序(Program):
是一个指令的集合。程序不能独立运行,只有被加载到内存中,系统为它分配资源后才能执行。进程(Process):
如上所述,一个执行中的程序称为进程。
进程是系统分配资源的独立单位,每个进程占有特定的地址空间。
程序是进程的静态文本描述,进程是程序在系统内顺序执行的动态活动。
线程(Thread): 是进程的"单一执的连续控制流程"
线程是cpu的调度和分配的基本单位,是比进程更小的能独立运行的基本单位,也被称为轻量级的进程。
线程不能独立存在,必须依附于某个进程。一个进程可以包括多个并行的线程,一个线程肯定属于一个进程。Java虚拟机允许应用程序并发地执行多个线程。
举例:如一个车间是程序,一个正在进行生产任务的车间是一个进程,车间内每个从事不同工作的工人是一个线程。
**
8.java中通过哪些方式创建多个线程类?分别使用代码说明。并调用
- 继承Thread类创建线程。
/**
* @program: java-learning->ThreadCreateTest
* @description:
*
* Thread类本质上实现了Runnable接口的一个实例,代表线程的一个实例。
* 启动线程的唯一方法就是通过Thread类的start()实例方法
* 这种方式实现多线程很简单,通过自己的类直接 extends Thread;
* 并复写run()方法,就可以启动新线程并执行自定定义的run()方法。
* start
* @author: liushuai
* @create: 2020-04-21 09:51
**/
public class ThreadCreateTest extends Thread{
@Override
public void run() {
System.out.println("ThreadCreateTest.run()");
}
public static void main(String[] args) {
new ThreadCreateTest().start();
ThreadCreateTest createTest = new ThreadCreateTest();
createTest.start();
}
}
- 实现Runnable接口创建线程
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @program: java-learning->ThreadPoolTest
* @description:
* @author: liushuai
* @create: 2020-04-21 13:33
**/
public class ThreadPoolTest {
/** 线程池数量 10个 */
private static int POOL_NUM = 10;
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < POOL_NUM; i++) {
RunnableThread thread = new RunnableThread();
executorService.execute(thread);
}
executorService.shutdown();
}
}
class RunnableThread implements Runnable{
@Override
public void run() {
System.out.println("通过线程池方式创建的线程:" + Thread.currentThread().getName());
}
}
- 实现Callable接口通过FutureTask包装器来创建Thread线程。
public class CallableInstance<Object> implements Callable<Object> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName()+"----> 我是通过实现Callable接口通过FutureTask包装器来实现的实现");
Object object = (Object) "success";
return object;
}
}
/**
* @program: java-learning->ThreadCreateTest2
* @description:
*
* 通过Callable和FutureTask创建线程
*
* 1: 创建Callable接口的实现类,并实现call方法
* 2:创建Callable实现类的实现,使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的call方法的返回值
* 3:使用FutureTask对象作为Thread对象的Target创建并启动线程
* 4:调用FutureTask对象的get()方法来获取子线程执行结束的返回值。
* @author: liushuai
* @create: 2020-04-21 09:59
**/
public class ThreadCreateTest2 {
public static void main(String[] args) throws Exception {
Callable<Object> callable = new CallableInstance<>();
FutureTask<Object> task = new FutureTask<>(callable);
Thread thread = new Thread(task);
System.out.println(Thread.currentThread().getName());
thread.start();
}
9.Thead类有没有实现Runnable接口?
有实现
public class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
10.当调用一个线程对象的start方法后,线程马上进入运行状态码?
不是,只是进行就绪(可运行)状态,等待分配CPU时间片。一旦得到CPU时间片,即进入运行状态。
11. 下面的代码,实际上有几个线程在运行?
public static void main(String[] argc) throws Exception {
Runnable r = new Thread6();
Thread t = new Thread(r, "Name test");
t.start();
}
两个:线程t和main()方法(主线程)。
12.线程的几种状态
线程通常有五种状态:
- 新建(NEW)
- 新创建了一个线程对象
- 就绪(Runnable)
- 线程对象创建后,其它线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
- 运行(Running)
- 就绪状态的线程获取了CPU,执行程序代码
- 阻塞
- 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
- 死亡
- 线程执行玩了或者因为异常退出了run()方法,该线程结束生命
阻塞的情况又分为三种:
- 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入"等待池"中。
进入这个状态后,是不能自动唤醒的,必须依靠其它线程调用notify()或notifyAll()方法才能唤醒。wait和notify、notifyAll()是Object类的方法。
- 同步阻塞:运行的线程在获取对象同步锁时,若该同步锁被别的线程占用,则JVM会把线程放入"锁池"中。
- 其他阻塞:运行的线程执行sleep()或join()方法,或者发出I/O请求时,JVM会把该线程置为阻塞状态。当Sleep()状态超时、Join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
sleep是Thread类的方法。
**
13.说说: sleep、yield、join、wait方法的区别?
sleep()方法需要指定等待的时间,它可以让当前的线程正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其它同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep()方法不会释放锁"锁标志",也就是说如果有sychronized同步块,其它线程仍然不能访问共享数据。 作用域线程
- Thread.sleep()方法用于来暂停线程的执行,将CPU放给线程调度器。
- Thread.sleep()方法是一个静态方法,它暂停的是当前执行的线程。
- Java有两种sleep方法,一个只有一个毫秒参数,另一个有毫秒参数,另一个有毫秒和纳秒两个参数。
- 与wait方法不同,sleep方法不会释放锁。
- 如果其它的线程中断了一个休眠的线程,sleep方法会抛出Interrupted Exception
- 休眠的线程在唤醒之后不保证能获取到cpu,它会先进入就绪状态,与其它线程竞争cpu。
- 有一个易错的地方,当调用t.sleep()的时候,会暂停线程t。这是不对的,因为Thread.sleep是一个静态方法,它会使当前线程等待而不是线程t进入休眠状态。
join()方法
当前线程等待,调用此方法的线程执行结束在继续执行。如:在main方法中调用t.join(),那main()方法在此时进入阻塞状态,一直等待t线程执行完,main方法再恢复到就绪状态,准备继续执行。
join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解;如果一个线程都没有start,那就它也就无法同步了。作用域线程
**
实现原理:
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
yield():它仅仅释放线程所占有的CPU的资源,从而让其它线程有机会运行,但是并不能保证某个特定的线程能够获得CPU资源。谁能获取CPU完全取决于调度器,在有些情况下调用yield()方法的线程甚至会再次得到CPU资源。所以,依赖于yield方法是不可靠的,它只能尽力而为。作用域线程wait():
- wait只能在同步(sychronzied)环境中被调用,而sleep不需要
- 进入wait状态的线程能够被notify和notifyAll线程唤醒,但是进入sleeping状态的线程不能被notify方法唤醒。
- wait通常有条件地执行,线程会一直处于wait状态,直到某个条件变为真,但是sleep仅仅让你的线程进入睡眠状态。
- wait方法在进入wait状态的时候会释放对象的锁,但是sleep方法不会。
wait方法是针对一个被同步代码块加锁的对象。
![4707a8b3ad20abc80049f0db2b31aa01.png](https://i-blog.csdnimg.cn/blog_migrate/03e1445c6eb1fd595d149e32287ad246.png)
14.为什么不推荐使用stop和destroy方法来结束线程的运行?
stop(): 此方法可以强行终止一个正在运行或挂起的线程。但stop方法不安全,就像强行切断计算机电源,而不是正常程序关机,可能会产生不可预料的结果
举例来说:
当一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,并抛出特殊的ThreadDeath()异常。这里的"立即"因为太"立即"了。
假如一个线程正在执行:
synchronized void {
x = 3;
y = 4;
}
由于方法时同步的,多个线程访问时总能保证想x,y被同时赋值,而如果一个线程正在执行到x=3时,被调用了 stop()方法,即使在同步块中,它也干脆地stop了,这样就产生了不完整的残废数据。而多线程编程中最最基础的条件要保证数据的完整性,所以请忘记 线程的stop方法,以后我们再也不要说“停止线程”了。*destroy*():该方法最初用于破坏该线程,但不作任何资源释放。它所保持的任何监视器都会保持锁定状态。不过,该方法决不会被实现。即使要实现,它也极有可能以 suspend() 方式被死锁。如果目标线程被破坏时保持一个保护关键系统资源的锁,则任何线程在任何时候都无法再次访问该资源。如果另一个线程曾试图锁定该资源,则会出现死锁。
15.写个代码说明,终止线程的典型方式。
- 当run()方法执行完后,线程就自动终止了。
- 但有些时候run()方法不会结束(如服务器端监听程序),或者其它需要用循环来处理的任务。在这种情况下,一般是将这些任务放在一个循环中,如while循环。如果想让循环永远运行下去,可以使用while(true){……}来处理。但要想使while循环在某一特定条件下退出,最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。
见代码:
public class ThreadEndTest extends Thread{
public volatile boolean exit = false;
@Override
public void run() {
while (!exit){
System.out.println("我还在");
}
}
public static void main(String[] args) throws InterruptedException {
ThreadEndTest endTest = new ThreadEndTest();
endTest.start();
//主线程延迟5秒
sleep(5000);
endTest.exit=true;
endTest.join();
System.out.println("线程退出");
}
}
16.A线程的优先级是10,B线程的优先级是1,那么当进行调度时一定会调用A吗?
不一定。线程优先级对于不同的线程调度器可能有不同的含义,可能并不是用户直观的推测。
17.synchronized修饰在方法前是什么意思?
一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入.
public class SynchronizedTest {
public static synchronized void read(String name){
System.out.println(name + "开始执行Read 方法");
try {
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(name + "结束执行Read 方法");
}
public static void main(String[] args) {
Runnable runnable = () -> {
System.out.println("线程:"+Thread.currentThread().getName()+"开始");
read(Thread.currentThread().getName());
System.out.println("线程:"+Thread.currentThread().getName()+"结束");
};
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
18.wait方法被调用时,所在的线程是否会释放所持有的锁资源?sleep方法呢?
wait:释放cpu,释放锁sleep:释放cpu,不释放锁
19.wait、notify、notifyAll是在Thread类中定义的方法吗?作用分别是什么?
wait(),notify(),notifyAll()
不属于Thread类,而是属于Object类,也就是说每个对象都有wait(),notify(),notifyAll()的功能。因为每个对象都有锁,锁是每个对象的基础,而wait(),notify(),notifyAll()都是跟锁有关的方法。
三个方法的作用分别是:
- wait:导致当前线程等待,进入阻塞状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。当前线程必须拥有此对象监视器(对象锁)。该线程释放对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行.
- notify:唤醒在此对象监视器(对象锁)上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。此方法只应由作为此对象监视器的所有者的线程来调用.
"当前线程必须拥有此对象监视器"与"此方法只应由作为此对象监视器的所有者的线程来调用"说明wait方法与 notify方法必须在同步块内执行,即synchronized(obj之内).
- notifyAll: 唤醒在此对象监视器(对象锁)上等待的所有线程。
20.notify是唤醒所在对象wait pool中的第一个线程吗?
不是。
调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择。