JAVA基础 多线程技术学习笔记(V1.0) 2

简介: JAVA基础 多线程技术学习笔记(V1.0)

2.4 线程状态和生命周期

一个线程对象在它的生命周期内,需要经历5个状态。

1.新生状态(New)

用new关键字建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态。

2.就绪状态(Runnable)

处于就绪状态的线程已经具备了运行条件,但是还没有被分配到CPU,处于“线程就绪队列”,等待系统为其分配CPU。就绪状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会进入执行状态。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。有4种原因会导致线程进入就绪状态:      

  • 新建线程:调用start()方法,进入就绪状态;
  • 阻塞线程:阻塞解除,进入就绪状态;
  • 运行线程:调用yield()方法,直接进入就绪状态;
  • 运行线程:JVM将CPU资源从本线程切换到其他线程。

3、运行状态(Running)

在运行状态的线程执行自己run方法中的代码,直到调用其他方法而终止或等待某资源而阻塞或完成任务而死亡。如果在给定的时间片内没有执行结束,就会被系统给换下来回到就绪状态。也可能由于某些“导致阻塞的事件”而进入阻塞状态。

4、阻塞状态(Blocked)

阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。


有4种原因会导致阻塞:


1.执行sleep(int millsecond)方法,使当前线程休眠,进入阻塞状态。当指定的时间到了后,线程进入就绪状态。

2.执行wait()方法,使当前线程进入阻塞状态。当使用nofity()方法唤醒这个线程后,它进入就绪状态。

3.线程运行时,某个操作进入阻塞状态,比如执行IO流操作(read()/write()方法本身就是阻塞的方法)。只有当引起该操作阻塞的原因消失后,线程进入就绪状态。

4.join()线程联合: 当某个线程等待另一个线程执行结束后,才能继续执行时,使用join()方法。

5、死亡状态(Terminated)


死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有两个。一个是正常运行的线程完成了它run()方法内的全部工作; 另一个是线程被强制终止,如通过执行stop()或destroy()方法来终止一个线程(注:stop()/destroy()方法已经被JDK废弃,不推荐使用)。当一个线程进入死亡状态以后,就不能再回到其它状态了。

三、线程的使用

3.1 终止线程的典型方式

终止线程我们一般不使用JDK提供的stop()/destroy()方法(它们本身也被JDK废弃了)。通常的做法是提供一个boolean型的终止变量,当这个变量置为false,则终止线程的运行。因为线程可能还有后续工作,不能直接将他们嘎了。控制子线程生死的是主线程。

