💕"i need your breath"💕
作者:Mylvzi
文章主要内容:线程学习(2)
前情回顾:
在上一篇博客中介绍到了进程与线程的区别,以及初步了解如何在Java实现多线程编程,通过内置的Thread类来实现多线程,充分利用多核cpu资源,要充分认识到每一个线程都是一个独立的"执行流",本篇文章继续讲解和Thread有关的一些操作
一.Thread类的创建方式
1.继承Thread 重写run
//创建一个类 继承于Thread类 class MyThread extends Thread { @Override public void run() { // 线程的入口 告诉线程要执行哪些逻辑 System.out.println("hello thread"); try { // 休眠1s Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } public class Test { public static void main(String[] args) throws InterruptedException { // 首先要实例化出一个Thread类 Thread thread = new MyThread(); // start和run都是Thread类的成员 // run只是告诉线程要去执行那些逻辑 // start是真正的调用系统的api,创建出一个线程,再让线程去执行run thread.start(); // thread.run(); while (true) { System.out.println("hello main"); // 休眠1s Thread.sleep(1000); } } }
2.实现Runnable 重写run
创建自定义类时让其实现Runnable接口,这样写的原因本质在于Thread类也实现了Runnable接口
class MyThread implements Runnable { @Override public void run() { while(true) { System.out.println("=="); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public class Demo3 { public static void main(String[] args) throws InterruptedException { // 使用向上转型 是Java生态中的常见方式 // 先实现一个Runnable接口 Runnable runnable = new MyThread(); Thread thread = new Thread(runnable); thread.start(); while (true) { System.out.println("=="); Thread.sleep(1000); } } }
说明:
Runnable表示的是一个"可以运行的任务",这个任务是交给线程执行还是交给其他是体执行,Runnable本身并不关心~
Runnable接口用来表示一个可以在线程中单独执行的任务,一个类只要实现了Runnable接口并且实现他的run方法,那么这个类的实例就能够单独在线程中执行,Runnable接口就像是一个点石成金的"魔法师",只要被他修饰过,就具有了"可被执行"的属性,这个任务不仅仅可以通过线程来执行,也可以通过线程池和执行器来执行
使用Runnable接口有哪些好处呢?直接继承Thread类不是更简单么?使用Runnable接口最大的好处就是可以"解耦合",降低代码之间的联系性,代码之间的联系性越高,耦合度就越高;反之亦然,耦合度过高不利于我们之后对代码进行修改~就像你和你最好的哥们一起创业,分钱肯定是不好分的~
上述两种创建Thread类的方式有所不同,第一种是直接通过MyThread类来实例化一个Thread类,第二种是先通过MyThread类先实例化一个Runnable接口,再通过这个接口去实例化一个Thread类。为什么第二种方式耦合度更低呢?原因在于第二种方式自定义类和Thread类之间的联系性降低了,他们之间是通过Runnable接口来联系起来的,以后使用更多线程的时候就都可以通过Runnable这个接口来实现,请看第二种方式创建线程的图解
第一种方式的图解
很明显第二种方式代码之间的耦合性更低
3.继承Thread,重写run,使用匿名内部类
public class Demo4 { public static void main(String[] args) throws InterruptedException { // 继承Thread 使用匿名内部类 Thread t = new Thread() { @Override public void run() { while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }; t.start(); while (true) { System.out.println("hello main"); Thread.sleep(1000); } } }
4.实现Runnable 重写run,使用匿名内部类
public class Demo11 { public static void main(String[] args) { // 实现Runnable 重写run 使用匿名内部类 Runnable runnable = new Runnable() { @Override public void run() { while(true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }; Thread t = new Thread(runnable); t.start(); } }
5.使用lambda表达式+Runnable接口(推荐方式)
Runnable接口是一个函数式接口,只有一个抽象方法run,所以可以使用lambda表达式来实现
public class Demo12 { public static void main(String[] args) throws InterruptedException { // 使用lambda表达式 Runnable runnable = () -> { while (true) { System.out.println("Mythread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }; Thread thread = new Thread(runnable); thread.start(); while (true) { System.out.println("main"); Thread.sleep(1000); } } }
使用这种方式创建线程,代码既简洁,又优雅,耦合性也低,推荐大家使用这种方式创建线程
Thread类的其他构造方法
Thread(String name) 创建线程对象,并命名
这个构造方法主要用于给线程命名,方便后续进行调试
// 可以为线程起一个名字作为标识 对线程的执行没有影响 就是单纯的一个"标识" 方便之后调试进行检查区分 Thread t = new Thread(() -> { while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } },"这是一个线程名字");
Thread(Runnable target, String name) 使用 Runnable 对象创建线程对象,并命名 【了解】Thread(ThreadGroup group, Runnable target) 线程可以被用来分组管理,分好的组即为线程组
二.Thread类的一些属性
1.ID
线程的唯一标识,是Java为每个线程分配的"身份标识"
获取方法
getId()
Thread t = new Thread(); long tid = t.getId();// 返回值是一个长整型 System.out.println("线程ID:" + tid);// 输出线程ID:20
2.名称name
就是线程的名字,便于后序进行调试
获取方法
getName()
Thread t = new Thread("我是线程"); String tName = t.getName(); System.out.println(tName);// 输出我是线程
注意:此方法在源码中是被final修饰的,意味着子类无法重写方法
3.状态 state
进程最常见的两种状态是就绪状态和阻塞状态,线程也有自己的一些属性
// 获取线程的所有状态 for (Thread.State state : Thread.State.values()) { System.out.print(state+" "); }
- NEW Thread 对象已经存在 但是还没有通过start方法调用
- RUNNABLE 就绪状态 线程已经在cpu上执行/等在在cpu上执行
- TERMINATED Thread对象还在 但系统内核中的线程不存在
- TIMED_WAITING 阻塞 由于sleep这种固定时间的方式产生的阻塞
- WAITING 阻塞 由于wait这种不固定时间的方式产生的阻塞
- BLOCKED 阻塞 由于锁竞争导致的阻塞
Thread t = new Thread(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); } }); System.out.println(t.getState());// Thread类存在,但是还没有调用start方法,状态为NEW t.start(); System.out.println(t.getState());// RUNNABLE Thread.sleep(3000); System.out.println(t.getState());// TERMINATED
4.优先级priority
获取线程的优先级
获取方法
getPriority
Thread t = new Thread("我是线程"); int tPriority = t.getPriority(); System.out.println(tPriority);
说明:其实此方法很"鸡肋",因为线程的优先级是由cpu的调度器决定的,在我们写代码的过程中很少去关注优先级,一是我们根本就观察不到,二是根本也没这个必要
线程学习(2)线程创建,等待,安全,synchronized(二)+https://developer.aliyun.com/article/1413578