内容简介:下面我们来学习一下如何创建线程。注意是调用 start 方法,而不是直接调用 run 方法。
- New:线程刚刚创建,还未加入线程调度
- Runnable:就绪态,调用 start() 后,线程加入线程调度。此时,只要获取到 CPU 时间片,就会进入运行态
- Running:运行态,线程获取到 CPU 时间片后,就会被 CPU 运行。可以通过 yield() 主动让出时间片,会使得线程返回就绪态
- Blocked:阻塞态,此时线程需要等待释放信号才能进入就绪态,如等待用户输入、等待锁被解除
- Dead:线程结束或抛出未捕获异常
下面我们来学习一下如何创建线程。
1. 创建线程的两种方法
extends Thread implements Runnable
1.1 继承 Thread 类
public class MyThread extends thread { @Override public void run() { // ... } }
MyThread thread = new MyThread(); thread.start();
注意是调用 start 方法,而不是直接调用 run 方法。
调用 start 方法会进入 runnable 就绪态。当分配到 CPU 时间片时,run() 方法就会执行,从而进入 Running 态。
1.2 实现 Runnable 接口
public class MyThread implements Runnable { @Override public void run() { System.out.println("hello..."); } }
Thread thread = new Thread(new MyThread()); thread.start();
Lambda 写法:
Thread thread = new Thread(() -> { System.out.println("你好"); }); thread.start();
2. 线程内部概念
2.1 常用 API
// 获取当前线程 Thread thread = Thread.currentThread(); // 获取线程 ID long id = thread.getId(); // 获取线程 Name String name = thread.getName(); // 获取线程优先级 int priority = thread.getPriority(); // 判定线程是否为守护线程 boolean isDaemon = thread.isDaemon(); // 判定线程是否被中断;被中断会进入阻塞 Blocked 状态 boolean isInterrupted = thread.isInterrupted(); // 判定线程是否存活 boolean isAlive = thread.isAlive();
线程优先级
Thread 中定义了三个优先级常量:
public final static int MIN_PRIORITY = 1; public final static int NORM_PRIORITY = 5; public final static int MAX_PRIORITY = 10;
所以线程一共有 10 个优先级,最低为 1,最高为 10,默认为 5
2.2 守护线程
private boolean daemon = false; public final boolean isDaemon() { return daemon; } public final void setDaemon(boolean on) { checkAccess(); if (isAlive()) { throw new IllegalThreadStateException(); } daemon = on; }
守护线程的含义是:该线程是一个后台线程,依赖于其他线程,当其他线程都结束之后,该线程也会自动结束。当正在运行的都是守护线程的时候,Java 虚拟机会关闭。
example:
public static void main(String[] args) { Thread front = new Thread(() -> { System.out.println("前台线程开始运行..."); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("前台线程运行结束"); }); Thread back = new Thread(() -> { while (true) { System.out.println("后台线程开始运行,当前台线程 Dead,我就自杀!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); front.start(); back.setDaemon(true); back.start(); }
result:
前台线程开始运行... 后台线程开始运行,当前台线程 Dead,我就自杀! 后台线程开始运行,当前台线程 Dead,我就自杀! 后台线程开始运行,当前台线程 Dead,我就自杀! 前台线程运行结束
2.3 线程状态转换、线程交互
在上面的状态图里,我们已经看到了部分线程状态转换、线程交互的方法。这里详细总结一下这部分的知识。
2.3.1 sleep
Thread 提供了 sleep 静态方法,可以使线程进入阻塞态指定时间,单位是毫秒。超时后,线程自动进入就绪态,等待再次分配 CPU 时间片进入运行态。
源码:
public static native void sleep(long millis) throws InterruptedException;
Example:
Thread.sleep(1000); // 睡眠 1s
这个方法非常常见,是 Thread 中使用最多的 API 之一。
2.3.2 yield
Thread 提供了 yield 静态方法,可以使当前线程主动让出时间片,进入就绪态,等待再次分配时间片。
public static native void yield();
2.3.3 join
等待该线程死亡,即在 B 线程中调用了 A.join(),那么 B 线程将在此处等待 A 线程死亡才会继续往下执行。
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; } } }
注意这里调用了 Object 类的 wait 方法。
Example:
public static void main(String[] args) { Thread front = new Thread(() -> { System.out.println("开始下载文件..."); for (int i = 0; i < 100; i++) { System.out.println("下载进度" + i + "%"); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("下载结束,是否打开文件?"); }); Thread back = new Thread(() -> { System.out.println("等待文件下载完毕..."); try { front.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("是,文件打开如下..."); }); front.start(); back.start(); }
Result:
开始下载文件... 等待文件下载完毕... 下载进度0% 下载进度1% // ... 下载进度99% 下载结束,是否打开文件? 是,文件打开如下...
2.3.4 Object 的 wait 和 notify
在 Thread 的 join 方法中使用了 wait 方法,wait 和 notify 是 Object 提供的线程之间协调工作的方法。
public final void wait() throws InterruptedException { wait(0); } // timeout 表示最长等待多长时间,如果 = 0,则表示一直等待,直到 notify 被触发! public final native void wait(long timeout) throws InterruptedException; public final native void notify(); public final native void notifyAll();
使用某个对象的 wait 方法,使得当前线程进入阻塞态,直到该对象的 notify 方法被调用,该线程进入就绪态。如果该对象的 wait 方法被多个线程调用,那么 notify 方法会随机释放一个线程。如果想要释放所有线程,使用 notifyAll 方法。
注意,使用 wait 和 notify 方法时,必须对该对象加锁。
Example:
public static void main(String[] args) { Integer obj = 1; Thread front = new Thread(() -> { System.out.println("线程1开始下载文件..."); for (int i = 1; i <= 10; i++) { System.out.println("下载进度" + (i*10) + "%"); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("下载结束,通知线程2打开文件"); synchronized (obj) { obj.notify(); } }); Thread back = new Thread(() -> { System.out.println("线程2启动,等待线程1通知文件下载完成..."); synchronized (obj) { try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("接受到线程1通知,打开文件"); }); front.start(); back.start(); }
Result:
线程1开始下载文件... 线程2启动,等待线程1通知文件下载完成... 下载进度10% 下载进度20% 下载进度30% 下载进度40% 下载进度50% 下载进度60% 下载进度70% 下载进度80% 下载进度90% 下载进度100% 下载结束,通知线程2打开文件 接受到线程1通知,打开文件
3. 多线程并发问题
在多线程环境下,经常会出现并发问题,主要体现在抢占临界资源问题上。
比如同时调用某个对象。
解决办法通常是使用支持并发的对象(即对象内的资源被加锁了),比如 ConcurrentHashMap,或者直接对调用该对象的代码块加锁。
最简单的加锁方式就是 synchronized,如:
synchronized (user) { user.setName("hello"); }
4. 线程池
创建和销毁线程通常会消耗一定的资源,如果在线程非常多的情况下,这种资源消耗就不可小视了。这时候我们引入了线程池的概念——通俗理解,管理线程的容器。
这里用一个简单的例子演示一下线程池的使用:
- 创建 ExecutorService 和提交 Runnable
ExecutorService pool = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { Runnable runnable = () -> { Thread currentThread = Thread.currentThread(); System.out.println(currentThread.getName() + " 执行任务"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(currentThread.getName() + " 完毕"); }; pool.execute(runnable); } pool.shutdown();
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Scratch少儿趣味编程
[ 日] 阿部和广 / 陶 旭 / 人民邮电出版社 / 2014-11 / 59.00元
Scratch 是麻省理工学院设计开发的一款编程工具,是适合少儿学习编程和交流的工具和平台,有中文版且完全免费。本书结合孩子们学习的语文、数学、科学、社会、音乐、体育等科目,手把手地教大家如何用Scratch 设计程序(如设计一个自动写作文的程序),配合各式卡通形象,通俗易懂,寓教于乐。麻省理工学院教授米切尔•瑞斯尼克作序推荐。 本书图文并茂,生动风趣,适合中小学生等初学者自学或在家长的帮助......一起来看看 《Scratch少儿趣味编程》 这本书的介绍吧!
Base64 编码/解码
Base64 编码/解码
HEX CMYK 转换工具
HEX CMYK 互转工具