多线程(四)并发与并行,守护线程,创建线程方式,Runnable与Callable,notify与notifyAll,sleep与wait,start与run
1、并发与并行的区别?
并发是在同一个CPU上,在若干道程序中进行多路复用,通过分配时间片的方式。
并行是在多个CPU上,每个CPU运行一个程序。
2、守护线程是什么?
守护线程在线程中拥有最低的优先级,用于为系统中的其他对象和线程提供服务。将一个线程设置为守护线程是在线程创建之前调用线程对象的setDaemon方法。典型的守护线程是JVM的垃圾回收线程。当所有的非守护线程都结束的时候,程序也就终止了,虚拟机就退出了。
需要注意的点:
(1)thread.setDaemon(true)必须在thread.start()之前设置,否则会报IlegalThreadStateException异常
(2)在Daemon中产生的新线程也是Daemon的
(3)守护线程永远不去访问固有资源,如文件、数据库等,因为它们会在任何时候甚至一个操作的中间发生中断。
3、创建线程的方式
(1)继承Thread类。
重写run方法,创建Thread类子类的实例,直接start()即可。
(2)实现Runnable接口
实现Runnable接口,并重写run方法。创建Runnable是实现类的实例,并把这个实例作为参数创建Thread对象。然后调用start()方法。
(3)使用Callable和Future创建线程
- 1、创建Callable接口的实现类,并实现call()方法。然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
- 2、使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值。
- 3、使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
- 4、调用FutureTask对象的get()方法获得子线程执行结束后的返回值。
public class TestCallable{
public static void main(String[] args) {
ThreadSub sub = new ThreadSub();
// 用FutureTask接收返回参数
FutureTask<String> result = new FutureTask<>(sub);
// FutureTask也实现了Runnable接口
new Thread(result).start();
try {
// 等线程执行完之后,获取返回值
String str = result.get();
System.out.println(str);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadSub implements Callable<String>{
@Override
public String call() throws Exception {
// 可以写逻辑
String string = "abc";
// 返回值
return string ;
}
}
(4)通过线程池创建线程
4、Runnable和Callable区别?
(1)实现Callable接口的任务线程能返回执行结果,而实现Runnable的不能。
(2)Callable接口的call()方法允许抛出异常,而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛。
5、notify和notifyAll的区别?
如果某些线程在等待某些条件触发,那么当这些条件为真时,可以使用notify和notifyAll来通知那些等待中的线程重新运行。不同的是,notify只通知一个线程,其他线程不会收到通知;notifyAll是通知所有线程。
比如:如果你的程序中有两个线程,即生产者和消费者。那么当生产者生产消息后,可以通知消费者,让消费者开始消费数据,因为缓存区不为空(有消息待消费)。
如果消费者消费数据了,就会通知生产者,因为缓存区不再为满。
6、sleep()和wait()的区别
(1)sleep是Thread线程类的方法,wait是Object顶级类的方法。
(2)sleep可以在任何地方使用,而wait只能在同步块或者同步方法中使用。
(3)sleep和wait调用后,都会暂停当前线程并让出CPU的执行时间。但不同的是sleep不会释放当前持有的对象的锁资源,到时间会继续执行。而wait会放弃所有锁并需要notify或者notifyAll后重新获取到对象资源后才能继续执行。
(4)sleep必须捕获异常,而wait/notify/notifyAll不需要
7、start()和run()方法的区别?
start()方法:可以启用线程,使线程处于就绪状态,并没有运行,一旦得到时间片,就开始执行run方法,run方法运行结束,线程就此终止。
run方法:只是thread的一个普通方法调用。
public static void main(String[] args) {
ThreadTwo threadTwo = new ThreadTwo();
threadTwo.start();
System.out.println("pong");
}
class ThreadTwo extends Thread{
@Override
public void run(){
System.out.println("ping");
}
}
输出:pongping
public static void main(String[] args) {
ThreadTwo threadTwo = new ThreadTwo();
threadTwo.run();
System.out.println("pong");
}
class ThreadTwo extends Thread{
@Override
public void run(){
System.out.println("ping");
}
}
输出:pingpong
由此可见,start方法只是启动线程,run方法只是一个普通方法调用。
8、一个线程连续调用两次start()会出现什么?
// start 方法源码
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
会抛出IllegalThreadStateException异常,由于start0()方法的实现已经封装在dll中了,但是肯定是在start0方法中修改了线程状态值threadStatus,如果再次进入,就会抛异常。