线程创建与运行
- 线程基础
-
- 继承Thread类
- 实现Runnable接口
- FutureTask
- 线程通知和等待
-
- wait()
- wait(long timeout)函数
- notify()函数
- notifyAll()
- join()
- sleep()
- 线程中断
-
-
- void interrupt()方法
- boolean isInterrupted()
- boolean interrupted()
-
线程基础
继承Thread类
public static class MyThread extends Thread{
@Override
public void run() {
System.out.println("i am a student");
}
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
注意,当创建完thread对象之后线程并没有启动,直到调用了start才是真正启动了线程。
其实调用start之后线程也没有立即执行,而是处于就绪状态,等待获取CPU资源之后才会开始。一旦run方法执行完毕,线程终止。
但是使用这种方法有弊端,一是java不支持多继承,继承了Thread类就不能再继承别的类;二是任务和代码没有分离,如果多个线程需要执行相同的任务,就需要同样的代码写好几份。
实现Runnable接口
public static class RunnableTask implements Runnable{
@Override
public void run() {
System.out.println("i am a student");
}
}
public static void main(String[] args) {
RunnableTask task = new RunnableTask();
new Thread(task).start();
new Thread(task).start();
}
如上所示,两个线程共用一个task代码逻辑,另外,RunnableTask还可以继承别的类。
FutureTask
以上两种方法都没有返回值,如果需要返回值,需要用到以下这种方法
public static class CallerTask implements Callable<String>{
@Override
public String call() throws Exception {
return "student";
}
}
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask(new CallerTask());
new Thread(futureTask).start();
try {
String res = futureTask.get();
System.out.println(res);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
以上代码中,的CallerTask类实现了Callable接口的call()方法。首先在main函数中创建FutureTask对象,然后使用创建的FutureTask对象作为任务创建一个线程并启动它,最后可以通过futureTask.get()获取返回值。
线程通知和等待
wait()
当一个线程调用一个共享变量的wait()方法时,该调用线程就会被阻塞直到① 其他线程调用了该共享对象的notify()、notifyAll()函数 ②其他线程调用该线程的interrupt()函数,该线程会排除InterruptedException返回。
如果要调用wait()方法,必须先获取该对象的监视器锁。获取监视器锁有两种方法。
1.执行synchronized同步代码块
synchronized(共享变量){}
2.调用该共享变量的synchronized修饰的方法
synchronized void add(int a,int b){}
虚假唤醒:一个线程没有被其他线程notify唤醒或者被中断但变成了运行状态。为了防止虚假唤醒,可以不停的去测试线程被唤醒的条件是否满足
synchronized(obj){
while(条件不满足){
obj.wait();
}
}
例子
public static class RunnableTask implements Runnable{
@Override
public void run() {
try {
synchronized (A){
System.out.println("A get A lock");
synchronized (B){
System.out.println("A got B lock");
System.out.println("A release A lock");
A.wait();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static class RunnableTask2 implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
synchronized (A){
System.out.println("B get A lock");
System.out.println("B try to get B lock");
synchronized (B){
System.out.println("B got B lock");
System.out.println("B release B lock");
A.wait();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static class CallerTask implements Callable<String>{
@Override
public String call() throws Exception {
return "student";
}
}
private static volatile Object A = new Object();
private static volatile Object B = new Object();
public static void main(String[] args) {
RunnableTask runnableTask = new RunnableTask();
RunnableTask2 runnableTask2 = new RunnableTask2();
new Thread(runnableTask).start();
new Thread(runnableTask2).start();
}
在main函数中启动了线程A和B,为了让A先获取到锁,让B睡了1s。A先获取到A和B锁,然后调用wait()方法阻塞自己,然后释放A锁。
B在结束休眠后先尝试获取A锁,如A还没有释放则B会阻塞。获取之后获取B锁,因为A一直没有释放,所以B获取不到。
这个例子可以证明当线程调用共享对象的wait()方法时,当前线程只会释放当前共享对象的锁,当前线程持有的其他共享对象的锁不会释放。
wait(long timeout)函数
设置超时参数,如果在指定时间内没有该共享变量没有被notify,那该函数还是会因为超时而返回。
notify()函数
一个线程调用共享对象notify()方法之后,会唤醒一个在该共享变量上调用wait系列方法而被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个线程是随机的。
但是,被唤醒的线程也不是立刻从wait方法返回并执行,而是需要先获取对共享对象的监视器锁。因为还会有其他线程和这个刚被唤醒的线程一起争夺监视器锁
public static class RunnableTask implements Runnable{
@Override
public void run() {
synchronized (A){
System.out.println("A get A lock");
try {
System.out.println("A begin wait");
A.wait();
System.out.println("A end wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static class RunnableTask2 implements Runnable{
@Override
public void run() {
synchronized (A){
System.out.println("B get A lock");
try {
System.out.println("B begin wait");
A.wait();
System.out.println("B end wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static class RunnableTask3 implements Runnable{
@Override
public void run() {
synchronized (A){
System.out.println("C get A lock");
System.out.println("C begin notify");
A.notify();
System.out.println("C end notify");
}
}
}
public static class CallerTask implements Callable<String>{
@Override
public String call() throws Exception {
return "student";
}
}
private static volatile Object A = new Object();
private static volatile Object B = new Object();
public static void main(String[] args) throws InterruptedException {
RunnableTask runnableTask = new RunnableTask();
RunnableTask2 runnableTask2 = new RunnableTask2();
RunnableTask3 runnableTask3 = new RunnableTask3();
new Thread(runnableTask).start();
new Thread(runnableTask2).start();
Thread.sleep(1000);
new Thread(runnableTask3).start();
}
B get A lock
B begin wait
A get A lock
A begin wait
C get A lock
C begin notify
C end notify
B end wait
可以看到,线程c在notify之后唤醒了线程B,而A没有被唤醒
notifyAll()
如果把c线程中的notify改成notifyAll,那么AB线程都被唤醒,AB会争抢锁,先后执行。
join()
join这个方法的主要作用就是当前线程等待子线程运行结束。
sleep()
当一个线程调用了sleep方法后,调用线程会暂时让出指定时间的执行权,也就是这期间不参与CPU调度,但该线程所持有的资源,如监视器锁不会让出。指定时间到了之后该函数会返回,线程处于就绪状态参与CPU调度。
线程中断
java的线程中断是一种线程间的写作模式,通过设置线程的中断标志并不能直接终止现成的运行,被中断的线程会根据中断状态自行处理。
就好比老师要求你写assignment,但是最后要不要写还是你自己决定。
void interrupt()方法
中断线程,当线程A运行时,线程B可以调用线程A的interrupt()方法设置A的中断标志为true并立即返回。但是设置标志位仅仅是设置了标志,线程A并没有被中断,他还会继续往下执行。如果线程A调用了wait系列的函数,join方法或sleep方法而被挂起,这时候若线程B调用线程A的interrupt方法,则会抛出InterruptedException而返回。
此函数是唯一一个可以更改标志位的函数。
boolean isInterrupted()
用来检测当前线程是否被中断,如果是就返回true,否则为false
boolean interrupted()
和isInterrupted方法不同的是,如果该方法发现线程被中断,不仅会返回true,还会清除中断标志。
下面是一个使用中断标志位判断线程是否终止的例子。
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread() + "hello");
}
}
});
thread.start();
Thread.sleep(100);
System.out.println("main thread interrupt thread");
thread.interrupt();
thread.join();
System.out.println("main over");
}
以知使用interrupt函数会让在sleep、wait等挂起状态的线程抛出异常并强制返回,节省时间,以下是例子
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("begin sleep");
Thread.sleep(20000);
System.out.println("awake");
} catch (InterruptedException e) {
System.out.println("interrupted");
return;
}
System.out.println("normally");
}
});
thread.start();
Thread.sleep(100);
thread.interrupt();
thread.join();
System.out.println("main over");
}