线程的创建与运行
在线程创建之前,需要先知道什么是线程。
线程 是进程的一个实体,线程是进程的一个单一的顺序执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程资源。
Java中线程创建有三种方法:
- 继承Thread类并重写run方法
- 实现Runnable接口的run方法
- 使用FutureTask方式
我们看一下代码实现
package com.thread.create;
/**
* 继承Thread类并重写run方法
**/
public class TestThread extends Thread {
// 继承Thread类并重写run方法
@Override
public void run() {
// TODO 自动生成的方法存根
// super.run();
System.out.println("i an a child thread");
}
public static void main(String[] args) {
// 创建线程
TestThread thread = new TestThread();
// 启动线程
thread.start();
/*
* 调用 start 方法后线程并没有马上执行而是处于就绪状态,这个就绪状态是指该 线程已经获取了除 CPU 资源外的其他资源,等待获取 CPU
* 资源后才会真正处于运行状态。 一旦 run 方法执行完毕,该线程就处于终止状态。
*/
/*
* 使用继承方式的好处是,在 run() 方法内获取当前线程直接使用 this 就可以了,无须 使用 Thread.currentThread()
* 方法;不好的地方是 Java 不支持多继承,如果继承了 Thread 类, 那么就不能再继承其他类。另外任务与代码没有分离,当多个线程执行一样的任务时需要
* 多份任务代码
*/
}
}
//package com.thread.create;
public class TestRunnabale {
/*
* 实现Runnable接口的run方法
*/
public static class RunableTask implements Runnable {
@Override
public void run() {
System.out.println("I am a child thread");
}
}
public static void main(String[] args) throws InterruptedException {
RunableTask task = new RunableTask();
new Thread(task).start();
new Thread(task).start();
}
}
package com.thread.create;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 使用FutureTask方式
**/
public class TestFutureTask {
//创建任务类CallerTask
public static class CallerTask implements Callable<String>{
@Override
public String call() throws Exception {
// TODO 自动生成的方法存根
return "hello";
}
public static void main(String[] args) throws InterruptedException {
//创建异步任务
FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
//启动线程
try {
//等待任务执行完毕,并返回结果
String result = futureTask.get();
System.out.println(result);
}catch(ExecutionException e) {
e.printStackTrace();
}
System.out.println(111);
}
}
}
线程的等待与通知
当线程调用共享对象的wait()方法时,当前线程指挥释放当前共享对象的锁,当前线程持有的其他共享对象的解释器锁并不会释放。
调用wait()后该线程会释放当前共享对象的锁,该线程将被挂起
public class Wait {
// 创建资源
private static volatile Object resourceA = new Object();
private static volatile Object resourceB = new Object();
public static void main(String[] args) throws InterruptedException {
// 创建线程A
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try {
// 获取resourceA共享资源的监视器锁
synchronized (resourceA) {
System.out.println("threadA get resourceA lock");
// 获取resourceB共享资源的监视器锁
synchronized (resourceB) {
System.out.println("threadA get resourceB lock");
// 线程A阻塞,并释放取得的resourceA的锁
System.out.println("threadA release resourceA lock");
resourceA.wait();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 创建线程B
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
try {
//休眠1s
Thread.sleep(1000);
// 获取resourceA共享资源的监视器锁
synchronized (resourceA) {
System.out.println("threadB get resourceA lock");
System.out.println("threadB try get resourceB lock...");
// 获取resourceB共享资源的监视器锁
synchronized (resourceB) {
System.out.println("threadB get resourceB lock");
// 线程A阻塞,并释放取得的resourceA的锁
System.out.println("threadB release resourceA lock");
resourceA.wait();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//启动线程
threadA.start();
threadB.start();
//等待两个线程结束
threadA.join();
threadB.join();
System.out.println("main over");
}
}
运行结果:
threadA get resourceA lock
threadA get resourceB lock
threadA release resourceA lock
threadB get resourceA lock
threadB try get resourceB lock…
可以看到,线程A调用resourceA的wait()方法之后,线程A释放了resourceA的监视器锁,然后被挂起,但并没有释放取到的resourceB的锁。
notify()与notifyAll()的区别
- notify(): 一个线程调用了共享对象的notify()方法后,会唤醒 一个 在该共享变量上调用wait系列方法后被挂起的线程。
一个共享变量上可能有多个线程在等待,具体唤醒哪个线程是随机的。 - notifyAll(): 该方法会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程。
用一个程序来体会他们的区别
public class NotifyAll {
// 创建资源
private static volatile Object resourceA = new Object();
public static void main(String[] args) throws InterruptedException {
// 创建线程A
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
// 获取resourceA共享资源的监视器锁
synchronized (resourceA) {
System.out.println("threadA get resourceA lock");
try {
System.out.println("threadA begin wait");
resourceA.wait();
System.out.println("threadA end wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 创建线程B
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
// 获取resourceA共享资源的监视器锁
synchronized (resourceA) {
System.out.println("threadB get resourceA lock");
try {
System.out.println("threadB begin wait");
resourceA.wait();
System.out.println("threadB end wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//创建线程C
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println("threadC begin notify");
//体会notify与notifyAll()的不同
resourceA.notify();
//resourceA.notifyAll();
}
}
});
//启动线程
threadA.start();
threadB.start();
Thread.sleep(1000);
threadC.start();
//等待线程结束
threadA.join();
threadB.join();
threadC.join();
System.out.println("main over");
}
}
等待线程执行终止的join方法
当线程A调用线程B的join()方法,线程A就会被阻塞,等待线程B执行结束后返回,如果线程A阻塞期间其他线程调用了线程A的interrupt方法中断了线程A,线程A就会抛出InterruptException异常而返回。
join的功能在上文例子中已经体现。
线程睡眠sleep方法
sleep()方法 Thread类有一个静态的sleep方法,调用线程会暂时让出指定时间的执行权。期间不参与CPU调度。
睡眠的时候该线程持有的监视器资源锁不会让出,指定时间之后该线程会正常返回,线程就处于就绪状态, 参与CPU调度,获取CPU资源后可继续执行。
如果睡眠期间其他线程调用该线程的interrupt()方法中断了该线程,则该线程会在调用sleep()的地方抛出InterruptedExecption异常而返回。
public class Sleep {
// 创建一个独占锁
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
// 创建线程
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
// 获取独占锁
lock.lock();
try {
System.out.println("child threadA is in sleep");
Thread.sleep(5000);
System.out.println("child threadA is in awaked");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
});
// 创建线程B
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
// 获取独占锁
lock.lock();
try {
System.out.println("child threadB is in sleep");
Thread.sleep(5000);
System.out.println("child threadB is in awaked");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
});
//启动线程
threadA.start();
threadB.start();
}
}
运行结果:
child threadA is in sleep
child threadA is in awaked
child threadB is in sleep
child threadB is in awaked
可以看出threadA在睡眠时拥有的监视器资源不会被释放。
让出CPU执行权的yield方法
yield():当线程调用yield方法时,当前线程就会让出CPU的使用权,然后处于就绪状态,线程调度器会从线程就绪队列中获取一个线程优先级最高的线程来获取CPU执行权,也有可能时刚刚让出CPU的线程。
public class TestYield implements Runnable {
public TestYield() {
// 创建并启动线程
Thread t = new Thread(this);
t.start();
}
@Override
public void run() {
// TODO 自动生成的方法存根
for (int i = 0; i < 3; i++) {
// 当i=0时,让出cpu执行权,放弃时间片,进行下一轮调度
if (i == 0) {
System.out.println(Thread.currentThread() + " yield cpu...");
// 当前线程让出cpu执行权,放弃时间片,进行下一轮调度
Thread.yield();
}
}
System.out.println(Thread.currentThread() + " is over");
}
public static void main(String[] args) {
new TestYield();
new TestYield();
new TestYield();
}
}
运行结果:
Thread[Thread-1,5,main] yield cpu...
Thread[Thread-0,5,main] yield cpu...
Thread[Thread-2,5,main] yield cpu...
Thread[Thread-1,5,main] is over
Thread[Thread-2,5,main] is over
Thread[Thread-0,5,main] is over
线程中断
public class TestInterrupt {
/**
* void interrupt()方法: 中断线程,设置线程的中断标志位true并返回,如果线程处于wait,join或者sleep等方法而被阻塞时,
* 被调用interrupt()方法会在这些方法的狄梵抛出InterruptedException异常而返回
*
* boolean isInterruptred()方法: 检测当前线程是否被中断,如果是则返回true
*
* boolean interrupted()方法: 检测当前线程是否被中断,如果是则返回true,如果发现线程被中断则会清楚中断标志,该方法为static
* 方法
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
// 根据中断标志判断线程是否被终止
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// TODO 自动生成的方法存根
// 如果当前线程被中断则退出循环
while (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread() + " hello");
}
}
});
//启动子线程
thread.start();
//主线程休眠1s,以便中断前让子线程输出
Thread.sleep(1000);
//中断子线程
System.out.println("main thread interrupt thread");
thread.interrupt();
//等待子线程执行完毕
thread.join();
System.out.println("main is over");
}
}
isInterruptred()与interrupted()方法可以自行测试。
线程死锁
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,
在无外力作用的情况下,这些线程会一直相互等待而无法继续运行下去
死锁的产生必须具备以下四个条件:
- 互斥条件 :指线程对已经获取到的资源进行排它性使用,即该资源同时只由一个线程占用。 如果此时还有其他线程请求获取该资源,则请求者只能等待,直至占有资源的线程释放该资源。
- 请求并持有条件:指一个线程已经持有了至少一个资源,但又提出了新的资源请求, 而新资源已被其他线程占有,所以当前线程会被阻塞,但阻塞的同时并不释放自己已经获取的资源。
- 不可剥夺条件 :指线程获取到的资源在自己使用完之前不能被其他线程抢占,只有在自己使用完毕后才由自己释放该资源。
- 环路等待条件 :指在发生死锁时,必然存在一个线程—资源的环形链,即线程集合 {T0,T1,T2,…,Tn} 中的 T0 正在等待一个 T1 占用的资源,T1 正在等待 T2 占用的资源,……Tn 正在等待已被 T0 占用的资源。
想避免死锁,只需要破坏掉至少一条造成死锁的必要条件即可。
看一个死锁的例子
package com.thread.deadLock;
public class TestDeadLock {
// 创建资源
private static Object resourceA = new Object();
private static Object resourceB = new Object();
public static void main(String[] args) {
// 创建线程A
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println(Thread.currentThread() + " get resourceA");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + " waiting get resourceB");
synchronized (resourceB) {
System.out.println(Thread.currentThread() + " get resourceB");
}
}
}
});
// 创建线程B
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceB) {
System.out.println(Thread.currentThread() + " get resourceB");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + " waiting get resourceA");
synchronized (resourceA) {
System.out.println(Thread.currentThread() + " get resourceA");
}
}
}
});
// 启动线程
threadA.start();
threadB.start();
}
}
只需要让线程B对资源的请求顺序与线程A一致即可以避免死锁。