掌握之并发编程-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<String> { @Override public String call() throws Exception { return null; } public static void main(String[] args) { MyCallable m = new MyCallable(); FutureTask<String> 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
方法。
JAVA中将 就绪(READY)和 运行中(RUNNING)两种状态统称为“运行”状态。
就绪 READY:就是说线程有资格运行,但此时调度程序还未选择线程。当以下行为发生时,线程进入就绪状态。
调用线程的start
方法当前线程的sleep
结束其他线程join
结束等待用户输入,但用户输入完毕线程拿到对象锁当前线程的时间片用完了调用当前线程的yield
方法运行中 RUNNING:调度程序从就绪的线程池中选择一个线程使其成为当前线程,此时线程处于的状态就是运行中。
阻塞 BLOCKED阻塞状态是线程在获取对象的同步锁synchorized
时,因为该锁被其他线程占用而放弃CPU使用权,暂时停止运行的状态。此时的线程会被JVM放入锁池中。
运行的线程执行wait()
方法,会释放线程占用的所有资源,并进入等待池中。此时,线程是不能自动唤醒的,必须依靠其他线程调用notify()
或notifyAll()
方法才能唤醒。
运行的线程执行sleep()
或join()
方法,或者发出I/O请求时的状态。此时线程会放弃CPU使用权。当sleep()
超时、join()
等待线程终止或超时、I/O处理完毕时,重新转入就绪。
线程执行完成或因异常而退出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使用权,回到就绪状态。
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。