进程和线程
进程是资源分配的最小单位,线程是CPU调度的最小单位
- 所有与进程相关的资源,都被记录在PCB中
- 进程是抢占处理机的调度单位;线程属于某个进程,共享其资源
- 线程只由堆栈寄存器,程序计数器和TCB组成
- 线程不能看做独立应用,而进程可看做独立应用
- 进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径
- 线程没有独立的地址空间,多进程的程序比多线程程序健壮
- 进程的切换比线程的切换开销大
Java进程和线程的关系
- Java对操作系统提供的功能进行封装,包括进程和线程
- 运行一个程序会产生一个进程,进程包含至少一个线程
- 每个进程对应一个JVM实例(每个JVM实例唯一对应一个堆),多个线程共享JVM里的堆
- Java采用单线程编程模型,程序会自动创建主线程(JVM是多线程的)
Thread常用方法
- start()
- isAlive():判断线程是否存活
- setPriority
- join()
- sleep()
- yield()
- interrupt()
start和run
- run():会引用主线程来执行方法
- start():会使用非主线程来执行方法;执行过程:通过调用JVM_StartThread创建一个子线程,并通过thread_entry调用run方法
Thread和Runnable
- Thread是实现了Runnable接口(仅有一个抽象run方法)的类,使得run支持多线程
- 因Java类的单一继承原则,推荐多使用Runnable接口
public class MyThread extends Thread{}
MyThread mt1 = new MyThread("thread1");
mt1.start();
public class MyRunnable implements Runnable{}
MyRunnable mr1 = new MyRunnable("runnable1");
Thread t1 = new Thread(mr1);
t1.start();
如何实现处理线程的返回值
- 给run()方法传参
- 构造函数传参
- 成员变量传参
- 回调函数传参
- 实现子线程返回值的方法
a 主线程等待法:
- 优点:简单
- 缺点:需要自己实现等待逻辑,当需要等待的变量一多,代码将会变得极其臃肿 / 需要循环多久是不能准确控制的
//当子线程没有返回cw值时,主线程一直等待
while(cw.value == null) {
Thread.currentThread().sleep(100);
}
b 使用Thread类的join()阻塞当前线程以等待子线程处理完毕:
- t.join()
- 优点:实现简单,控制时间精准
- 缺点:粒度不够细
c 通过Callable接口实现:通过FutureTask / 线程池获取
FutureTask:
- 构造函数:接受Callable实例
- isDone():用来判断传给它的Callable实例的call方法是否已经执行完成
- get():用来阻塞当前调用它的线程,直到Callable实例的call方法执行完毕取到返回值,能够精准的获取子线程返回的数据
- get(long timeout, TimeUnit unit):超过时间,抛出异常
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
String value = "test";
System.out.println("Ready to work");
Thread.currentThread().sleep(3000);
System.out.println("task done");
return value;
}
}
//FutureTask
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<String> task = new FutureTask<String>(new MyCallable());
new Thread(task).start();
if(!task.isDone()) {
System.out.println("task has not finished, please wait");
}
System.out.println("task return: "+ task.get());
}
//线程池
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
Future<String> future = pool.submit(new MyCallable());
if(!future.isDone()) {
System.out.println("task has not finished, please wait");
}
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
pool.shutdown();
}
}
Java线程的状态
-
新建(new):创建后尚未启动的线程的状态
-
运行(Runnable):包含Running(正在执行)和Ready(位于线程池中,正在等待CPU为它分配时间)
-
无限期等待(Waiting):不会被分配CPU执行时间,需要显式被唤醒
- 没有设置Timeout参数的Object.wait()方法
- 没有设置Timeout参数的Thread.join()方法
- LockSupport.park()方法
- 限期等待(Timed Waiting):在一定时间后会由系统自动唤醒
- Thread.sleep()方法
- 设置了Timeout参数的Object.wait()方法
- 设置了Timeout参数的Thread.join()方法
- LockSupport.parkNanos()方法
- LockSupport.parkUntil()方法
- 阻塞(Blocked):等待获取排它锁
- 结束(Terminated):已终止线程的状态,线程已经结束执行,不可复活
join
Thread的非静态方法join()让一个线程等待另外一个线程完成才继续执行。如果线程A执行体中调用B线程的join()方法,则A线程将会被阻塞,直到B线程执行完为止,A才能得以继续执行。
sleep和wait的区别
- 基本的差别
- sleep是Thread类的方法,wait是Object类的方法
- sleep()方法可以在任何地方使用
- wait()方法只能在synchronized方法或synchronized块中使用
- 最主要的本质区别
- Thread.sleep只会让出CPU,不会导致锁行为的改变
- Object.wait不仅让出CPU,还会释放已经占有的同步资源锁(wait方法必须写在同步方法中,只有获得锁,才可释放)
notify和notifyall的区别
- 锁池EntryList
假设线程A已经拥有了某个对象(不是类)的锁,而其它线程B,C想要调用这个对象的某个synchronized方法(或者块),由于B,C线程在进入对象的synchronized方法(或者块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正在被线程A所占用,此时B,C线程会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池。
- 等待池WaiSet
假设线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁,同时线程A就会进入到了该对象的等待池中,进入到等待池中的线程不回去竞争该对象的锁。
- notify和notifyall的区别
- notify只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会
- notifyall会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会
yield
- 当调用Thread.yield()函数时,会给线程调度器一个当前线程愿意让出CPU使用的按时,但是线程调度器可能会忽略这个暗示。
- 不会对锁产生影响
如何中断线程-interrupt()
- 已经被抛弃的方法
- 通过调用stop()方法停止线程(太过暴力且不安全)
- 通过调用suspend()和resume()方法
- 目前使用的方法
- 调用interrupt(),通知线程应该中断了(hint仅仅是一种暗示)
– 如果线程处于被阻塞状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
– 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响。
需要被调用的线程配合中断:
- 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。