线程的创建
- 写一个类去继承Thread类,重写run()方法
- 写一个类去实现Runable接口
public class MyThread extends Thread { @Override public void run() { for (int i=1;i<=100;i++){ System.out.println(i+"子线程启动。。。。。。。。。子线程"); } } }
public class TestCreateThread { public static void main(String[] args) { System.out.println("main方法启动。。。。。。。。。。主线程"); //创建子线程 Thread thread=new MyThread(); Thread thread1=new Thread(()->{ System.out.println("接口创建线程"); }); thread1.run(); //启动线程 thread.start(); for (int i=0;i<100;i++){ System.out.println(i+"main方法启动。。。。。。。。。。主线程"); } System.out.println("主线程的逻辑。。。。。"); } }
Thread类的常用方法
currentThread()方法
Thread.currentThread()方法可以获得当前线程 ,Java 中的任何一段代码都是执行在某个线程当中的. 执行当前代码的线程就是当前线程.同一段代码可能被不同的线程执行 , 因此当前线程是相对的,Thread.currentThread()方法的返回值是在代码实际运行时候的线程对象
setName()/getName()
thread.setName(线程名称), 设置线程名称,thread.getName()返回线程名称,通过设置线程名称,有助于程序调试,提高程序的可读性, 建议为每个线程都设置一个能够体现线程功能的名称
isAlive()
thread.isAlive()判断当前线程是否处于活动状态,活动状态就是线程已启动并且尚未终止
sleep()
Thread.sleep(millis); 让当前线程休眠指定的毫秒数
getId()
hread.getId()可以获得线程的唯一标识
注意:
某个编号的线程运行结束后,该编号可能被后续创建的线程使用,重启的 JVM 后,同一个线程的编号可能不一样
yield()
Thread.yield()方法的作用是放弃当前的 CPU 资源
setPriority()
thread.setPriority( num ); 设置线程的优先级,java 线程的优先级取值范围是 1 ~ 10 , 如果超出这个范围会抛出异常 IllegalArgumentException.
在操作系统中,优先级较高的线程获得 CPU 的资源越多,线程优先级本质上是只是给线程调度器一个提示信息,以便于调度器决定先调度哪些线程. 注意不能保证优先级高的线程先运行.
Java 优先级设置不当或者滥用可能会导致某些线程永远无法得到运行,即产生了线程饥饿.线程的优先级并不是设置的越高越好,一般情况下使用普通的优先级即可,即在开发时不必设置线程的优先级
线程的优先级具有继承性, 在 A 线程中创建了 B 线程,则 B 线程的优先级与 A 线程是一样的
interrupt()
中断线程.注意调用 interrupt()方法仅仅是在当前线程打一个停止标志,并不是真正的停止线程,搭配isInterrupt()使用
setDaemon()
Java 中的线程分为用户线程与守护线程
守护线程是为其他线程提供服务的线程,如垃圾回收器(GC)就是一个典型的守护线程
守护线程不能单独运行, 当 JVM 中没有其他用户线程,只有守护线程时,守护线程会自动销毁, JVM 会退出
示例:
package com.edu.creatThread; /** * @作者 five-five * @创建时间 2020/9/13 */ public class DemonThread extends Thread { @Override public void run() { super.run(); while(true){ System.out.println("守护线程启动。。。。。。"); } } } ------------------------------------------------------------------------- package com.edu.creatThread; /** * @作者 five-five * @创建时间 2020/9/13 */ public class TestDemon { public static void main(String[] args) { DemonThread demonThread = new DemonThread(); demonThread.setDaemon(true);//设置线程为守护线程 for (int i=0;i<1;++i){ System.out.println("main线程在跑。。。。。"); } demonThread.start(); } }
多线程的生命周期
线程的生命周期是线程对象的生老病死,即线程的状态
线程生命周期可以通过 getState()方法获得 , 线程的状态是Thread.State 枚举类型定义的
NEW,新建状态.
创建了线程对象,在调用 start()启动之前的状态
RUNNABLE,可运行 状态
它 是一 个复 合状 态 , 包 含 :READY 和RUNNING 两个状态. READY 状态该线程可以被线程调度器进行调度使它 处 于 RUNNING 状 态 , RUNING 状 态 表 示 该 线 程 正 在 执 行 .
Thread.yield()方法可以把线程由 RUNNING 状态转换为 READY 状态
BLOCKED 阻塞状态
线程发起阻塞的 I/O 操作,或者申请由其他线程占用的独占资源,线程会转换为 BLOCKED 阻塞状态. 处于阻塞状态的线程不会占用 CPU 资源. 当阻塞 I/O 操作执行完,或者线程获得了其申
请的资源,线程可以转换为 RUNNABLE.
WAITING 等待状态
线程执行了 object.wait(), thread.join()方法会把线程转换为 WAITING 等待状态, 执行 object.notify()方法,或者加入的线程执行完毕,当前线程会转换为 RUNNABLE 状态
TIMED_WAITING 状态
与 WAITING 状态类似,都是等待状态.区别在于处于该状态的线程不会无限的等待,如果线程没有在指定的时间范围内完成期望的操作,该线程自动转换为 RUNNABLE
TERMINATED 终止状态
线程结束处于终止状态
线程生命示例图:
多线程编程存在的问题与风险:
线程安全(Thread safe)问题.
多线程共享数据时,如果没有采取正确的并发访问控制措施,就可能会产生数据一致性问题,如读取脏数据(过期的数据), 如丢失数据更新
线程活性(thread liveness)问题.
由于程序自身的缺陷或者由资源稀缺性导致线程一直处于非 RUNNABLE 状态,这就是线程活性问题
常见的活性故障有以下几种:
- 死锁(Deadlock). 类似鹬蚌相争.
- 锁死(Lockout), 类似于睡美人故事中王子挂了
- 活锁(Livelock). 类似于小猫咬自己尾巴
- 饥饿(Starvation).类似于健壮的雏鸟总是从母鸟嘴中抢到食物.
上下文切换(Context Switch).
处理器从执行一个线程切换到执行另外一个线程
可靠性
可能会由一个线程导致 JVM 意外终止,其他的线程也无法执行