摘要:执行新线程的方式是调用该线程对象的方法。线程之间没有父子关系线程与线程之间是平等的,并没有父子关系。线程是并行执行的在示例中,其实是与主线程并行执行的。当它运行完毕时,我们就得到所有线程的执行结果了。
主线程
首先每个 Java 程序都是从主线程开始运行的,主线程就是执行 main() 方法的那个线程。在 main() 方法中获取当前线程很简单:
// 示例1 public static void main(String[] args) { Thread mainThread = Thread.currentThread(); System.out.println("当前线程: " + mainThread.getName()); }
Thread 对象的文档在这里。Thread 对象包含很多方法和属性,除了上面例子当中的 name 属性外,还有状态、优先级等等。现在我们只需要知道 main() 方法是在主线程中运行就可以了。
线程是可以暂停的我们通常使用 sleep() 方法来使线程在指定的时间内暂停执行。下面是一个在主线程中执行循环和暂停的例子:
// 示例2 public static void main(String[] args) { for (int i = 0; i < 10; i++) { System.out.println(i); try { Thread.sleep(500); // 暂停线程 } catch (InterruptedException e) { e.printStackTrace(); } } }
注意:首先,sleep() 方法是静态的,因为它永远只能用在当前的线程上;其次,sleep() 方法会抛出异常,该异常通常发生在暂停状态被打断时。所以这里用 try-catch 代码块包围起来。
sleep() 方法接受一个 long 类型的参数,指明需要暂停多少毫秒。这个例子当中,我们循环输出 i 变量,共循环 10 次,每输出一次就暂停 500 毫秒。
你可能觉得整个程序的运行时间会是精确的 5000 毫秒,但请千万不要这么认为,首先 sleep() 方法并非十分精确,CPU 在各个线程之间切换会要花掉很微量的一点时间,如果这个例子循环次数不是 10 次而是十万次百万次,那么积累的误差就会比较大了;其次,代码中的 System.out.println() 方法和 for 循环本身也要花掉一点时间,所以每次循环不会是绝对精确的 500 毫秒。
创建新的线程除了主线程外,我们还可以创建和执行另外的线程。执行新线程的方式是调用该线程对象的 start() 方法。
// 示例3 public static void main(String[] args) { Thread thread1 = new Thread(); thread1.start(); // 启动线程 }
从这个例子中我们可以看到,thread1 是一个通过 new Thread() 创建出来的对象。把线程看作是对象这点十分重要,这意味着我们可以创建 Thread 的子类,而子类的对象仍然是线程对象。执行这段代码什么输出都没有,因为我们没有为 thread1 定义要执行什么操作。下面的例子中,我们让 thread1 来做循环输出。
// 示例4 public static void main(String[] args) { Thread thread1 = new Thread() { @Override public void run() { // 指定线程要做的事 for (int i = 0; i < 10; i++) { System.out.println(i); try { Thread.sleep(500); // 暂停线程 } catch (InterruptedException e) { e.printStackTrace(); } } } }; thread1.start(); // 启动线程 }
这里我们创建了一个 Thread 类的匿名子类,并覆写了 run() 方法。通过覆写 run() 方法,我们可以指定线程要做哪些事情。
线程是并行执行的线程之间没有父子关系
线程与线程之间是平等的,并没有父子关系。不过 Java 为了方便管理线程,定义了一个叫线程组(ThreadGroup)的类,线程组之间可以存在父子关系。不过这个概念平常用的很少,所以这里只是顺带提下,不作详细介绍。
在示例4中,thread1 其实是与主线程并行执行的。为了演示这点,我们首先将这个循环提取成一个方法:
private static void printNumbers(int start, int end) { for (int i = start; i < end; i++) { System.out.println(i); try { Thread.sleep(500); // 暂停线程 } catch (InterruptedException e) { e.printStackTrace(); } } }
然后示例4就变成了:
public static void main(String[] args) { Thread thread1 = new Thread() { @Override public void run() { printNumbers(0, 10); // 提取出来的方法 } }; thread1.start(); // 启动线程 }
我们在最后面添加一行,让主线程在启动 thread1 之后也做一个循环输出:
// 示例5 public static void main(String[] args) { Thread thread1 = new Thread() { @Override public void run() { printNumbers(0, 10); // 循环输出 } }; thread1.start(); // 启动线程 printNumbers(100, 110); // 主线程也循环输出 }
执行 main() 方法,在输出中你可以看到 0~9 与 100~109 交替出现,这说明主线程和 thread1 在同时执行。
将线程中的逻辑独立出来为了使线程中的逻辑能够被重用,我们通常将其声明为一个独立的类。在前面的代码示例中,我们都是以匿名类的方式来创建线程的。独立声明一个线程类的方式是这样的:
// 示例6 public class MyThread extends Thread { private int start; private int end; // 构造方法 public MyThread(int start, int end) { this.start = start; this.end = end; } @Override public void run() { printNumbers(); } private void printNumbers() { for (int i = this.start; i < this.end; i++) { System.out.println(i); try { Thread.sleep(500); // 暂停线程 } catch (InterruptedException e) { e.printStackTrace(); } } } }
在示例 6 中我们可以看到,执行这个线程所需的两个参数现在变成了 MyThread 的两个成员。这是我们向线程传递执行参数的一般方式。提取成独立的类之后,线程使用起来就非常简单了:
public static void main(String[] args) { new MyThread(0,10).start(); new MyThread(100,110).start(); new MyThread(1000,1010).start(); }线程的返回值
我们有时候希望当线程执行完毕时,我们能得到一个结果。在示例 6 中我们了解了向线程传递参数的方式,类似的我们也可以为线程类定义一个成员用来保存线程的执行结果。下面是一个例子:
// 示例7 public class ThreadWithReturnValue extends Thread { public String result; @Override public void run() { try { Thread.sleep(3000); this.result = "result"; // 假设产生结果需要比较长的时间 } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws Exception { ThreadWithReturnValue thread = new ThreadWithReturnValue(); thread.start(); Thread.sleep(3100); System.out.println(thread.result); // 获得结果 } }
在这个例子当中,ThreadWithReturnValue 线程产生结果需要 3 秒钟,那么主线程就需要等待 3 秒以上才能得到 "result",否则就只能得到 null。在实际情况中,我们并不知道线程产生结果需要多长时间,而我们也不想无限制的等下去。
出于这样的目的,Thread 对象为我们提供了 join() 方法,用于等待指定的线程直到执行完毕。示例 7 当中的 main() 方法可以改造成这样子:
public static void main(String[] args) throws Exception { ThreadWithReturnValue thread = new ThreadWithReturnValue(); thread.start(); thread.join(); // 等待直到 thread 执行完毕 System.out.println(thread.result); }
这样我们就能在线程执行完毕时立刻得到结果了。我们可以运行多个线程,然后依次调用它们的 join() 方法,这样等待的时间就是它们当中运行最久的那个线程的运行时间。当它运行完毕时,我们就得到所有线程的执行结果了。线程的入门概念就介绍到这里,本文只是介绍非常基本的概念,Java 在处理多线程和并发方面还有很多很复杂的东西等待你去了解和尝试。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/64182.html
摘要:本文是作者自己对中线程的状态线程间协作相关使用的理解与总结,不对之处,望指出,共勉。当中的的数目而不是已占用的位置数大于集合番一文通版集合番一文通版垃圾回收机制讲得很透彻,深入浅出。 一小时搞明白自定义注解 Annotation(注解)就是 Java 提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法。Annotion(注解) 是一个接口,程序可以通过...
摘要:哪吒社区技能树打卡打卡贴函数式接口简介领域优质创作者哪吒公众号作者架构师奋斗者扫描主页左侧二维码,加入群聊,一起学习一起进步欢迎点赞收藏留言前情提要无意间听到领导们的谈话,现在公司的现状是码农太多,但能独立带队的人太少,简而言之,不缺干 ? 哪吒社区Java技能树打卡 【打卡贴 day2...
摘要:一个线程做完,并将数据刷新回主内存了,下一个线程才会启动。声明了的变量在被赋值之后,线程会立刻将值写回主内存在读取变量时,线程会到主内存去读取变量的最新值。 一个例子: public class Counter { public static int count = 0; public synchronized static void inc() { ...
摘要:我的学习笔记汇总标签笔记分为两大部分和笔记内容主要是对一些基础特性和编程细节进行总结整理,适合了解基础语法,想进一步深入学习的人如果觉得不错,请给,这也是对我的鼓励,有什么意见欢迎留言反馈目录基础巩固笔记反射基础巩固笔记泛型基础巩 我的java&javaweb学习笔记(汇总) 标签: java [TOC] 笔记分为两大部分:javase和javaweb javase javawe...
阅读 3593·2021-11-23 09:51
阅读 2794·2021-11-23 09:51
阅读 675·2021-10-11 10:59
阅读 1672·2021-09-08 10:43
阅读 3223·2021-09-08 09:36
阅读 3288·2021-09-03 10:30
阅读 3292·2021-08-21 14:08
阅读 2195·2021-08-05 09:59