二、线程
现代操作系统中,线程是被操作系统调度和执行的单位。进程是拥有资源所有权的单位。Java中的线程Thread是对操作系统的线程的一种抽象,他与操作系统的线程有些不一样。
2.1、线程的特点
1)一个进程中包含一个或多个进程;
2)同一个进程中的线程可以共享进程中的资源,包括内存资源、IO资源等等;
3)线程创建、切换、销毁成本比进程小;
4)线程间通信方便、效率高,因为可以共享进程中的资源,所以可以使用共享内存进行通信。
2.2、java中创建线程的方法
(1)Runnable接口
编写一个类实现Runnable接口,并实现run()方法。创建该类的对象来创建一个任务。再新建一个Thread类的对象,将这个任务对象通过Thread的构造函数传入,并执行Thread类对象的start()方法。
public class Demo001 {
public static void main(String[] args) {
Task01 task = new Task01();
Thread thread = new Thread(task);
thread.start();
}
}
class Task01 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":任务正在执行");
}
}
(2)Thead类
因为Thread类本身已经实现类Runnable接口。所以我们可以编写一个类继承Thread类,并覆盖Thread的run()方法。再创建这个类的对象,然后执行对象的start()方法。
public class Demo002 {
public static void main(String[] args) {
Task002 task = new Task002();
task.start();
}
}
class Task002 extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":任务正在执行");
}
}
(3)Callable接口
编写一个类,实现Callable接口,覆写call方法,创建Callable实现类对象,再使用FutureTask包装此对象,将包装后的对象传入Thread的构造函数中,并执行Thread对象的start()方法。
关于Callable和Future详见2.6
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Demo003 {
public static void main(String[] args) throws Exception{
Task003 task003 = new Task003();
FutureTask<Integer> futureTask = new FutureTask<>(task003);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println("线程已经开始");
Integer result = futureTask.get();
System.out.println("线程结束,结果为 = " + result);
}
}
class Task003 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Thread.sleep(5000L);
return 100;
}
}
2.3、Thread类的相关重要方法
1)static void sleep(long millis) throws InterruptedException
该方法可以让当前执行的线程睡眠指定毫秒数,这是一个阻塞操作,执行该方法会将当前线程放入阻塞队列中,到达指定时间后在从阻塞队列中出来,进入就绪队列;
该方法抛出InterruptedException,当阻塞过程中收到中断信号,会抛出异常。
2)static Thread currentThread()
获取当前执行本段代码的线程对象。
3)void setDeamon(boolean on)
设置一个线程为守护线程,只有线程在未启动的时候才可以设置,如果已经启动线程在调用本方法会抛出一个运行时异常IllegalThreadStateException;
守护线程又叫后台线程,当所有其他非守护线程全部结束后,守护线程自动结束;
默认情况,从非守护线程上创建的线程是非守护线程,从守护线程上创建的线程是守护线程,除非手动指定。
4)static void yield()
当前线程让出执行时间片,不一定有用。
5)void join()、void join(long millis)
当前线程等待执行该方法的线程结束后才能继续执行。这是非静态方法,需要一个线程对象的引用。当前线程会进入阻塞状态,直到执行该方法的线程结束,才能继续执行;
带参数的方法,会设置一个阻塞时间,时间过去后,执行该方法的线程还没结束,当前线程也会退出阻塞状态。
2.4、线程中断
2.4.1、线程中断和检测中断状态的方法的方法
Thread类中有3个方法和线程中断相关。
执行代码的线程称为当前线程,调用方法的线程称为执行线程
(1)void interrupt()
当前线程向执行线程发送一个中断信号,且设置执行线程的中断状态为true
(2)boolean isInterrupted()
返回执行线程的中断状态
(3)static boolean interrupted()
静态方法,返回当前线程的中断状态,且清除当前线程的中断状态,也就是将中断状态只为false,相当于处理了这个中断信号
2.4.2、线程中断异常
JDK中有很多方法会抛出InterruptedException线程中断异常。在这些方法的执行过程中,会一直检测当前线程的中断状态,如果收到中断信号,也就是中断状态变为了true,那么这个方法就会立即返回,并且抛出一个InterruptedException线程中断异常,并且会清除当前线程的中断状态。
Thread中会抛出InterruptedException线程中断异常的方法:
(1)public static native void sleep(long millis) throws InterruptedException;
(2)public final void join() throws InterruptedException
2.5、线程通信wait/notify机制
1)等待/唤醒机制的使用场景
等待唤醒机制存在于多线程之间的通信。一个线程在执行过程中,如果发现执行条件不满足,它应该释放当前持有的锁,并进入当前锁的等待队列中去等待(wait),直到有另外一个线程完成了指定条件的操作,它会去唤醒(notify)等待队列中的等待的进程,将他们放入到同步队列中去,到了同步队列中后,这些线程又拥有了去获取锁的权利,等他们获取到锁之后,就可以继续执行他们的代码了。
java中每一个锁都对应了一个等待队列和同步队列。
2)通用模式
一般会有两个以上线程,分别是等待线程和通知线程
等待线程的通用模式
synchronized(锁对象){
逻辑1
while(条件不满足){
锁对象.wait();
}
逻辑2
}
通知线程的通用模式
synchronized(锁对象){
完成条件
锁对象.notifyAll();
}
注:
a)锁对象要是同一把锁;
b)wait和notify方法必须写在同步代码块之内,否则会报错。
c)等待线程判断条件时,需用while,而不是if
3)wait和sleep的区别
a)wait是object类的示例方法,sleep是Thread类的静态方法;
b)wait方法会释放锁对象,sleep方法不会释放;
c)wait停止后可以notify来唤醒,而sleep必须设置时间;
2.6、Callable接口和Future
(0)代码示例
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Demo003 {
public static void main(String[] args) throws Exception{
Task003 task003 = new Task003();
FutureTask<Integer> futureTask = new FutureTask<>(task003);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println("线程已经开始");
Integer result = futureTask.get();
System.out.println("线程结束,结果为 = " + result);
}
}
class Task003 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Thread.sleep(5000L);
return 100;
}
}
(1)Callable接口
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
(2)Future接口
Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
(3)FutureTask类
FutureTask类实现了RunnableFuture接口,RunnableFuture接口实现了Future类和Runnable类。所以FutureTask是Future的实现类,并且它是一个Runnable任务。
所以我们将FutureTask传入Thread的target参数,其实Thread线程执行的是FutureTask的任务,即执行的是FutureTask的run方法。
因为FutureTask对象封装了Callable的子类,所以我们在FutureTask的run方法中就可以同步的执行Callable子类的call方法。当调用FutureTask对象的get()方法时,如果Callable还没执行完,就将其阻塞在此即可,直到call()方法执行完,返回结果。