Java 多线程(一) Thread API 基础

素颜马尾好姑娘i 2022-03-06 06:40 300阅读 0赞

前言

Java自开发之初就具有多线程多特性,其于JDK1.5又增添了java.util.concurrent内增添了非常多的多线程组件.于本章之中,我们优先总结下在Java初期,我们经常使用的Java API. 其中,虽然stop()等方法已经废弃,我们仍然将其提出,并且提出其优/缺点.

Java中主要的API有如下几部分内容:

  • 线程的创建 - Thread 类 与 Runnable接口
  • 线程的启动 - start()方法与run()方法
  • 线程的睡眠 - sleep()方法
  • 线程的停止 - stop()与interrupted()
  • 线程的暂停 - suspend()方法与resume()方法
  • 线程的放弃 - yield()方法
  • 线程的优先级与执行顺序 - setPriority()方法
  • 守护线程 - setDaemeon()方法
  • 其他API - Thread.currentThread() / isAlive() / getId() / getName()

本章中引用的示例都可以在如下的项目内找到
https://github.com/SeanYanxml/arsenal/tree/master/arsenal-java/arsenal-multithreading
如果你觉得本文或项目写的不错的话, 不妨给我一个Star


正文

线程的创建

线程的创建,我们主要有两种方式:

  • 继承Thread类
  • 实现Runnable方法

    Thread

    class StartThread extends Thread{

    1. public void run(){
    2. }

    }
    public void static void main(String []args){

    1. StartThread thread = new StartThread();
    2. thread.start();

    }

    Runnable

    class StartRunnabelThread implements Runnabel{

    1. public void run(){
    2. }

    }
    public void static void main(String []args){

    1. StartThread thread = new StartThread();
    2. thread.start();

    }

注意: 在Thread的构造函数内有Thread(Runnable)
在这里插入图片描述
我们有时可以这样创建,这样的结果就是其他线程调用某一个线程的run()方法.

  1. Thread threadA = new Thread();
  2. Thread threadB = new Thread(threadA);
  3. Thread threadC = new Thread(threadA);
  4. @Override
  5. public void run() {
  6. if (target != null) {
  7. target.run();
  8. }
  9. }
线程的启动

我们主要使用start()方法与run(). 其中run()方法是在当前进程内进行运行线程; start()方法是启动一个子线程进行运行.

  1. # Thread
  2. class StartThread extends Thread{
  3. public void run(){
  4. for(int i=0;i<10;i++){
  5. System.out.print(i);
  6. }
  7. }
  8. }
  9. public void static void main(String []args){
  10. // 写法1
  11. StartThread thread = new StartThread();
  12. thread.start();
  13. System.out.println("END.");
  14. // 写法2
  15. StartThread thread = new StartThread();
  16. thread.run();
  17. System.out.println("END.");
  18. }

方法1中输出为12345678910END (顺序执行.)
方法2的输出为123END45678910(END输出的先后是随机的.)
我们可以使用Thread.currentThread.getName()的接口进行查看,可以发现方法1中为Main,而方法二为Thread-0.因为run()方法为当前线程执行,而start()方法为启动子线程进行执行.

线程的睡眠

线程的睡眠由sleep(mills)方法进行启动.值得注意的是,sleep()方法不会释放当前对象的锁.并且,在线程睡眠时对线程进行停止stop()interrupted()方法,会抛出InterruptedException.所以,经常需要throws或者try-cath代码块内.

  1. try{
  2. Thread.sleep(1000);
  3. }catch(InterruptedException e){
  4. e.printStackTrace();
  5. }
线程的停止 - stop()与interrupted()

使用stop()方法能够直接停止线程.

  1. class SeanThreadDeathExceptionThread extends Thread{
  2. public void run() {
  3. try {
  4. this.stop();
  5. } catch (ThreadDeath e) {
  6. System.out.println("进入Catch方法");
  7. e.printStackTrace();
  8. }
  9. }
  10. }
  11. public static void main(String[] args) throws InterruptedException {
  12. Thread thread2 = new SeanThreadDeathExceptionThread();
  13. thread2.start();
  14. }

