WatchDogs

Knowledge of Backend Development

0%

Java线程

线程的创建方式

常见的Java线程创建方式有四种

继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Thread1 {

public static void main(String[] args) {

Thread.currentThread().setName("主线程");
System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
//创建一个新线程
ThreadDemo1 thread1 = new ThreadDemo1();
//为线程设置名称
thread1.setName("线程一");
//开启线程
thread1.start();
}
}

class ThreadDemo1 extends Thread{

@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
}

}
实现Runnable接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Thread2 {

public static void main(String[] args) {
Thread.currentThread().setName("主线程");
System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
//创建一个新线程
Thread thread2 = new Thread(new ThreadDemo2());
//为线程设置名称
thread2.setName("线程二");
//开启线程
thread2.start();
}

}

class ThreadDemo2 implements Runnable {

@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
}

}
实现Callable接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Thread3 {

public static void main(String[] args) throws Exception {
Thread.currentThread().setName("主线程");
System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
//创建FutureTask的对象
FutureTask<String> task = new FutureTask<String>(new ThreadDemo3());
//创建Thread类的对象
Thread thread3 = new Thread(task);
thread3.setName("线程三");
//开启线程
thread3.start();
//获取call()方法的返回值,即线程运行结束后的返回值
String result = task.get();
System.out.println(result);

}

}

class ThreadDemo3 implements Callable<String> {

@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
return Thread.currentThread().getName()+":"+"返回的结果";
}
}
使用线程池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class Thread4 {

public static void main(String[] args) throws Exception {

Thread.currentThread().setName("主线程");
System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
//通过线程池工厂创建线程数量为2的线程池
ExecutorService service = Executors.newFixedThreadPool(2);
//执行线程,execute()适用于实现Runnable接口创建的线程
service.execute(new ThreadDemo4());
service.execute(new ThreadDemo6());
service.execute(new ThreadDemo7());
//submit()适用于实现Callable接口创建的线程
Future<String> task = service.submit(new ThreadDemo5());
//获取call()方法的返回值
String result = task.get();
System.out.println(result);
//关闭线程池
service.shutdown();
}
}
//实现Runnable接口
class ThreadDemo4 implements Runnable{

@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
}

}
//实现Callable接口
class ThreadDemo5 implements Callable<String>{

@Override
public String call() throws Exception {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
return Thread.currentThread().getName()+":"+"返回的结果";
}

}
//实现Runnable接口
class ThreadDemo6 implements Runnable{

@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
}

}
//实现Runnable接口
class ThreadDemo7 implements Runnable{

@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
}

}

线程的通知与等待

Object中的通知与等待系列函数

wait()函数

当一个线程调用一个共享变量 wait() 方法时, 该调用线程会被阻塞挂起,直到发生下面几件事情之一才返回

  • 线程调用了该共享对象 notify()或者 notifyAll() 方法
  • 其他线程调用了该线程 interrupt() 方法 该线程抛出InterruptedException 异常返回。

另外需要注意的是,如果调用 wait() 方法 线程没有事先获取该对象的监视器锁,则调用 wait() 方法时调用线程会抛出 IllegalMonitorStateException 异常。

wait(long timeout)函数

该方法相 wait()方法多了一个超时参数,它的不同之处在于,如果一个线程调用共享对象的该方法挂起后,没有在指定的 timeout ms 内被其它线程调用该共享变量的notify() 或者notifyAll() 方法唤醒,那么该函数还是会因为超时而返回。如果将 timeout置为0则和 wait 方法效果一样,因为在 wait 方法内部就是调用了 wait 需要注意的是,如果在调用该函数时,传递了一个负的 timeout 会抛出 IllegalArgumentException 异常

wait(long timeout, int nanos)函数

在其内部调用的是 wait(long timeout)函数,只有在 nanos>0 时才使参数timeout 递增1

notify()函数

一个线程调用共享对象的notify()方法后,会唤醒一个在该共享变量上调用wait系列方法后被挂起的线程。 一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的。
此外,被唤醒的线程不能马上从wait方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回,也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可继续执行。
类似wait系列方法,只有当前线程获取到了共享变量的监视器锁后,才可以调用共享变量的 notify()方法,否则会抛出 IllegalMonitorStateException异常。

notifyAll()函数

不同于在共享变量上调用 notify()函数会唤醒被阻塞到该共享变量上的一个线程,notifyAll()方法则会唤醒所有在该共享变量上由于调用 wait 系列方法而被挂起的线程。

Thread中的通知与等待系列函数

