详解Java创建线程的五种常见方式

目录
Java中如何创建线程呢?
1.显示继承Thread,重写run来指定现成的执行代码。
2.匿名内部类继承Thread,重写run来执行线程执行的代码。
3.显示实现Runnable接口,重写run方法。
4.匿名内部类实现Runnable接口,重写run方法
5.通过lambda表达式来描述线程执行的代码
【面试题】:Thread的run和start之间的区别?
Thread类的具体用法
Thread类常见的一些属性
中断一个线程
1.方法一:让线程run完
2.方法二:调用interrupted()方法
线程等待
线程休眠
线程的状态转换
Java中如何进行多线程编程,如何使用多线程?在Java标准库中提供了一个Thread类。Java中,一个进程正在运行时至少会有一个线程正在运行,这些线程在后台默默地执行,比如调用main()方法时就是这样的,主线程是由JVM创建的。实现多线程编程的方式主要有两种,一是继承Thread类,另一种是实现Runnable接口。这里我们先来看看Thread类的结构:

从源代码中可以发现,Thread类实现了Runnable接口,它们之间具有多态关系。其实,使用继承Thread类的方式创建新线程是,最大的局限就是不支持多继承,因为在Java语言特点就是单继承,所以为了支持多继承,完全可以实现Runnable接口的方式。

Java中如何创建线程呢?
1.显示继承Thread,重写run来指定现成的执行代码。
代码
public class Demo1 {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println(“hello world, 我是一个线程”);
while (true) {

  1. }
  2. }
  3. }
  4. public static void main(String[] args) {
  5. // 创建线程需要使用 Thread 类, 来创建一个 Thread 的实例.
  6. // 另一方面还需要给这个线程指定, 要执行哪些指令/代码.
  7. // 指定指令的方式有很多种方式, 此处先用一种简单的, 直接继承 Thread 类,
  8. // 重写 Thread 类中的 run 方法.
  9. // [注意!] 当 Thread 对象被创建出来的时候, 内核中并没有随之产生一个线程(PCB).
  10. Thread t = new MyThread();
  11. t.start();
  12. // 执行这个 start 方法, 才是真的创建出了一个线程.
  13. // 此时内核中才随之出现了一个 PCB, 这个 PCB 就会对应让 CPU 来执行该线程的代码. (上面的 run 方法中的逻辑)
  14. while (true) {
  15. // 这里啥都不干
  16. }
  17. }
  18. }

2.匿名内部类继承Thread,重写run来执行线程执行的代码。
代码
public class Demo2 {
// Runnable 本质上就是描述了一段要执行的任务代码是啥.
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(“我是一个新线程”);
}
}

  1. public static void main(String[] args) {
  2. // 2. 通过匿名内部类的方式继承 Thread
  3. Thread t = new Thread() {
  4. @Override
  5. public void run() {
  6. }
  7. };
  8. t.start();
  9. }

3.显示实现Runnable接口,重写run方法。
代码
public class Demo3 {
// Runnable 本质上就是描述了一段要执行的任务代码是啥.
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(“我是一个新线程”);
}
}

  1. public static void main(String[] args) {
  2. // 3. 显式创建一个类, 实现 Runnable 接口, 然后把这个 Runnable 的实例关联到 Thread 实例上.
  3. Thread t = new Thread(new MyRunnable());
  4. t.start();

4.匿名内部类实现Runnable接口,重写run方法
代码
public class Demo4 {
// Runnable 本质上就是描述了一段要执行的任务代码是啥.
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(“我是一个新线程”);
}
}

  1. public static void main(String[] args) {
  2. // 4. 通过匿名内部类来实现 Runnable 接口
  3. Runnable runnable = new Runnable() {
  4. @Override
  5. public void run() {
  6. System.out.println("我是一个新线程");
  7. }
  8. };
  9. Thread t = new Thread(runnable);
  10. t.start();
  11. }

