[TOC]
Thread类和Runnable接口
创建线程的常规方法有两种,一种是实现Runnable接口一种是扩展Thread类;
Runnable是一个函数式接口,只有一个void run()方法;
Runnable接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
注意到一个有趣的现象,这里的方法定义成abstract了,一般要得到同样的结果,可以直接在接口里面声明“void run();”,编译器会把它转成public abstract ...()的形式
Thread类
Thread类是一个线程的抽象,包含了几个重要的属性:
- 优先级
- 目标任务(Runnable实例)
- 是否是守护线程
- 线程名(默认Thread-N)、ID(默认N)、group(默认currentThread.getGroup()线程组的作用是:可以批量管理线程或线程组对象,有效地对线程或线程组对象进行组织。)
大部分方法的实现都是native方法,主要方法有:
- sleep()
sleep方法使当前线程休眠(阻塞)N毫秒,但是不会释放持有的锁,在休眠期间可能会被系统挂起
- yield()
主动让出CPU,不会释放持有的锁资源,但是调度器可以忽略让出CPU请求,且即使同意,让出的时间也不确定,一般让出后高优先级的线程能执行,但是也不一定,也许让出后自己又再次获得了;
/**
* 测试是否会释放锁
*/
public class YieldTest {
static class MyTask {
void a() throws InterruptedException {
System.out.println("method a run");
synchronized (this) {
System.out.println("a lock");
Thread.yield();
Thread.sleep(1000);
System.out.println("a unlock");
}
}
void b() throws InterruptedException {
System.out.println("method b run");
synchronized (this) {
System.out.println("b lock");
Thread.yield();
Thread.sleep(700);
System.out.println("b unlock");
}
}
}
public static void main(String[] args) {
final MyTask task = new MyTask();
new Thread(() -> {
try {
task.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "ThreadA").start();
Thread tb = new Thread(() -> {
try {
task.b();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "ThreadB");
tb.setPriority(9);
tb.start();
}
}
结果:
method a run
a lock
method b run
a unlock
b lock
b unlock
证明不会释放锁
- wait()/notify()/notifyAll()
条件对象上的等待和唤醒,必须在同步块或者同步方法内部执行,且方法接受者必须和锁对象一致。 wait方法会释放当前持有的锁,notify不会。
public class WaitTest {
static class MyTask {
private final Object lock = new Object();
void a() throws InterruptedException {
System.out.println("method a run");
synchronized (lock) {
System.out.println("a lock");
lock.wait();
System.out.println("a unlock");
}
}
void b() {
System.out.println("method b run");
synchronized (lock) {
System.out.println("b lock");
lock.notify();
System.out.println("b unlock");
try {
System.out.println("b sleep");
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("sleep end");
}
}
}
}
public static void main(String[] args) {
final MyTask task = new MyTask();
new Thread(() -> {
try {
task.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "ThreadA").start();
new Thread(() -> {
task.b();
}, "ThreadB").start();
}
}
结果:
method a run
a lock
method b run
b lock
b unlock
b sleep
sleep end
a unlock 证明notify没有释放锁
- start()
启动一个线程,实际执行时间要由OS调度,启动后执行run方法,注意不能对一个线程执行多次start()方法,会抛出异常。
- join() /join(n mills)
t.join()当前线程阻塞直到线程t执行结束
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("start run");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("run end");
});
t.start();
// t.join();
t.join(1000);
System.out.println("main end");
}
}
- interrupt()/isInterrupted()/interrupted()
interrupt()中断一个线程,实际上只是发出中断请求,并将线程的中断标志置true,并不是操作系统层面上中断一个线程,线程内部可以检测中断标志位然后对应做出反应;
isInterrupted()返回线程中断状态,interrupted()是静态方法,也是返回中断状态,区别和注意点在于这个方法会清除标志位,也就是将true置为false;
- 废弃的方法:suspend()/resume()
分别为挂起、恢复一个线程,会造成数据不一致问题以及死锁问题,已经弃用。suspend和resume方法很容易引起死锁问题。如果挂起一个持有锁的线程,挂起并不会释放锁,如果一直没有被resume,那么这个锁就永远被这个挂起的线程独占,也就造成了其他线程的饥饿。或者如果调用resume的线程等待获取被挂起线程持有的锁,那就会造成死锁。
- 废弃的方法: stop
stop方法会强制终止一个执行中的线程,会强制结束线程内的未结束的方法,包括run方法,这种强制杀死线程的方法无异于断电操作,对于需要回滚的操作破坏力极强。对于被终止的线程,会立即释放线程中持有的所有锁,因为锁已经释放,对数据的破坏会被其他尚未停止的线程观察到。
stop方法抛出一个ThreadDeath错误对象来杀死线程。
- get/setContextClassLoader
用来打破类加载机制的双亲委派模型
线程状态
- New
创建了一个Thread对象时,还没有start,这个时候就是new状态
- Runnable
运行和就绪统称为可运行,这个状态在start之后
- Blocked
等待获取一个内部的对象锁(而非java.util.concurrent库里面的锁)时,进入阻塞状态;
- Waiting
线程等待另一个线程通知一个条件时,进入等待状态,wait或join方法的调用会进入等待状态,另外等待concurrent库的Lock或者Condition时,也会进入等待状态;
- TimeWaiting
Thread.sleep,Object.wait/join/Lock.tryLock/Condition.await的计时版本;
- Terminated
自然结束、异常结束
线程的创建和启动
Thread的基本创建方式:
// 未指定优先级默认为5,java线程优先级从1-10,10最高
@Test
public void createThread() {
// extend class Thread
class MyThread extends Thread { // or implements Runnable
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " run");
}
}
new MyThread().start();
// 匿名内部类
new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " run");
}
}.start();
// 匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " run");
}
}).start();
// lambda
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " run");
},"Thread-lambda").start();
}
Runnable接口的run方法是没有返回值的(Thread类也是实现了Runnable接口),且不能抛出受检查的异常,如果需要线程执行结束的结果,需要将结果写进一个外部变量中。Callable接口可以有返回值且能抛出受检查的异常。Callable接口通常用在执行器中,返回一个Future对象可以阻塞或非阻塞的获取执行结果,在执行器框架中,Runnable接口和Callable接口都是任务的抽象,对应不同的使用场景。
如果要将Callable接口用于单个线程需要一个帮助类FutureTask,它将Callable转换成一个Runnable,使其可以传入Thread的构造器。
Callable也是一个函数式接口:
@FunctionalInterface
public interface Callable {
/**
* 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;
}
示例:
Callable c = () -> {
System.out.println(Thread.currentThread().getName() + " run");
return 100;
};
FutureTask task = new FutureTask<>(c);
new Thread(task).start();
try {
//阻塞直到获取结果
System.out.println(task.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
停止一个线程
停止一个线程不能使用stop方法,这个方法十分的危险!
常用的方法有轮询标志位或者利用InterruptException(抛出异常线程肯定结束)
public class StopAThread {
@Test
public void stopwithCheckInterruptStaus() throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("work0");
System.out.println("work1");
System.out.println("work2");
while (!Thread.currentThread().isInterrupted()) {
System.out.println("no,current is not interrupted");
}
System.out.println(Thread.currentThread().getName() + " will end");
});
t.start();
Thread.sleep(1);
t.interrupt();
t.join();
System.out.println("main end");
}
@Test
public void stopByInterruptedException() throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("work0");
System.out.println("work1");
System.out.println("work2");
try {
TimeUnit.SECONDS.sleep(30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " will end");
});
t.start();
Thread.sleep(1);
t.interrupt();
t.join();
System.out.println("main end");
}
@Test
public void stopByCheckFlag() throws InterruptedException {
final boolean[] flag = {false}; // 注意变量的可见性,此处由于是局部变量,是肯定可见的
Thread t = new Thread(() -> {
System.out.println("work0");
System.out.println("work1");
System.out.println("work2");
while (!flag[0]) {
System.out.println("working");
}
System.out.println(Thread.currentThread().getName() + " will end");
});
t.start();
Thread.sleep(1);
flag[0]=!flag[0];
t.join();
System.out.println("main end");
}
}