join()方法

等待线程执行终止.

1
2
3
4
5
6
7
8
9
......
//启动子线程
threadOne.start();
threadTwo.start();
System.out.println("wait all child thread over !”);
//等待子线程执行完毕,返回
threadOne.join();
threadTwo.join();
......

如上代码在主线程里面启动了两个子线程,然后分别调用了它 join()方法,那
么主线程首先会在调用 threadOne.join()方法后被阻塞threadOne执行完毕后返回threadOne执行完毕后threadOne.join()就会返回,然后主线程调用 threadTwo.join()方法后再次被阻塞,等待 threadTwo 执行完毕后返回。这里只是为了演示 join 方法的作用, 在这种情况下使用后面会讲到的CountDownLatch 是个不错的选择

另外,线程调用线程 join 方法后会被阻塞,当其它线程调用了线程A的interrupt()方法中断了线程A时,线程A会抛出InterruptedException 异常而返回。

sleep方法

让线程睡眠

当一个执行中的线程调用了Thread的sleep方法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与CPU的调度,但是该线程所拥有的监视器资源,比如锁还是持有不让出的。指定的睡眠时间到了后该函数会正常返回,线程就处于就绪状态,然后参与CPU的调度,获取到CPU资源后就可以继续运行了。如果在睡眠期间其线程调用了该线程的 intenrrupt()方法中断了该线程,则该
线程会在调用 sleep 方法的地方抛出 InterruptedException 异常而返回

yield方法

让出CPU执行权

当一个线程调用 yield 方法时,实际就是在暗示线程调度器当前线程请求让出自己CPU使用,但是线程调度器可以无条件忽略这个暗示。我们知道操作系统是为每个线程分配一个时间片来占有CPU的, 正常情况下当一个线程分配给自己的时间片使用完后,线程调度器才会进行下一轮的线程调度,而当一个线程调用了Thread类的静态方法yield时,是在告诉线程调度器自己占有的时间片中还没
有使用完的部分自己不想使用了,这暗示线程调度器现在就可以进行下一轮的线程调度。
当一个线程调用yield 方法时, 当前线程会让出CPU 使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出CPU的那个线程来获取CPU执行权。

线程中断

Java 中的线程中断是 种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。

  • void interrupt()方法: 中断线程 例如,当线程A运行时,线程B可以调用线程A的interrupt()方法来设置线程A的中断标志为true并立即返回。设置标志仅仅是设置标志,线程A实际并没有被中断,它会继续往下执行。如果线程A因为调用了wait系列函数、join方法或者sleep方法阻塞挂起,这时候若线程B调用线程A的interrupt()方法,线程A会在调用这些方法的地方抛出InterruptedException返回。

  • boolean isinterrupted()方法:检测当前线程是否被中断,如果是返回 true 否则返false

1
2
3
4
public boolean isInterrupted() { 
//传递false ,说明不清除中断标志
return isinterrupted (false)
}
  • boolean interrupted() 方法: 检测当前线程是否被中断,如果是返回true否则返回false。与 isInterrupted不同的是,该方法如果发现当前线程被中断, 会清除中断标志,并且该方法是static方法,通过Thread 类直接调用。另外从下面的代码可以知道,在interrupted()内部是获取当前调用线程的中断标志,而不是调用interrupted()方法的实例对象对象的中断标志。
1
2
3
4
public static boolean interrupted() { 
//清除 中断标志
return currentThread().isinterrupted(true);
}

守护线程与用户线程

Java 中的线程分为两类,分别为daemon线程(守护线程)和user线程(用户线程)。在JVM启动时会调用main函数,main函数所在的线程就是一个用户线程,其实在JVM内部同时还启动了好多守护线程,比如垃圾回收线程。那么守线程和用户线程有什么区别呢?区别之一是当最后一个非守护线程结束时,JVM 正常退出,而不管当前是否有守护线程,也就是说守护线程是否结束并不影响JVM退出。言外之意,只要有一个用户线程还没结束,正常情况下JVM就不会退出。

ThreadLocal

多线程访问同一个共享变量时特别容易出现并发问题,特别是在多个线程需要对
共享变量进行写入时。为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步。
同步的措施一般是加锁,这就需要使用者对锁有一定的了解,这显然加重了使用者的负担。那么有没有一种方式可以做到,当创建一个变量后,每个线程对其进行访问的时候访问的是自己线程的变量呢?其实 ThreadLocal就可以做这件事情,虽然 ThreadLocal并不是为了解决这个问题而出现的。