【并发编程之美】线程简介与线程的简单操作
声明:博客全部参考《JAVA并发编程之美》
什么是线程
线程是进程中的一个实体,本身不独立存在。它是代码再数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。
点击[JAVA运行时数据区域][JAVA],查看java的内存分配。
线程的创建与执行
线程的创建有三种方式,其实可以分为两种类型:
- 没有返回值的:
1.1 继承Thread类并重写run的方法
这个方法的好处是,再run()方法内获取当前线程直接使用this就可以了,无需使用Thread.currentThread()方法
不好的地方是,继承了这个类之后,就不能再去继承别的类了
1.2 实现Runnable接口的run方法
没有继承Thread类的限制,这样就可以实现多个接口 有返回值的:使用FutureTask方法
使用FutureTask方法,再main函数内首先创建了一个FutrueTask对象,然后使用创建的FutrueTask对象作为任务创建了一个线程并且启动她,最后通过futureTask.get()等待任务执行完毕并返回结果package com.banana.chapter01.demo1_2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
/ 第一种 实现Runnable接口的run方法 // 创建线程 MyThread thread = new MyThread(); // 启动线程 thread.run(); /
/ 第二种 继承Thread类并重写run的方法 RunableTask task = new RunableTask(); new Thread(task).start(); new Thread(task).start(); /
/* 第三种 使用FutureTask方法 //创建异步任务 FutureTask<String> futureTask=new FutureTask<>(new CallerTask()); //启动线程 new Thread(futureTask).start(); try { //等待任务执行完毕,并且返回结果 String result = futureTask.get(); System.out.println(result); }catch (ExecutionException e){ e.printStackTrace(); } */
}
/* 第一种 public static class MyThread extends Thread { @Override public void run() { System.out.println("I am a child thread"); } } */
/* 第二种 public static class RunableTask extends Thread { @Override public void run() { System.out.println("I am a child thread"); } } */
/* 第三种 public static class CallerTask implements Callable<String> { @Override public String call() throws Exception { return "hello"; } } */
}
线程的通知和等待
这里就要讲两个方法,一个是wait(),另一个是notify()
wait()
调用wait方法时,该调用线程会被阻塞挂起,直到发生以下两个情况才会返回:
- 其他线程调用了该共享对象的notify()或者notifyAll()方法;
- 其他线程调用了该线程的interrupt()方法,该线程抛出InterrupteException异常返回。
wait(long timeout)
加了一个超时参数,默认的wait()其实就是调用的wait(0),如果传入了负数则就会抛出IllegalArgumentException异常。
wait(long timeout, int nanos)
内部调用的是wait(long timeout),会根据nanos的值再去看wait方法如何执行。
notify()
调用notify方法后,会唤醒一个在该共享变量上调用wait系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待线程是随机的。
notifyAll()
notifyAll()会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程。
等待线程执行终止的join方法
join方法实现的就是等待线程执行完毕再向下执行,有的时候多个线程加载资源,需要等待全部完成后再汇总处理就会用得到
package com.banana.chapter01.demo1_4;
public class ThreadJoinTest {
public static void main(String[] args) throws InterruptedException {
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("child threadOne over!");
}
});
Thread threadTwo = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("child threadTwo over!");
}
});
//启动子线程
threadOne.start();
threadTwo.start();
System.out.println("wait all child thread over!");
//等待子线程执行完毕,返回
threadOne.join();
threadTwo.join();
System.out.println("all child thread over!");
}
}
打印结果
wait all child thread over!
child threadOne over!
child threadTwo over!
all child thread over!
让线程睡眠的sleep方法
sleep方法会暂时让出指定时间的执行权,也就是在这期间不参与CPU的调度,但是该线程所拥有的资源监视器资源,比如锁还是持有不让出的。
package com.banana.chapter01.demo1_5;
public class ThreadJoinTest {
public static void main(String[] args) throws InterruptedException {
//创建线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try{
System.out.println("child thread is in sleep");
Thread.sleep(10000);
System.out.println("child thread is in awaked");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//启动线程
thread.start();
//主线程休眠2s
Thread.sleep(2000);
//主线程中断子线程
thread.interrupt();
//因为现在子线程处于休眠状态,所以此时中断子线程就会报错
}
}
打印结果
child thread is in sleep
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.banana.chapter01.demo1_5.ThreadJoinTest$1.run(ThreadJoinTest.java:13)
at java.lang.Thread.run(Thread.java:745)
因为此时子线程处于休眠状态,所以在这个时候执行中断操作就会报错。
让出CPU执行权的yield方法
当一个线程执行yield方法的时候,当前线程就会让出自己的CPU使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,也有可能就会调度到刚刚让出CPU的那个线程来获取CPU执行权。
package com.banana.chapter01.demo1_6;
public class YieldTest implements Runnable {
public static void main(String[] args) {
new YieldTest();
new YieldTest();
new YieldTest();
}
YieldTest() {
//创建并启动线程
Thread t = new Thread(this);
t.start();
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
//当i=0的时候让出CPU执行权,放弃时间片,进行下一轮调度
if ((i % 5) == 0) {
System.out.println(Thread.currentThread() + "yield cpu...");
//当前线程让出CPU执行权,放弃时间片,进行下一轮调度
Thread.yield();
}
}
System.out.println(Thread.currentThread()+ " is over");
}
}
打印结果
Thread[Thread-1,5,main]yield cpu...
Thread[Thread-2,5,main]yield cpu...
Thread[Thread-1,5,main] is over
Thread[Thread-0,5,main]yield cpu...
Thread[Thread-2,5,main] is over
Thread[Thread-0,5,main] is over
sleep和yield的方法区别
当线程调用sleep方法时调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程。而调用yield方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态,线程调度器下一次调度时就可能调度到当前线程执行。
线程中断
线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。
void interrupt()
中断线程的方法。
boolean isInterrupted()
检测当前线程是否被中断,如果是返回true,否则返回false。
boolean interrupted()
检测当前线程是否被中断,如果是返回true,否则返回false。和isInterrupted()方法不同的事,这个方法如果发现线程被中断的话,就会把中断标志清楚,并且这是一个static方法。
根据中断标志判断线程是否终止
package com.banana.chapter01.demo1_7;
public class ThreadTest {
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();
//主线程休眠1s,以便中断前让子进程输出
Thread.sleep(1000);
//中断子线程
System.out.println("main thread interrupt thread");
thread.interrupt();
//等待子线程执行完毕
thread.join();
System.out.println("main is over");
}
}
执行结果
Thread[Thread-0,5,main] hello
Thread[Thread-0,5,main] hello
…
Thread[Thread-0,5,main] hello
Thread[Thread-0,5,main] hello
main thread interrupt thread
Thread[Thread-0,5,main] hello
main is over
由于线程在运行中被中断,然后判断了线程是否被中断,接着退出了子线程,主线程结束。
interrupted()和isInterrupted()的不同
package com.banana.chapter01.demo1_7;
public class ThreadTest3 {
public static void main(String[] args) throws InterruptedException {
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
for (; ; ) {
}
}
});
threadOne.start();
threadOne.interrupt();
//获取中断标志
System.out.println("isInterrupt: " + threadOne.isInterrupted());
//获取中断标志并重置
System.out.println("isInterrupt: " + threadOne.interrupted());//这里获取的是主线程的interrupt状态
//获取中断标志并重置
System.out.println("isInterrupt: " + Thread.interrupted());
//获取中断标志
System.out.println("isInterrupt: " + threadOne.isInterrupted());
threadOne.join();
System.out.println("main thread is over");
}
}
打印结果
isInterrupt: true
isInterrupt: false
isInterrupt: false
isInterrupt: true
第一个:true,线程被中断,中断标志为true
第二个:false,因为是获取的当前线程,当前线程是主线程,所以虽然是调用的threadOne的方法,但实际上依旧是获取的当前线程,当前线程没有被中断,所以此处是false
第三个:false,此处是主线程,和第二个一样
第四个:true,获取线程是否被中断,可以得知线程是被中断了的
最后没有输出main thread is over,是因为当前的进程一直处于运行状态,所以没有继续向下运行
线程上下文切换
在多线程编程中,线程个数一般都大于CPU个数,而每个CPU同一时刻只能被一个线程使用,为了让用户感觉多个线程是在同时执行的,CPU资源的分配采用了时间片轮转的策略,也就是给每个线程分配一个时间片,线程在时间片内占用CPU执行任务。当前线程使用完时间片后,就会处于就绪状态并让出CPU让其他线程占用,这就是上下文切换,从当前线程的上下文切换到了其他线程。那么就有一个问题,让出CPU的线程等下次轮到自己占有CPU时如何知道自己之前运行到哪里了?所以在切换线程上下文时需要保存当前线程的执行现场,当再次执行时根据保存的执行现场信息恢复执行现场。
线程上下文切换时机有:当前线程的CPU时间片使用完处于就绪状态时,当前线程被其他线程中断时。
线程死锁
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,在无外力作用的情况下,这些线程会一直相互等待而无法继续运行下去。
线程A持有了资源2,线程B持有了资源1,线程A请求资源1,线程B请求资源2,他们都不释放自己所拥有的,所以就造成了死锁。
产生死锁的四个条件:
- 互斥条件
- 请求并持有条件
- 不可剥夺条件
环路等待条件
package com.banana.chapter01.demo1_9;
public class DeadLockTest01 {
//创建资源
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();
}
}
运行结果
Thread[Thread-1,5,main]get ResourceB
Thread[Thread-0,5,main]get ResourceA
Thread[Thread-0,5,main]waiting get ResourceB
Thread[Thread-1,5,main]waiting get ResourceA
此时这个进程还处于运行状态,所以可以体现出目前线程是处于一个死锁的状态,不能向下运行。
守护线程和用户线程
什么是守护线程
Java中的线程分为两类,分别为daemon线程(守护线程)和user线程(用户线程)。在JVM启动时会调用main函数,main函数所在的线程就是一个用户线程,其实在JVM内部同时还启动了好多守护线程,比如垃圾回收线程。那么守护线程和用户线程有什么区别呢?区别之一是当最后一个非守护线程结束时,JVM会正常退出,而不管当前是否有守护线程,也就是说守护线程是否结束并不影响IVM的退出。言外之意,只要有一个用户线程还没结束,正常情况下JVM就不会退出。
守护线程的创建
和普通的线程创建一样,加一句thread.setDaemon(true)即设置线程为守护线程。
package com.banana.chapter01.demo1_10;
public class ThreadTest {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (; ; ) {
}
}
});
thread.setDaemon(true);
//启动子线程
thread.start();
System.out.println("main thread is over");
}
}
运行结果
main thread is over
设置了线程为守护线程之后,主线程运行不会管这个守护线程是否正在运行,运行完会直接退出。
总结:如果你希望主线程结束后JVM进程马上结束,那么在创建线程时可以将其设置为守护线程,如果你希望在主线车工结束后子线程继续工作,等子线程结束后再让JVM进程结束,那么就将子线程设置为用户线程。
还没有评论,来说两句吧...