掌握之并发编程-2.线程
掌握高并发、高可用架构
第二课 并发编程
从本课开始学习并发编程的内容。主要介绍并发编程的基础知识、锁、内存模型、线程池、各种并发容器的使用。
创新互联公司总部坐落于成都市区,致力网站建设服务有做网站、成都网站设计、网络营销策划、网页设计、网站维护、公众号搭建、微信小程序定制开发、软件开发等为企业提供一整套的信息化建设解决方案。创造真正意义上的网站建设,为互联网品牌在互动行销领域创造价值而不懈努力!
第二节 线程
并发编程
并发基础
进程
线程
线程通信
上一节学习了进程和线程的关系,CPU和线程的关系。在程序开发过程中,最主要的还是线程,毕竟它是用来执行任务的。所以就需要知道,如何启动和停止线程;线程的状态;线程间如何通信。
线程的启动
- 实现
Runnable
接口,然后当成Thread
的构造参数生成线程对象,调用t.start()
方法
public class MyThread implements Runnable {
@Override
public void run() {
System.out.println("thread02");
}
public static void main(String[] args) {
Thread t = new Thread(new MyThread());
t.start();
}
}
这是线程最本质的实现。Thread
类实现了Runnable
接口,在执行t.start()
时,会调用Thread
的run()
方法,从而间接调用target.run()
。
Thread类实现Runnalbe接口:
public class Thread implements Runnable {
private Runnable target;
public void run() {
if (target != null) {
target.run();
}
}
}
2 继承Thread类,然后调用start()
方法
public class MyThread extends Thread {
public void run() {
System.out.println("thread01");
}
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
由于Thread
实现了Runnable
,所以继承Thread
来重写run()
方法的本质依然是实现Runnable
接口的定义。此时,由于target
对象为null
,所以Thread
的run()
方法不会执行target.run()
,而是直接执行自定义的run()
方法。
3 实现Callable
接口,并通过FutureTask
public class MyCallable implements Callable {
@Override
public String call() throws Exception {
return null;
}
public static void main(String[] args) {
MyCallable m = new MyCallable();
FutureTask f = new FutureTask<>(m);
Thread t = new Thread(f);
t.start();
String result = f.get(); // 同步获取任务执行结果
System.out.println(result);
}
}
由于FutureTask
实现了RunnableTask
接口,而RunnableTask
又实现了Runnable
和Future
接口,因此在构造Thread
时,FutureTask
还是被转型为Runnable
来使用了。
前两种方法只能执行任务,而不能得到任务的结果;第三种方法可以通过FutureTask
的get()
方法同步的获取任务结果。当任务执行中时,其会阻塞直到任务完成。
4 匿名内部类
public class DemoThread {
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
//...
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
//...
}
}).start();
}
}
5 Lambda表达式
public class Demo {
public static void main(String[] args) {
new Thread(() -> System.out.println("running")).start();
}
}
6 线程池
public class MyThreadPool implements Runnable {
@Override
public void run() {
// TODO
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
MyThreadPool m = new MyThreadPool();
exec.execute(m);
}
}
把任务的执行交给ExecutorService
去处理,最终还是利用Thread
创建线程。优点是线程的复用,省去了每个线程的创建和销毁过程,从而提高效率。
7 定时器
public class MyTimer {
public static void main(String[] args) {
Timer t = new Timer();
t.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
// TODO
}
}, 2000, 1000);
}
}
TimerTask实现了Runnable
接口,Timer
内部有个TimerThread
继承了Thread
,所以还是Thread
+Runnable
。
线程的停止
- 当线程的
run
方法执行完成后,线程自动释放资源进而终止。 - 在另外的线程中调用
interrupt
来中断某个线程。这是线程间通信,我们后续再讲
线程的状态
先上图(借用CSDN博主 潘建南 的图)。
所以,线程的状态一共有6种。下面咱们来详细讲解。
- 初始状态 NEW
通过实现Runnable
或继承Thread
得到一个线程类,并使用new
创建出一个线程对象,就进入了初始状态。此时,还未调用start
方法。
- 运行状态 RUNNABLE
JAVA中将 就绪(READY)和 运行中(RUNNING)两种状态统称为“运行”状态。
就绪 READY:就是说线程有资格运行,但此时调度程序还未选择线程。当以下行为发生时,线程进入就绪状态。
- 调用线程的
start
方法 - 当前线程的
sleep
结束 - 其他线程
join
结束 - 等待用户输入,但用户输入完毕
- 线程拿到对象锁
- 当前线程的时间片用完了
- 调用当前线程的
yield
方法
运行中 RUNNING:调度程序从就绪的线程池中选择一个线程使其成为当前线程,此时线程处于的状态就是运行中。
- 阻塞 BLOCKED
阻塞状态是线程在获取对象的同步锁synchorized
时,因为该锁被其他线程占用而放弃CPU使用权,暂时停止运行的状态。此时的线程会被JVM放入锁池中。
- 等待 WAITING
运行的线程执行wait()
方法,会释放线程占用的所有资源,并进入等待池中。此时,线程是不能自动唤醒的,必须依靠其他线程调用notify()
或notifyAll()
方法才能唤醒。
- 超时等待 TIMED_WAITING
运行的线程执行sleep()
或join()
方法,或者发出I/O请求时的状态。此时线程会放弃CPU使用权。当sleep()
超时、join()
等待线程终止或超时、I/O处理完毕时,重新转入就绪。
- 终止 TERMINATED
线程执行完成或因异常而退出run
方法体的状态。
线程各个状态之间的跳转,可以仔细看图。
线程间通信
- 通过共享变量通信
在共享对象的变量中设置信号量。线程A修改信号量的值,线程B根据信号量来做不同的处理。
- 通过
wait()
、notify()
、notifyAll()
来通信
JAVA要求wait()
、notify()
、notifyAll()
必须在同步代码块中使用。就是说,必须要获得对象锁。所以wait()
、notify()
、notifyAll()
经常和sychronized
搭配使用。
执行了锁定对象的wait()
方法后,当前线程会释放获得的对象锁,进入锁定对象的等待池
在执行同步代码块的过程中,如果调用Thread.sleep()
或Thread.yield()
,当前线程只是放弃CPU,并不会释放对象锁
JOIN
作用:让 主线程 等待 子线程 执行完成再继续运行。
// 主线程
public class Father extends Thread {
public void run() {
Son son = new Son();
son.start();
son.join();
...
}
}
// 子线程
public class Son extends Thread {
public void run() {
...
}
}
在Father主线程中,先启动Son子线程,然后调用son.join()
,此时,Father主线程会一直等待,直到子线程执行完成,才能继续运行。
分析源码可以知道,JOIN的实现原理是:只要子线程是活动的,就一直触发主线程的wait()
方法,使其一直处于等待状态。
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
yield
调用yield()
方法,意思是放弃CPU使用权,回到就绪状态。
本文标题:掌握之并发编程-2.线程
文章网址:http://pwwzsj.com/article/psgpej.html