进程和线程的区别
进程和线程的由来
进程是资源分配的最小单位,线程是CPU调度的最小单位
所有与进程相关的资源,都被记录在PCB中
进程是抢占处理机的调度单位;线程属于某个进程,共享其资源
线程只有堆栈寄存器、程序计数器和TCB(线程控制表)组成
总结
线程不能看做独立应用,而进程可看做独立应用
进程有独立的地址空间,互相不影响,线程只是进程的不同执行路径
线程没有独立的地址空间,多进程的程序比多线程的程序健壮
进程的切换比线程的切换开销大
Java进程和线程的关系
Java对操作系统提供的功能进行封装,包括进程和线程
运行一个程序会产生一个进程,进程包含至少一个线程
每个进程对应一个JVM实例,多个线程共享JVM里的堆
Java采用单线程编程模型,程序会自动创建主线程
主线程可以创建子线程,原则上要后于子线程完成执行
进程间通信
管道:速度慢,容量有限,只有父子进程能通讯
FIFO:任何进程间都能通讯,但速度慢
消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
信号量:不能传递复杂消息,只能用来同步
共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
Thread中的run和start的区别
调用start()方法会创建一个新的子线程并启动
run()方法只是Thread的一个普通方法的调用
Thread和Runnable的关系
Thread是实现了Runnable接口的类,使得run支持多线程
因类的单一继承原则,推荐多使用Runnable接口
public class MyRunnable implements Runnable {
private String name;
public MyRunnable(String name){
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Thread start:" + this.name + ",i=" + i);
}
}
public static void main(String[] args) {
MyRunnable mr1 = new MyRunnable("Runnable1");
MyRunnable mr2 = new MyRunnable("Runnable2");
MyRunnable mr3 = new MyRunnable("Runnable3");
Thread t1 = new Thread(mr1);
Thread t2 = new Thread(mr2);
Thread t3 = new Thread(mr3);
t1.start();
t2.start();
t3.start();
}
}
如何给run()方法传参
构造函数传参
成员变量传参
回调函数传参
如何实现处理线程的返回值
主线程等待法
使用Thread类的join()阻塞当前线程以等待子线程处理完毕
通过Callable接口实现:通过FutureTask或者线程池获取
import java.util.concurrent.*;
/**
* @author lijiayin
*/
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
String value = "test";
System.out.println("Ready to work");
Thread.sleep(5000);
System.out.println("Work finish");
return value;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<MyCallable> futureTask = new FutureTask(new MyCallable());
new Thread(futureTask).start();
if(!futureTask.isDone()){
System.out.println("Work not finish");
}
System.out.println("return value : " + futureTask.get());
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> future = executorService.submit(new MyCallable());
if(!future.isDone()){
System.out.println("Work not finish");
}
try{
System.out.println("return value : " + future.get());
}catch (Exception e){
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
}
线程的状态
新建(New):创建后尚未启动的线程状态
-
运行(Runnable):包含Running和Ready
无限期等待(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):已终止线程的状态,线程已经结束执行
sleep和wait的区别
基本的差别
sleep是Thread类的方法,wait是Object类中定义的方法
sleep()方法可以在任何地方使用
wait()方法只能在synchronized方法或者synchronized块中使用
最主要的本质区别
Thread.sleep只会让出CPU,不会导致锁行为的改变
Object.wait不仅让出CPU,还会释放已经占有的同步资源锁
notify和notifyAll的区别
两个概念
锁池EntryList:假设线程A已经拥有了某个对象(不是类)的锁,而其它线程B、C想要调用这个对象的synchronized方法(或者块),由于B、C线程在进入对象的synchronized方法(或者块)之前必须获得该对象锁的拥有权,而恰巧该对象的锁目前正被线程A所占用,此时B、C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池。
等待池WaitSet:假设线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁,同时线程A就进入到了该对象的等待池中,进入到等待池中的线程不会去竞争该对象的锁。
结论
notifyAll会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会。
notify只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会。
yield
概念
当调用Thread.yield()函数时,会给线程调度器一个当前线程愿意让出CPU使用的暗示,但是线程调度器可能会忽略这个暗示。
对锁行为没有影响。
interrupt函数
通知线程应该中断了
如果线程处于被阻塞状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常
如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响。
需要被调用的线程配合中断
在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断就自行停止线程。
如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响。