但是,当在线程sleep()的时候进行停止,会抛出InterruptedException异常.
此外,当线程stop()之后,是会释放线程的锁.但是,有时会直接停止线程的运行,逻辑立即停止,造成一些数据的错乱.

  1. class SyncStopThread extends Thread{
  2. private String username = "a";
  3. private String password = "aa";
  4. synchronized public void run(){
  5. username = "b";
  6. try {
  7. Thread.sleep(10000);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. password = "b";
  12. }
  13. }
  14. public static void main(String[] args) throws InterruptedException {
  15. SyncStopThread thread = new SyncStopThread();
  16. thread.run();
  17. try {
  18. Thread.sleep(2000);
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. thread.stop();
  23. System.out.println("username:"+thread.username+" password:"+password);
  24. }

输出为username:b password: aa,因为线程在运行到一半的时候停止了.

线程的停止 - interrupted()方法
关于停止的方法有interrpued()isInterrupted()方法两种.

  • 其中interrupted()方法为线程Thread的static静态的方法,意指当前线程的是否停止;public static boolean interrupted()
  • isInterrupted()的线程的Thread的dynamic动态的方法,意指thread类的线程是否停止.public boolean isInterrupted()
    其中具体的差距可以看下com.yanxml.multithreading.core.basic.stop.IsInterruptedThread这个例子.

    /**

    • 1-7-2 判断线程是否
    • */

    class SeanIsInterruptedThread extends Thread{

    1. public void run(){
    2. super.run();
    3. for(int i=0;i<100;i++){
    4. System.out.println("i = "+i);
    5. }
    6. }

    }

    public class IsInterruptedThread {

    1. // method test1
    2. // interrupted() 方法测试当前线程是否停止 当前线程为main线程从未停止
    3. public static void method1() throws InterruptedException{
    4. Thread thread = new SeanIsInterruptedThread();
    5. thread.start();
    6. Thread.sleep(1000);
    7. thread.interrupt();
    8. System.out.println("是否停止1:"+thread.interrupted());
    9. System.out.println("是否停止2:"+thread.interrupted());
    10. }
    11. // method test2
    12. // interrupted() 方法测试当前线程是否停止 当前线程为main线程停止 第一次停止后 标示位重置 为false
    13. public static void method2(){
    14. Thread.currentThread().interrupt();
    15. System.out.println("main 是否停止1:"+Thread.interrupted());
    16. System.out.println("main 是否停止2:"+Thread.interrupted());
    17. }
    18. // method test3
    19. public static void method3() throws InterruptedException{
    20. SeanIsInterruptedThread thread3 = new SeanIsInterruptedThread();
    21. thread3.start();

    // Thread.sleep(1000);

    1. thread3.interrupt();
    2. System.out.println("thread isInterrupted 是否停止1:"+thread3.isInterrupted());
    3. System.out.println("thread isInterrupted 是否停止2:"+thread3.isInterrupted());
    4. }
    5. public static void main(String[] args) throws InterruptedException {

    // method1();

    // method2();

    1. method3();
    2. }

    }

    // method3

    // 线程未结束时
    //thread isInterrupted 是否停止1:true
    //thread isInterrupted 是否停止2:true

    // 线程结束时
    //thread isInterrupted 是否停止1:false
    //thread isInterrupted 是否停止2:false

常见的停止线程写法

  1. # 写法1
  2. class InterruptedThread extends Thread{
  3. while(true){
  4. if(this.isInterrupted()){
  5. break;
  6. // throw new InterruptedException
  7. return;
  8. }
  9. // 业务逻辑
  10. }
  11. }

有时,我们还可以使用一个volatile关键字修饰的变量进行辅助控制.

  1. # 写法2
  2. class InterruptedThread extends Thread{
  3. public volatile boolean on = true;
  4. public void run(){
  5. while(on & !this.isInterrupted()){
  6. // 业务逻辑
  7. }
  8. }
  9. }
  10. public class Main{
  11. public static void main(String[] args) throws InterruptedException {
  12. InterruptedThread thread = new InterruptedThread();
  13. thread.start();
  14. Thread.sleep(1000);
  15. // 停止方法1
  16. thread.interrupt();
  17. // 停止方法2
  18. // thread.on = false;
  19. }
  20. }

线程的暂停 - suspend()方法与resume()方法
我们一般暂停线程,主要是使用sleep()方法.之前仍然有过时的suspend()和resume()方法.其中suspend()方法并未释放线程的锁.此外,当逻辑尚未运行结束后,直接运行有时会造成脏读问题.

  1. class User{
  2. private String username = "a";
  3. private String password = "aa";
  4. }
  5. class SuspendThread extends Thread{
  6. private User user;
  7. public SuspendThread(User user){
  8. this.user = user;
  9. }
  10. public void run(){
  11. username = "b";
  12. try {
  13. this.suspend(10000);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. password = "bb";
  18. }
  19. }
  20. public static void main(String[] args) throws InterruptedException {
  21. final User user = new User();
  22. SuspendThread thread = new SuspendThread(user);
  23. thread.run();
  24. try {
  25. Thread.sleep(2000);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. Thread printThread = new Thread(
  30. new Runnable(){
  31. public void run(){
  32. System.out.println("username:"+user.username+" password:"+user.password);
  33. }
  34. }
  35. );
  36. }

此处的输出应当为username:b password:aa,因为此处在suspend()
运行的时候进行输出,造成了一定的逻辑混乱.

线程的放弃 - yield()方法

线程的yield()方法,意指当前线程放弃执行.剩余的几个线程重新争抢.有可能当前线程刚刚放弃,就又抢到了线程的执行.

  1. /**
  2. * 1-9 yield方法
  3. * (放弃当前CPU资源.放弃时间不稳定,有可能刚刚放弃CPU资源,就又获得了CPU的资源.)
  4. * */
  5. class SeanYieldThread extends Thread{
  6. public void run(){
  7. long beginTime = System.currentTimeMillis();
  8. int count = 0;
  9. for(int i=0;i<10000;i++){
  10. // Thread.yield();
  11. count += 1;
  12. }
  13. long endTime = System.currentTimeMillis();
  14. System.out.println("ALL Time: " + (endTime-beginTime));
  15. }
  16. }
  17. public class YieldThread {
  18. public static void main(String[] args) {
  19. Thread thread = new SeanYieldThread();
  20. thread.start();
  21. }
  22. }
  23. // 非yield
  24. //ALL Time: 0
  25. // Thread.yield()
  26. //ALL Time: 11
线程的优先级与执行顺序 - setPriority()方法

线程的优先级为1-10,默认值为5.常见的线程优先级为

  • Thread.MAX_PRIORITY //10
  • Thread.MIN_PRIORITY // 1
  • Thread.NORM_PRIORITY //5

此外,线程的优先级具有如下的几个性质:

  • (1). 线程的优先级和执行顺序不是一回事.
  • (2). 优先级高的线程普遍比优先级低的线程先执行完毕.
  • (3). 线程的优先级具有继承性.(A线程启动B线程 AB线程优先级一样)
守护线程 - setDaemeon()方法

有时,我们需要启动守护线程.我们可以通过thread.setDaemeon(true)开启.默认为false. Main方法就是一个典型的守护线程,它会在所有线程结束后进行停止.

守护线程与普通线程的区别在于.普通线程停止全部后,JVM才会停止.如果这个线程是守护线程,JVM会忽视守护线程.
守护线程与普通线程

说到守护线程还有一个问题是守护进程.守护进程在Linux系统内部.后台运行进程不等于守护进程.守护进程通常是带d结尾的,我们通常使用的ssh远程登陆的sshd就是守护进程.
什么是守护进程
守护进程的作用
后台进程不等于守护进程
解析Linux后台进程与守护进程的区别

其他API - Thread.currentThread() / isAlive() / getId() / getName()
  • Thread.currentThread()
    获取当前运行的线程.Thread.currentThread().getName()this.currentThread().getName()的区别在于,this.currentThread始终指向的是创建者线程.
    this.getName()Thread.currentThread().getName()有时可能不同,因为,有时创建者的run()方法被其他线程调用了.

    package com.yanxml.multithreading.core.basic.current;

    /**

    • 显示currentThread()方法。
    • 主要描述 Thread.currentThread.getName() 与 this.getName()
    • thread.run() 和 thread.start()方法的区别。
    • */
      public class CurrentThreadDemo {
      // 例A
      public static void demoMethodA(){

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

      }

      // 例B
      public static void demoMethodB(){

      1. Thread threadB = new SeanCurrentThread();
      2. threadB.start();

      // threadB.run();
      }

      // 例C
      public static void demoMethodC(){

      1. Thread threadC = new SeanCurrentThreadExt();

      // threadC.start();
      // threadC.run();

      1. Thread threadCExt = new Thread(threadC);

      // threadCExt.start();

      1. threadCExt.run();

      }

      public static void main(String[] args) {

      1. // A Method.

      // demoMethodA();

      1. // B Method.

      // demoMethodB();

      1. // C Method.
      2. demoMethodC();

      }
      }

    class SeanCurrentThread extends Thread{

    1. public SeanCurrentThread(){
    2. System.out.println("构造方法打印: "+Thread.currentThread().getName());
    3. }
    4. public void run(){
    5. System.out.println("运行方法打印: "+Thread.currentThread().getName());
    6. }

    }

    class SeanCurrentThreadExt extends Thread{

    1. public SeanCurrentThreadExt(){
    2. // this.getName()这边的this指什么?
    3. System.out.println("SeanCurrentThreadExt 构造 current:" + Thread.currentThread().getName());
    4. System.out.println("SeanCurrentThreadExt this current:" + this.currentThread().getName());
    5. System.out.println("SeanCurrentThreadExt 构造 this:" + this.getName());
    6. }
    7. public void run(){
    8. System.out.println("SeanCurrentThreadExt run current:" + Thread.currentThread().getName());
    9. System.out.println("SeanCurrentThreadExt this current:" + this.currentThread().getName());
    10. System.out.println("SeanCurrentThreadExt run this:" + this.getName());
    11. }

    }

    // 输出:

    // case-1: main

    // case - 2-1: start()
    // 构造方法打印: main
    // 运行方法打印: Thread-0

    // case - 2-2: run()
    //构造方法打印: main
    //运行方法打印: main

    // case - 3-1: threadC.start()
    //SeanCurrentThreadExt 构造 current:main
    //SeanCurrentThreadExt 构造 this:Thread-0
    //SeanCurrentThreadExt run current:Thread-0
    //SeanCurrentThreadExt run this:Thread-0

    //case - 3-2: threadC.run()
    //SeanCurrentThreadExt 构造 current:main
    //SeanCurrentThreadExt 构造 this:Thread-0
    //SeanCurrentThreadExt run current:main
    //SeanCurrentThreadExt run this:Thread-0

    //case - 3-3 threadCExt.start()
    //SeanCurrentThreadExt 构造 current:main
    //SeanCurrentThreadExt 构造 this:Thread-0
    //SeanCurrentThreadExt run current:Thread-1
    //SeanCurrentThreadExt run this:Thread-0

    //case - 3-4 threadCExt.run()
    //SeanCurrentThreadExt 构造 current:main
    //SeanCurrentThreadExt 构造 this:Thread-0
    //SeanCurrentThreadExt run current:main
    //SeanCurrentThreadExt run this:Thread-0

    // 结论1 : this.getName() 在创建时已经被指定了。
    // 结论2 : start()为另启一个线程进行执行;run()为当前main()方法所在线程进行执行。

  • isAlive()
    判断线程是否存活

  • getId()
    获取线程ID,Main主线程为1
  • getName()
    获取线程名称. 另外,可以通过setName()对线程名称进行赋值.

后记

发现在使用Thread API的过程中,我们更加应该重视下Thread的多个线程之间的状态转换. 具体的状态转换模式如下所示.(根据大学所学的经验,我们可以知道.注意包括: 三态模型 / 五态模型)

在这里插入图片描述
Thread详解


Reference

[1]. Java 多线程编程核心技术
[2]. Java并发编程的艺术
[3]. JavaTM Platform Standard Ed. 6
[4]. 当用Thread的子类构造创建多线程时,并同时传入Runnable覆盖run方法时,此时执行的run方法是
[5] 线程的几种状态转换

发表评论

表情:
评论列表 (有 0 条评论,300人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java Thread线

    Thread 学习线程之前先要了解线程与进程的区别。 我们的计算机中的一个程序就是一个进程,一个程序中有很多个线程去完成各种各样的任务。 用一个生活的例子来说,一条商业街