5.通过lambda表达式来描述线程执行的代码
代码
public class Demo4 {
// Runnable 本质上就是描述了一段要执行的任务代码是啥.
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(“我是一个新线程”);
}
}

  1. public static void main(String[] args) {
  2. // 5. 使用 lambda 表达式来指定 线程执行的内容
  3. Thread t = new Thread(() -> {
  4. System.out.println("我是一个新线程");
  5. });
  6. t.start();
  7. }

【面试题】:Thread的run和start之间的区别?
run()方法::普通的方法调用,没有创建新的线程,输出语句是在原线程中执行的。

start()方法::这才是创建了一个新线程,由新的线程来执行输出

Thread类的具体用法

Thread类常见的一些属性

ID是现成的唯一标识,不同线程不会重复

名称是各种调试工具会用到的

状态标识线程当前所处的一个情况

优先级高的线程理论上来说更容易被调度到

关于后台先后曾,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行

是否存活,即run方法是否运行结束了

线程的中断问题

我们通过编写具体的代码来观察方法的使用:
public class ThreadDemo6 {
public static void main(String[] args) {
Thread t=new Thread(“cxk”){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//run方法执行过程中就代表着系统内线程得生命周期
//润方法执行中,内核的线程就存在
//run方法执行完毕,内核中的线程随之销毁

  1. //这一组属性,只要线程创建完毕,属性就变了
  2. System.out.println(t.getName());
  3. System.out.println(t.getPriority());
  4. System.out.println(t.isDaemon());
  5. System.out.println(t.getId());
  6. //这俩属性会随着现成的运行过程而发生改变
  7. System.out.println(t.isAlive());
  8. System.out.println(t.isInterrupted());
  9. System.out.println(t.getState());
  10. t.start();
  11. while (t.isAlive()){
  12. System.out.println("cxk线程正在执行");
  13. System.out.println(t.isInterrupted());
  14. System.out.println(t.getState());
  15. }
  16. }
  17. }

执行结果如下:会出现很多组相同的数据

中断一个线程
让一个线程结束有两种情况:

1.此线程已经把任务执行完了。即让线程run完(比较温和)。

2.此线程将任务执行到一半,被强制结束。即调用线程的interrupt()方法,比较激烈。

1.方法一:让线程run完
这种结束方式比较温和,当标记位被设置上之后,等到这次循环执行完了之后,在结束线程,如下,当线程执行到sleep的时候,已经sleep100ms了,此时isQuit被设置为true,当前线程不会立即退出,而是会继续sleep,把剩下的 400ms sleep完才会结束这个线程。
public class ThreadDemo7 {
private static boolean isQuit=false;

