JAVA中的线程世界
进程与线程
- 进程是程序向操作系统申请资源(如内存空间和文件句柄)的基本单位.
- 线程是进程中可独立执行的最小单位.
- 在Java中创建一个线程就是创建一个Thread类的实例。
- 每个线程都有其要执行的任务.线程任务的处理逻辑是在Thread类的run方法中实现或进行调用的,因此run方法相当于线程任务逻辑处理的入口方法,它由java虚拟机在运行相应的线程时进行调用,而不是由应用代码进行调用。
- Thread类的start方法是用于启动相关线程的,启动一个线程的实质是请求java虚拟机运行相应的线程,而这个线程具体什么时候运行是由线程调度器决定的.因此,虽然start方法被执行了,但是并不意味着这个线程就开始运行了,它可能稍后运行,也可能永远不运行.
- Thread中常用的两个构造器是:Thread()和Thread(Runnable target),这两种创建线程的方式如下:
- 定义Thread类子类的方式创建线程
public class WelcomeApp {public static void main(String[] args) { // 创建线程 Thread welcomeThread = new WelcomeThread(); // 启动线程 welcomeThread.start(); // 输出“当前线程”的线程名称 System.out.printf("1.Welcome! I'm %s.%n", Thread.currentThread().getName()); } }// 定义Thread类的子类 class WelcomeThread extends Thread {// 在该方法中实现线程的任务处理逻辑 @Override public void run() { System.out.printf("2.Welcome! I'm %s.%n", Thread.currentThread().getName()); } }
- 实现Runnable接口的方式创建线程
public class WelcomeApp1 {public static void main(String[] args) { // 创建线程 Thread welcomeThread = new Thread(new WelcomeTask()); // 启动线程 welcomeThread.start(); // 输出“当前线程”的线程名称 System.out.printf("1.Welcome! I'm %s.%n", Thread.currentThread().getName()); }}class WelcomeTask implements Runnable { // 在该方法中实现线程的任务处理逻辑 @Override public void run() { // 输出“当前线程”的线程名称 System.out.printf("2.Welcome! I'm %s.%n", Thread.currentThread().getName()); }}
- 定义Thread类子类的方式创建线程
- 不管使用以上哪种方式运行,一旦线程的run方法执行结束,相应线程的运行也就结束了,运行结束的线程所占的资源会如同其它java对象一样被java虚拟机垃圾回收。
- Thread的实例只能start一次,若多次调用一个实例的
start()
会抛出IllegalThreadStateException
异常。 - 可以通过Thread.currentThread()获取当前线程,进而可以对其进行属性设置或获取它的相关信息,例如:
Thread.currentThread().setName("线程A"); Thread.currentThread().getName();
- 上述中,线程的run方法一般由java虚拟机调用,但是,线程也是一个Thread类的一个实例其次run方法也是由public修饰符修饰,所以run方法也能被直接调用,但是一般不会这么做,违背我们床架线程的初衷。
public class WelcomeApp {public static void main(String[] args) { // 创建线程 Thread welcomeThread = new WelcomeThread(); // 启动线程 welcomeThread.start(); // 输出“当前线程”的线程名称 System.out.printf("1.Welcome! I'm %s.%n", Thread.currentThread().getName()); welcomeThread.run(); } }// 定义Thread类的子类 class WelcomeThread extends Thread {// 在该方法中实现线程的任务处理逻辑 @Override public void run() { System.out.printf("2.Welcome! I'm %s.%n", Thread.currentThread().getName()); } } ==================结果================== 1.Welcome! I'm main. 2.Welcome! I'm Thread-0. 2.Welcome! I'm main.
- 线程的属性
属性 属性类型及用途 只读 重要注意事项 编号
ID用于标识不同的线程,不同的线程拥有不同的编号 是 某个编号的线程运行结束后,该编号可能被后续创建的线程使用。不同线程拥有的编号虽然不同,但是这种编号的唯一性只在Java虚拟机的一次运行有效。也就是说重启一个Java虚拟机(如重启Web服务器)后,某些线程的编号可能与上次Java虚拟机运行的某个线程的编号一样,因此该属性的值不适合用作某种唯一标识,特别是作为数据库中的唯一标识(如主键) 名称
Name用于区分不同的线程。默认值与线程的编号有关,默认值的格式为:Thread-线程编号,如Thread-0 否 Java并不禁止我们将不同的线程的名称属性设置为相同的值。尽管如此,设置线程的名称属性有助于代码调试和问题定位 线程类别
Daemon值为true表示相应的线程为守护线程,否则表示相应的线程为用户线程。该属性的默认值与相应线程的父线程的该属性的值相同,在正常停止java程序时,当有用户线程还没执行完虚拟机不会立即停止,会等待其执行完毕,但是如果只有守护线程还没执行完则不会阻止虚拟机停止,这说明守护线程通常用于执行一些重要性不是很高的任务,例如监视其它线程的 否 该属性必须在相应线程启动之前设置,即对setDaemon方法的调用必须在对start方法的调用之前,否则setDaemon方法会抛出IllegalThreadStateException异常。负责一些关键任务处理的线程不适宜设置为守护线程 优先级
Priority该属性本质上是给线程调度器的提示,用于表示应用程序希望哪个线程能够优先得以运行。Java定义了1~10的10个优先级。默认值一般为5。对于具体的一个线程而言,其优先级的默认值与其父线程(创建该线程的线程)的优先级值相等 否 一般使用默认优先级即可。不恰当地设置该属性值可能导致严重的问题(线程饥饿) - 线程方法
方法 功能 备注 static Thread
currentThread()返回当前线程,即当前代码的执行线程(对象) 同一段代码对Thread.currentThread()的调用,其返回值可能对应着不同的线程(对象) void run() 用于实现线程的任务处理逻辑 该方法是由Java虚拟机直接调用的,一般情况下应用程序不应该调用该方法 void start() 启动相应线程 该方法的返回并不代表相应的线程已经被启动。一个Thread实例的start方法只能够被调用一次,多次调用会导致异常的抛出 void join() 等待相应线程运行结束 若线程A调用线程B的join方法,那么线程A的运行会被暂停,直到线程B运行结束 static void yield() 使当前线程主动放弃其对处理器的占用,这可能导致当前线程被暂停 这个方法是不可靠的。该方法被调用时当前线程可能仍然继续运行(视系统当前的运行状况而定) static void sleep(long millis) 使当前线程休眠(暂停运行)指定的时间
- Thread类实现Runnable接口
public class Thread implements Runnable{}
- Runnable是一个interface且其中只有一个方法,所以实现Runnable的类要重新run方法。
- 两种方式执行任务逻辑的过程
- 实现Runnable接口,重写run方法
class WelcomeTask implements Runnable { @Override public void run() { //任务逻辑 } }
new Thread(new WelcomeTask()).start();
WelcomeTask
实例在Thread中的传递过程,它最终会被赋值给Thread中的一个target成员变量
private Runnable target;
具体过程如下:
1.有参构造函数,生成一个ThreadName,例如:Thread-0
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
2.进入第一个初始化函数,这里面无需关注其它参数,因为还有其它构造方法会调用init(...)
private void init(ThreadGroup g, Runnable target, String name,long stackSize){ init(g, target, name, stackSize, null, true); }
3.第二层初始化函数,即将target赋值
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) { throw new NullPointerException("name cannot be null"); }this.name = name; ..... this.target = target; .... }
4.Thread实例调用start()方法
public synchronized void start() { //threadStatus != 0代表这个Thread实例已经被start()过了,所以会抛出异常 if (threadStatus != 0) throw new IllegalThreadStateException(); ... try { //最终会调用本地方法start0()来启动这个线程 start0(); ... } finally { ... } }
5.本地方法启动线程,即将线程交给虚拟机的线程调度器
private native void start0();
6.线程调度器执行线程任务逻辑
//启动线程后,调度器会帮助我们运行执行线程run()方法,即任务逻辑 //如果target != null的时候会调用WelcomeTask中实现的任务逻辑,否则什么都不会执行 public void run() { if (target != null) { target.run(); } }
- 继承Thread类,重写run方法
class WelcomeThread extends Thread { @Override public void run() { //任务逻辑 } }
new WelcomeThread().start(); ... //任务调度器直接调用WelcomeThread中实现的任务逻辑
- 实现Runnable接口,重写run方法
- 【JAVA中的线程世界】两种方式的区别
- 面向对象角度:一种是基于继承实现
extends Thread
,一种是基于组合new Thread(new Runable())
,组合相对与继承来说耦合性更低。 - 对象共享的角度:一个
CountingTask
实例可以被多个线程共享所以可能出现资源竞争问题。
class CountingThread extends Thread { int count = 0; @Override public void run() { for (int i = 0; i < 100; i++) { i++; } } System.out.println("count:" + count); }class CountingTask implments Runnable { int count = 0; @Override public void run() { for (int i = 0; i < 100; i++) { i++; } } System.out.println("count:" + count); }public static void main(String[] args) { Thread thread; CountingTask task = new CountingTask(); for(int i = 0; i < 10; i++){ thread = new Thread(task); thread.start(); } for(int i = 0; i < 10; i++){ thread = new CountingThread(); thread.start(); } }
- 面向对象角度:一种是基于继承实现
文章图片
- New(新创建)
Thread thread = new Thread();
此时线程只是备new出来并没有开始执行。 - Runnable(可运行)
thread.start();
1.ready:还没有被分配资源 。
2.running:正在执行 。 - 阻塞
1.Blocked(阻塞) 没有获取被synchronized保护的代码的锁 。
2.Waiting(等待)
1.Object.wait(); - o.notify()/o.notifyAll() 2.Thread.join(); - join线程结束/被中断 Blocked与Waiting的区别是Blocked是在等待释放某个资源,Waiting是在等待达到某个条件 。
- Time Waiting(计时等待) 设置了时间参数的一些阻塞 。
1.Thread.sleep(m);
- 时间超时/join的进程结束/被中断 。
2.Object.wait(m)
- 时间到/o.notify()/o.notifyAll()
3.Thread.join(m)
- 时间超时/join的进程结束/被中断 。
- Terminated(终止)
1.run方法正常执行完毕 。
2.出现没有捕获的异常,意外终止。
推荐阅读
- JavaWEB|javaWEB六(Servlet 之 其它相关问题)
- 操作系统|【杂谈】为什么说Linux和服务器是天生一对()
- 自己写了个Java|自己写了个Java RMI(远程方法调用)的实现案例
- Python|Python 爬取 "王者荣耀.英雄壁纸" 过程中的矛和盾
- 不寻常的 Java(StackTrace 扩展了 Throwable)
- dubbo线程模型及参数优化【持续更新】
- druid连接池引起的线程blocked
- 笔记|java图形用户界面编辑常用组件和容器
- 笔记|java中使用while计算1到100之间的奇数和
- 笔记|JDBC(一)