package cn.it.bz.Thread;
import java.io.IOException;
public class KillThread implements Runnable {
    //生死牌,true为生,false为死
    private boolean flag = true;
    //控制生死牌的方法
    public void killThread(){
        this.flag = false;
    }
    //子线程
    @Override
    public void run() {
        System.out.println("子线程开始:"+Thread.currentThread().getName());
        int  i = 0;
        while (flag){
            System.out.println(Thread.currentThread().getName()+"-"+i);
            i++;
            try {
                Thread.sleep(1000); //休眠,线程由运行状态变为阻塞状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("子线程结束");
    }
    //主线程
    public static void main(String[] args) throws IOException {
        System.out.println("主线程开始");
        KillThread kill = new KillThread();
        Thread thread = new Thread(kill);
        //启动子线程
        thread.start();
        //使主线程阻塞
        System.in.read();
        //主线程结束,结束子线程
        kill.killThread();
        System.out.println("主线程结束");
    }
}

主线程启动后,将创建的线程对象包装为Thread 对象,调用start();方法启动子线程。此时子线程执行while循环,主线程阻塞,但是子线程一直在执行,当从键盘输入数据时,主线程不再阻塞并开始向下执行,子线程杀死程序和打印输出语句,主线程是不会等待子线程死亡的。子线程被杀死时不是立即结束工作,而是先执行完线程(也就是run方法)后死亡。

3.2 线程休眠

sleep()方法:可以让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态。sleep方法为Thread类的静态方法,参数为休眠的毫秒数(1秒 = 1000毫秒)。

package cn.it.bz.Thread;
public class SleepThread implements Runnable {
    @Override
    public void run() {
        System.out.println("子线程开始:"+Thread.currentThread().getName());
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
            //子线程休眠
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //主线程,主线程在哪个类是没有区别
    public static void main(String[] args) {
        System.out.println("主线程开始");
        SleepThread sleepThread = new SleepThread();
        Thread thread = new Thread(sleepThread);
        //启动子线程
        thread.start();
        System.out.println("主线程结束");
    }
}

7e98e9100a384cdaa1e3934902b9ec84.png

主线程是不会等待子线程的,两个线程分别执行各自的。

3.3 线程让步

yield()让当前正在运行的线程回到就绪状态,以允许具有相同优先级的其他线程获得运行的机会。因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。

使用yield方法时要注意的几点:

  • yield是一个静态的方法。
  • 调用yield后,yield告诉当前线程把运行机会交给具有相同优先级的线程。
  • yield不能保证,当前线程迅速从运行状态切换到就绪状态。
  • yield只能是将当前线程从运行状态转换到就绪状态,而不能是等待或者阻塞状态。当让步线程遇到堵塞时先变为阻塞态,阻塞结束了再变为就绪态。
package cn.it.bz.Thread;
public class TestyieldThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 15; i++) {
            //如果当前线程名字是Thread-1,就让步,而且只让步第一次
            if ("Thread-1".equals(Thread.currentThread().getName())){
                if (i == 0){
                    System.out.println("我™直接让步~");
                    Thread.yield();
                }
            }
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
    public static void main(String[] args) {
        Thread thread1 = new Thread(new TestyieldThread());//子线程1
        Thread thread2 = new Thread(new TestyieldThread());//子线程2
        //启动线程,线程的运行顺序取决于CPU的线程调度
        thread1.start();
        thread2.start();
    }
}

3.4 线程联合

当前线程邀请调用方法的线程优先执行,在调用方法的线程执行结束之前,当前线程不能再次执行。线程A在运行期间,可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。和Java中方法的执行顺序差不多。

join方法的使用

join()方法就是指调用该方法的线程在执行完run()方法后,再执行join方法后面的代码,即将两个线程合并,用于实现同步控制。

package cn.it.bz.Thread;
import java.util.stream.Stream;
//子线程A
class A implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("当前A线程:"+Thread.currentThread().getName()+"--"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//主线程
public class TestJoinThread {
    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(new A());
        threadA.start();
        for (int i = 0; i < 10; i++) {
            if (i == 2) {
                //主线程联合A线程,直接在主线程调用join方法
                threadA.join();
            }
            System.out.println("主线程:"+Thread.currentThread().getName()+"--"+i);
            Thread.sleep(1000);
        }
    }
}

主线程和A线程在没有联合之前是同步执行的,但是执行到threadA.join();时,主线程会等待A线程执行完毕之后再执行。

3.4.1 线程联合案例

package cn.it.bz.Thread;
//儿子买烟线程
class SonThread implements Runnable{
    @Override
    public void run() {
        System.out.println("儿子得知要去买烟,买烟需要十分钟");
        for (int i = 0; i < 10; i++) {
            System.out.println("第"+i+"分钟");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("儿子买烟回来了");
    }
}
//爸爸抽烟线程
class FatherThread implements Runnable{
    @Override
    public void run() {
        System.out.println("爸爸想抽烟发现烟抽完了,让儿子去买包华子");
        //启动儿子买烟线程
         Thread thread = new Thread(new SonThread());
         thread.start();
         //爸爸需要等着儿子买烟回来
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("儿子买烟出异常了,爸爸出门找儿子");
            System.exit(1); //结束运行在虚拟机的进程,找儿子去吧
        }
        System.out.println("爸爸开心地接过烟,猛吸了一口,说真好!b( ̄▽ ̄)d ");
    }
}
public class TestJoinDemo {
    public static void main(String[] args) {
        System.out.println("这是个爸爸和儿子的故事~");
        Thread thread = new Thread(new FatherThread());
        thread.start();
    }
}

3.5  Thread类中的其他常用方法

3.5.1 获取当前线程名称

方式一

this.getName()获取线程名称,该方法适用于继承Thread实现多线程方式。

class GetName1 extends Thread{

 @Override

 public void run() {

   System.out.println(this.getName());

  }

}

方式二

Thread.currentThread().getName()获取线程名称,该方法适用于实现Runnable接口实现多线程方式。Thread.currentThread()获取当前线程对象

class GetName2 implements Runnable{

 @Override

 public void run() {

   System.out.println(Thread.currentThread().getName());

  }

}

3.5.2 修改线程名称

方式一

当线程继承Thread类时通过构造方法设置线程名称。

package cn.it.bz.Thread;
class SetName1 extends Thread{
    //接受自己定义的线程名称
    public SetName1(String name){
        super(name); //调用父类的构造方法
    }
    @Override
    public void run() {
        System.out.println("SetName1线程名称:"+this.getName());
    }
}
//主线程
public class TestSetNameThread {
    public static void main(String[] args) {
            SetName1 setName1 = new SetName1("setName1");
            setName1.start();
    }
}

方式二

当线程实现Runable接口时通过setName()方法设置线程名称。

package cn.it.bz.Thread;
class SetName implements Runnable{
    @Override
    public void run() {
        System.out.println("当前线程名字:"+Thread.currentThread().getName());
    }
}
public class TestSetNameThread2 {
    public static void main(String[] args) {
         //创建Thread对象
        Thread thread = new Thread(new SetName());
        thread.setName("😄");
        thread.start();
    }
}

3.5.3判断线程是否存活

isAlive()方法: 判断当前的线程是否处于活动状态。返回值是true表示活着,false表示死亡。

活动状态是指线程已经启动且尚未终止,线程处于正在运行或准备开始运行的状态,就认为线程是存活的。

package cn.it.bz.Thread;
class Alive implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("当前线程:"+Thread.currentThread().getName()+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//主线程
public class TestAliveThread {
    public static void main(String[] args) throws InterruptedException {
      Thread thread = new Thread(new Alive());
      thread.setName("🤭");
      thread.start();
      System.out.println("当前🤭线程是否存活:"+thread.isAlive());//true
      Thread.sleep(7000); //主线程休眠7秒
      System.out.println("当前🤭线程是否存活:"+thread.isAlive());//false
    }
}

4a8d2ebb403845f398b86e00c887a33d.png

相关文章
|
5天前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
7天前
|
存储 安全 Java
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(进阶篇)
本文是Java基础的进阶篇,对异常、集合、泛型、Java8新特性、I/O流等知识进行深入浅出的介绍,并附有对应的代码示例,重要的地方带有对性能、底层原理、源码的剖析。适合Java初学者。
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(进阶篇)
|
2天前
|
Java
深入理解Java中的多线程编程
本文将探讨Java多线程编程的核心概念和技术,包括线程的创建与管理、同步机制以及并发工具类的应用。我们将通过实例分析,帮助读者更好地理解和应用Java多线程编程,提高程序的性能和响应能力。
15 4
|
6天前
|
Java 调度 开发者
Java中的多线程基础及其应用
【9月更文挑战第13天】本文将深入探讨Java中的多线程概念,从基本理论到实际应用,带你一步步了解如何有效使用多线程来提升程序的性能。我们将通过实际代码示例,展示如何在Java中创建和管理线程,以及如何利用线程池优化资源管理。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧,帮助你更好地理解和应用多线程编程。
|
7天前
|
存储 安全 Java
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)
本文是“Java学习路线”中Java基础知识的高级篇,主要对多线程和反射进行了深入浅出的介绍,在多线程部分,详细介绍了线程的概念、生命周期、多线程的线程安全、线程通信、线程同步,并对synchronized和Lock锁;反射部分对反射的特性、功能、优缺点、适用场景等进行了介绍。
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)
|
16天前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
79 6
【Java学习】多线程&JUC万字超详解
|
10天前
|
Java 调度 开发者
Java并发编程:深入理解线程池
在Java的世界中,线程池是提升应用性能、实现高效并发处理的关键工具。本文将深入浅出地介绍线程池的核心概念、工作原理以及如何在实际应用中有效利用线程池来优化资源管理和任务调度。通过本文的学习,读者能够掌握线程池的基本使用技巧,并理解其背后的设计哲学。
|
9天前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
11天前
|
缓存 监控 Java
java中线程池的使用
java中线程池的使用
|
11天前
|
算法 Java 数据处理
Java并发编程:解锁多线程的力量
在Java的世界里,掌握并发编程是提升应用性能和响应能力的关键。本文将深入浅出地探讨如何利用Java的多线程特性来优化程序执行效率,从基础的线程创建到高级的并发工具类使用,带领读者一步步解锁Java并发编程的奥秘。你将学习到如何避免常见的并发陷阱,并实际应用这些知识来解决现实世界的问题。让我们一起开启高效编码的旅程吧!