  1. public static void main(String[] args) throws InterruptedException {
  2. Thread t=new Thread(){
  3. @Override
  4. public void run() {
  5. while (!isQuit){
  6. System.out.println("别烦我,我在忙着转账呢");
  7. try {
  8. Thread.sleep(500);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. System.out.println("转账操作被终止");
  14. }
  15. };
  16. t.start();
  17. Thread.sleep(500);
  18. //老板来电话了说对方是内鬼终止交易
  19. System.out.println("有内鬼,终止交易!!!");
  20. isQuit = true;
  21. }
  22. }

执行结果:

2.方法二:调用interrupted()方法
public class ThreadDemo8 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread() {
@Override
public void run() {
// 此处直接使用线程内部的标记位来判定.
while (!Thread.currentThread().isInterrupted()) {
System.out.println(“别管我, 我在忙着转账呢”);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
System.out.println(“转账被终止.”);
}
};
t.start();

  1. Thread.sleep(5000);
  2. System.out.println("对方是内鬼, 快终止交易!!!");
  3. t.interrupt();
  4. }
  5. }

执行结果如下:

在这段代码中,t.start()是主线程继续往下执行之后,主线程还是会继续走,新线程则会执行run方法,如果没有后续的sleep,新线程能否继续输出就是不确定的了。原因:多线程之间是抢占实质性的,如果主线程中没有sleep,此时接下来CPU是执行主现成的isQuit=true还是新线程的while循环,这都是不确定的。对于新线程来说,run方法执行完,线程就结束了。对于主线程来说main方法执行完,住线程就结束了。

由上可得:

1.通过thread对象调用interrupt()方法通知该线程停止运行。

2.thread收到通知的方式有两种:

如果线程调用了wait/join/sleep等方法而阻塞挂起,则以InterrupterException异常的形式通知,清除中断标志

如果没有调用上述方式,就只是内部的一个中断标志被设置,thread可以通过Thread.interrupted()判断当前线程的中断标志被设置,来清除中断标志。也可以

使用Thread.currentThread().isInterrupted()判断指定线程的中断标志被设置,但是不会清除中断标志。

在Java中第二种方式通知收到的更及时,即使线程正在sleep也可以马上收到。
public class ThreadDemo10 {
public static void main(String[] args) {
Thread t=new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().isInterrupted());
//仅仅是判定标记位,不会修改标记位
}
}
};
t.start();
t.interrupt();
}
}
public class ThreadDemo11 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(){
@Override
public void run() {
System.out.println(“我是新线程”);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t.start();
while (true){
System.out.println(“我是主线程”);
Thread.sleep(1000);
//对于新线程来说,run方法执行完新线程就结束了,
//对于主线程来说,main方法执行完主线程就结束了
}
}
线程等待
线程之间是并发执行的关系,多个线程之间,谁先执行,谁后执行,谁执行到何处让出CPU…开发人员是完全无法感知的,全权由系统内核负责,例如,创建一个新线程的时候,此时接下来是主线程继续执行,还是新线程执行,这个事情是不能保证的,这就是“抢占式”执行的重要特点。这时候就引入了线程等待:开发人员可以控制哪个线程先结束,哪个线程后结束。join()方法的执行就会让线程阻塞,一直阻塞到对应线程执行结束之后,才会继续执行。这就可以控制线程结束的先后顺序。如果线程结束了才调用到join,此时也会立刻返回。
public class ThreadDemo12 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(“我是线程1”);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t2=new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(“我是线程2”);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
t2.start();
t1.join();// join 起到的效果是等待线程结束. 当执行到这行代码是, 程序就阻塞了. 一直阻塞到 t1 结束, 才会继续执行.
t2.join();
System.out.println(“主线程执行完毕”);
}
}
执行结果如下:

线程休眠

当线程在正常运行计算判断逻辑此时就是在就绪队列中排队,调度器就会从就绪队列中筛选出合适的PCB让他上CPU执行,如果某个线程调用sleep就会让对应的线程PCB进入到阻塞队列,线程一旦进入到了阻塞队列是没有办法上CPU执行的,对于sleep进入冷宫的时间是有限制的,时间到了之后,就自动被系统把这个PCB那回到原来的就绪队列中了。

线程的状态转换

更多相关文章

  1. TortoiseSVN 执行清理( cleanUp )失败的解决方案
  2. android中view组件使用详解
  3. Android使用View类动画
  4. android 的Handler处理UI主线程外的耗时操作
  5. Android(安卓)Socket网络通信
  6. Android(安卓)Handler学习笔记
  7. 【黑科技】钉钉自动打卡
  8. Android线程间通信的Message机制
  9. Android之Handler用法总结

随机推荐

  1. Android 单选队列 RadioGroup与RadioButt
  2. 48.Android(安卓)标签TextView的点击技巧
  3. Android界面开发
  4. Android Handler机制4之Looper与Handler
  5. android Toast大全(五种情形)建立属于你自
  6. android 应用移植到ophone 平台需注意
  7. 设置TextView文字居中
  8. Android 音视频汇总
  9. Android内嵌H5(1)
  10. Android学习笔记(2)---android字体风格设置