一、进程和线程的区别
首先记住一句话:进程是资源分配的最小单位,线程是CPU调度的最小单位(根本)
所有与进程相关的资源,都保存在PCB中;
进程是抢占处理机的调度单位,线程属于某个进程,共享其资源,即一个进程内可能存在多个线程,多个线程可以共享资源;
线程只由堆栈寄存器、程序计数器和TCB组成;
总结:
1.线程不能看做独立应用,而进程可以看做独立应用;
2.进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径;
3.线程没有独立的地址空间,多进程的程序比多线程程序健壮,因为单线程的进场如果线程挂了,那进场也就挂了,如果是多线程,其中一个线程挂了还有其他的可以执行;
4.进程的切换比线程的切换开销大,毕竟一个进程内可能有多个线程;
二、Java进程和线程的关系
1.Java对操作系统提供的功能进行封装,包括进程和线程;
2.运行一个程序会产生一个进程,进程包含至少一个线程;
3.每个进程对应一个JVM实例,多个线程共享JVM的堆,每个线程都有自己独立的栈;
4.Java采用单线程编程模型,程序会自动创建主线程;
三、Thread中start和run方法的区别
1.调用start()方法会创建一个新的子线程来运行程序;
2.run()方法只是Thread的一个普通方法,还是依赖于主线程去运行,并不会创建一个新的线程;
四、Thread和Runnable的关系
1.Thread是实现了Runnable接口的类,使得run支持多线程;
2.因类的单一继承原则,推荐多使用Runnable接口;
五、如何实现处理线程的返回值
实现的方式有三种:
主线程等待法
操作简单,但是对性能有很大的影响,代码如下:
public class ThreadTest implements Runnable{
private String name;
public static void main(String[] args) throws InterruptedException {
ThreadTest threadTest = new ThreadTest();
Thread t = new Thread(threadTest);
t.start();
//如果name值为空就继续等待1秒
while (threadTest.name==null){
Thread.sleep(1000);
}
System.out.println(threadTest.name);
}
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
name="大A";
}
}
使用Thread类的join()方法
join()方法会阻塞当前线程直到等待子线程处理完毕,这种方法比主线程等待法稍微好点,因为join()方法在子线程处理完就会直接继续运行主线程,而主线程等待法每次都需要等待一定时间,但是join()方法性能还是不够好,毕竟会阻塞,以下为代码示例:
public class ThreadTest implements Runnable{
private String name;
public static void main(String[] args) throws InterruptedException {
ThreadTest threadTest = new ThreadTest();
Thread t = new Thread(threadTest);
t.start();
//使用join方法让主线程等待
t.join();
System.out.println(threadTest.name);
}
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
name="大A";
}
}
通过Callable接口实现——通过FutureTask
不过逼逼,直接上代码
首先实现Callable接口,然后重写call方法,也就是你的多线程需要执行的业务逻辑:
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
String result="OK";
System.out.println("准备开始执行");
Thread.sleep(3000);
System.out.println("执行完毕");
return result;
}
}
接着开始测试,是不是很简单!
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Future {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<String> task = new FutureTask<String>(myCallable);
Thread thread = new Thread(task);
thread.start();
//判断task线程是否完成
if(!task.isDone()){
System.out.println("还没有完成哦,请稍等");
}
//获取task线程完成后的返回值
System.out.println(task.get());
}
}
通过Callable接口实现——通过线程池获取
没啥好说的,直接上代码:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreadPoll {
public static void main(String[] args) {
//创建线程池
ExecutorService poll = Executors.newCachedThreadPool();
//提交任务
Future<String> future = poll.submit(new MyCallable());
//判断是否执行完毕
if(!future.isDone()){
System.out.println("还没有完成哦,请稍等");
}
try {
//获取返回值
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
//关闭线程池
poll.shutdown();
}
}
}
使用线程池的好处就是可以提交多个实现Callable的类,让线程池去并发处理结果;
六、线程的状态
New(新建):创建后尚未启动的线程的状态
Runnable(运行):包含Running和Ready
Waiting(无限期等待):不会被分配CPU执行时间,需要显式被唤醒
Timed Waiting(限期等待):在一定时间后会由系统自动唤醒
Blocked(阻塞):等待获取排它锁
Terminated(结束):已终止线程的状态,线程已经结束执行
七、sleep和wait的区别
基本的差别
sleep是Thread类的方法,wait是Object类中定义的方法;
sleep()方法可以在任何地方使用;
wait()方法只能在synchronized方法或synchronized块中使用;
本质的差别
Thread.sleep只会让出CPU,不会导致锁行为的改变,也就是不会释放当前持有的锁;
Object.wait不仅让出CPU,还会释放已经占有的同步资源锁;
八、notify和notifyAll的区别
首先我们需要先了解两个概念,锁池EntryList和等待池WaitSet;
锁池EntryList
假设线程A已经拥有了某个对象(不是类)的锁,而其他线程B、C想要调用这个对象的某个synchronized方法(或者块),由于B、C线程在进入对象的synchronized方法(或者块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正在被线程A所占用,此时B、C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池。
等待池WaitSet
假设线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁,同时线程A就进入到了该对象的等待池中,进入到等待池中的线程不会去竞争该对象的锁。
这时候咱们就可以来了解notify和notifyAll的区别了,这两个方法的功能都是唤醒在等待池的线程,从方法名中我们就可以大概的知道两者的区别:
notifyAll:会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会。
notify:只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会。
九、yield
当调用Thread.yield()函数时,会给线程调度器一个当前线程愿意让出CPU使用的暗示,但是线程调度器可能会忽略这个暗示。
十、interrupt中断函数
已经被抛弃的方法:stop()、suspend()、resume(),这些方法都太过暴力,会直接将当前线程退出;
目前正确使用的方法:调用interrupt(),通知线程应该中断了,但是线程并不一定会中断,取决于线程自身:
1.如果线程处于被阻塞状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptException异常。
2.如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响。
所以如果想要中断线程就需要被调用的线程配合中断,比如:
在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。