看完这篇文章你还敢说不知道多线程是什么?

1、我们为什么要学习多线程:

  1. 为了面试打基础、做准备。
  2. 成为互联网精英!多线程这项技能是必会的

2、什么多线程

	我理解的就是将一个复杂的任务分解成多个不同的可以独立执行的小任务之后再一起汇总结果。

3、案例场景(生活中)

讲述的是我和同学一起吃饭的场景:在这里插入图片描述
1、其实在生活中有许多这样的多线程的场景、比如去银行办理业务多窗口办理业务、买票、就医等等许多场景都是多线程的好例子,以上的例子我和老六一起点完餐,厨师的做饭过程(切墩儿准备食材的时候主厨就可以起锅煮馄饨,服务员服务我们这些顾客)
2、假如没有分工,而是一个人干所有的工作,那么在服务我们得时候其他顾客得等我们进行一系列的流程(点餐、吃饭、等等);那后面的人等我们吃完的时候都不饿了(效率极其低下)
3、但是通过分工,多人协作,餐厅的工作才能高效运转起来。我们开发的程序也是如此,如果你所有的工作都在一个线程里,那么首先这段主逻辑会相当复杂,而且难于维护和扩展,另外相信效率也会相对低下。如果我们的程序通过多线程 + 缓冲的方式,把不同步骤解耦,那么将大大提高效率。

4、从源码来学习多线程

JavaDoc中介绍的多线程

  • A thread is a thread of execution in a program. The Java Virtual Machine allows an application to have multiple threads of execution
    running concurrently.
    词义:线程就是程序中执行的线程。java虚拟机允许一个应用有多个线程同时执行。

  • Every thread has a priority. Threads with higher priority are executed in preference to threads with lower priority. Each thread may or may not also be marked as a daemon.

  • 词义:每个线程都有一个优先级。高优先级的线程在低优先级的线程执行前执行。每个前程有可能或者不可能被标记为一个守护线程。

  • When code running in some thread creates a new Thread object,the new thread has its priority initially set equal to the priorityof the creating thread, and is a daemon thread if and only if the creating thread is a daemon.

  • 词义:当运行某个线程的代码,创建了一个新的线程对象时,该新创建的线程的优先级和创建线程的优先级保持一样,并且新线程是否是一个守护线程看创建该线程的线程是否是一个守护线程

  • When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named
    main of some designated class.
    词义:当JVM启动的时候,通常有一个非守护线程(指定调用指定类的main的方法

  • The Java Virtual Machine continues to execute threads until either of the following occurs:
    (词义:JVM 持续执行线程直到出现下面的情况
    1、The exit method of class Runtime has been called and the security manager has permitted
    the exit operation to take place.
    已经调用了Runtime类的exit方法,并且安全管理器已经允许进行退出操作。
    2、All threads that are not daemon threads have died, either by returning from the call to the run method or by throwing an exception that propagates beyond the run
    所有不是守护进程线程的线程都已经死亡,无论是从调用返回到run方法或者抛出不属于run方法的run

  • There are two ways to create a new thread of execution.
    有两种方式去创建一个可以执行的线程
    One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread.
    1、一种是声明一个Thread的字类。这个字类应该去重写Thread的 run 方法,如下示例:

package threadBase;
public class CreateThread_1 extends Thread {
    
    
  private String threadName;
  /** 初始个数 */
  public static int count = 1;
  /** 最大数量 */
  private int max_count = 100;
  /** 赋予线程的名称方法 */
  public CreateThread_1(String threadName) {
    
    
    super(threadName);
    this.threadName = threadName;
  }
/**
   * 主动执行的run方法所有的业务逻辑代码都在这个里面
   */
  @Override
  public void run() {
    
    
    while (count < max_count) {
    
    
      System.out.println(threadName + " ___first create Thread .... " + count);
      count++;
    }
  }
}
public class Thread_0508 {
    
    
  public static void main(String[] args) {
    
    
    /**线程的第一种创建方式并且调用*/
    CreateThread_1 thread_1 = new CreateThread_1("thread_01");
    CreateThread_1 thread_2 = new CreateThread_1("thread_02");
    thread_1.start();
    thread_2.start();
}

输出结果:
在这里插入图片描述
The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating thread, and started.
2、另一种方式创建一个线程先声明一个类,这个类要实现Runnable接口。该类也应该重写run方法。然后可以分配类的实例,在创建Thread时作为参数传递,并启动。如下示例:

package threadBase;

public class CreateThread_2 implements Runnable{
    
    

  private String threadName;
  /** 初始个数 */
  public static int count = 1;
  /** 最大数量 */
  private int max_count = 100;
  /** 赋予线程名称的方法 */
  public CreateThread_2(String threadName) {
    
    
    this.threadName = threadName;
  }
  /** 主动执行的run方法所有的业务逻辑代码都在这个里面 */
  @Override
  public void run() {
    
    
    while (count < max_count) {
    
    
      System.out.println(threadName + " ___second create Thread .... " + count);
      count++;
    }
  }
}

public class Thread_0508 {
    
    
  public static void main(String[] args) {
    
    
    /**
     * 注意:其实第二种创建线程的方式追究其根本还是第一种,只不过这样做的好处就是线程与具体的业务逻辑分开了
     */
    Thread thread_21 = new Thread(new CreateThread_2("thread_21"));
    thread_21.start();
    Thread thread_22 = new Thread(new CreateThread_2("thread_22"));
    thread_22.start();
   }
 }

输出结果:
在这里插入图片描述

  • Every thread has a name for identification purposes. More than one thread may have the same name. If a name is not specified when a thread is created, a new name is generated for it.
    词义:每个线程都有一个识别的名字,多个线程也许有相同的名字,当一个线程在创建的时候没有指定名字,会自动生成一个名字给它
  • Unless otherwise noted, passing a {@code null} argument to a constructor or method in this class will cause a {@link NullPointerException} to be thrown NullPointerException
    词义: 除非另有说明,否则将null参数传递给null中的构造函数或方法将导致抛出异常
public class Thread_0508 {
    
    
  public static void main(String[] args) {
    
    
    /** 测试线程名字问题*/
    Thread thread = new Thread(()->{
    
    
      System.out.println("我是新线程");
    });
    thread.start();
    System.out.println("Thread name ->"+thread.getName());
}

输出结果:
在这里插入图片描述

5、线程的8种构造方法

1、public Thread()
方法说明:分配一个新的Thread对象。 此构造具有相同的效果Thread (null, null, gname) ,其中gname是新生成的名字。 自动生成的名称格式为"Thread-" + n ,其中 n 为整数。如下示例:

public Thread() {
    
    
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
/** For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    
    
    return threadInitNumber++;
}
public class Thread_0508 {
    
    
  public static void main(String[] args) {
    
    
    /** 测试第一种构造方法*/
    Thread thread = new Thread();
    thread.start();
    System.out.println("Thread name -> "+thread.getName());
  }
}   

输出结果:
在这里插入图片描述
2、public Thread(Runnable target)
方法说明:分配一个新的Thread对象。 该构造函数具有与Thread (null, target, gname)相同的效果,其中gname是新生成的名称。 自动生成的名称格式为"Thread-"+ n ,其中n为整数。
参数 target - 启动此线程时调用其run方法的对象。 如果null ,这个类run方法什么都不做。

public Thread(Runnable target) {
    
    
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
@Override
public void run() {
    
    
    if (target != null) {
    
    
        target.run();
    }
}

public class Thread_0508 {
    
    
  public static void main(String[] args) {
    
    
    Thread thread = new Thread(()->{
    
    
      System.out.println("我是第二种构造方法");
    });
    thread.start();
    System.out.println("Thread name -> "+thread.getName());
  }
}  

输出结果:
在这里插入图片描述
3、public Thread(ThreadGroup group,Runnable target)
方法说明:分配一个新的Thread对象。 此构造具有相同的效果Thread (group, target, gname) ,其中gname是新生成的名字。 自动生成的名称格式为"Thread-"+ n ,其中n为整数。
group - 线程组。 如果是null并且有一个安全管理员,那么该组由SecurityManager.getThreadGroup()决定 。 如果没有安全管理员或SecurityManager.getThreadGroup()返回null ,该组将设置为当前线程的线程组。
target - 启动此线程时调用其run方法的对象。 如果null ,这个线程的run方法被调用。

public Thread(ThreadGroup group, Runnable target) {
    
    
    init(group, target, "Thread-" + nextThreadNum(), 0);
}

public class Thread_0508 {
    
    
  public static void main(String[] args) {
    
    
    ThreadGroup threadGroup = new ThreadGroup("new_threadGroup");
    Thread thread =new Thread(threadGroup,()->{
    
    
      System.out.println("threadGroup name -> "+threadGroup.getName());
    });
    thread.start();
    System.out.println("current thread threadGroup name -> "+thread.getThreadGroup().getName());
    System.out.println("current thread threadGroup parent threadGroup -> "+thread.getThreadGroup().getParent()); 
   }}

输出结果:
在这里插入图片描述
4、public Thread(String name)
方法说明:分配一个新的Thread对象。 此构造具有相同的效果Thread (null, null, name) 。
name - 新线程的名称

public Thread(String name) {
    
    
    init(null, null, name, 0);
}
public class Thread_0508 {
    
    
  public static void main(String[] args) {
    
    
    /**测试第四种构造方法*/
    Thread thread =new Thread("thread");
    thread.start();
    System.out.println("thread name -> "+ thread.getName());
  }
}

输出结果:
在这里插入图片描述
5、public Thread(ThreadGroup group, String name)
方法说明:分配一个新的Thread对象。 此构造具有相同的效果Thread (group, null, name) 。
group - 线程组。 如果是null并且有一个安全管理器,则该组由SecurityManager.getThreadGroup()决定 如果没有安全管理员或SecurityManager.getThreadGroup()返回null ,则该组将设置为当前线程的线程组。
name - 新线程的名称

public Thread(ThreadGroup group, String name) {
    
    
    init(group, null, name, 0);
}
public class Thread_0508 {
    
    
  public static void main(String[] args) {
    
    
    Thread thread =new Thread("thread");
    thread.start();
    System.out.println("thread name -> "+ thread.getName());
    System.out.println("threadGroup -> "+ thread.getThreadGroup());
    System.out.println("threadGroup name -> "+ thread.getThreadGroup().getName()); 
  }
}

输出结果:
在这里插入图片描述
6、public Thread(Runnable target,String name)
方法说明:分配一个新的Thread对象。 此构造具有相同的效果Thread (null, target, name) 。
target - 启动此线程时调用其run方法的对象。 如果null ,则调用此线程的run方法。
name - 新线程的名称

public Thread(Runnable target, String name) {
    
    
    init(null, target, name, 0);
}
public class Thread_0508 {
    
    
  public static void main(String[] args) {
    
    
    Thread thread =new Thread(()->{
    
    
      System.out.println("Runnable");
    },"thread");
    thread.start();
    System.out.println("thread name -> "+ thread.getName());
  }
 }

输出结果:
在这里插入图片描述
7、public Thread(ThreadGroup group,Runnable target,String name)
方法说明:分配一个新的Thread对象,使其具有target作为其运行对象,具有指定的name作为其名称,属于group引用的线程组。如果有安全管理器,则使用ThreadGroup作为参数调用其checkAccess方法。此外,它的checkPermission方法由RuntimePermission(“enableContextClassLoaderOverride”)权限调用,直接或间接地由覆盖getContextClassLoader或setContextClassLoader方法的子类的getContextClassLoader setContextClassLoader调用。新创建的线程的优先级设置为等于创建线程的优先级,即当前正在运行的线程。 可以使用方法setPriority将优先级改变为新值。当且仅当创建它的线程当前被标记为守护线程时,新创建的线程才被初始化为守护线程。 方法setDaemon可以用于改变线程是否是守护进程。
group - 线程组。 如果是null并且有一个安全管理器,则该组由SecurityManager.getThreadGroup()决定 。 如果没有安全管理员或SecurityManager.getThreadGroup()返回null ,该组将设置为当前线程的线程组。
target - 启动此线程时调用其run方法的对象。 如果null ,则调用此线程的run方法。
name - 新线程的名称

public Thread(ThreadGroup group, Runnable target, String name) {
    
    
    init(group, target, name, 0);
}
public class Thread_0508 {
    
    
  public static void main(String[] args) {
    
    
    ThreadGroup threadGroup = new ThreadGroup("new_threadGroup");
    Thread thread = new Thread(threadGroup,()->{
    
    
      System.out.println("Runnable");
    },"threadName");
    thread.start();
    System.out.println("thread name -> "+ thread.getName());
    System.out.println("threadGroup -> "+ thread.getThreadGroup());
    System.out.println("threadGroup name -> "+ thread.getThreadGroup().getName());
  }
}

输出结果:
在这里插入图片描述
8、public Thread(ThreadGroup group, Runnable target,String name,long stackSize) (不建议使用)
方法说明:分配一个新的Thread对象,以便它具有target作为其运行对象,将指定的name正如其名,以及属于该线程组由称作group ,并具有指定的堆栈大小 。这个构造函数与Thread(ThreadGroup,Runnable,String)相同,除了它允许指定线程栈大小的事实之外。 堆栈大小是虚拟机为该线程的堆栈分配的大致的地址空间字节数。 stackSize参数的影响(如果有的话)与平台有关。在某些平台上,指定了一个较高的值stackSize参数可以允许抛出一个前一个线程来实现更大的递归深度StackOverflowError 。 类似地,指定较低的值可能允许更多数量的线程同时存在,而不会抛出OutOfMemoryError (或其他内部错误)。 所述stackSize参数的值和最大递归深度和并发水平之间的关系的细节是依赖于平台的。 在某些平台上,该值stackSize参数可能没有任何效果。 虚拟机可以自由地对待stackSize参数作为建议。 如果平台的指定值不合理地低,虚拟机可能会改为使用一些平台特定的最小值; 如果指定的值不合理地高,虚拟机可能会使用一些平台特定的最大值。 同样,虚拟机可以自由地按照合适的方式向上或向下舍入指定的值(或完全忽略它)。对于指定的值为零stackSize参数将使这种构造的行为酷似Thread(ThreadGroup, Runnable, String)构造。由于此构造函数的行为依赖于平台依赖性质,因此在使用时应特别小心。 执行给定计算所需的线程栈大小可能会因JRE实现而异。 鉴于这种变化,可能需要仔细调整堆栈大小参数,并且可能需要对要运行应用程序的每个JRE实现重复调整。
group - 线程组。 如果null并且有一个安全管理器,该组由SecurityManager.getThreadGroup()确定 。 如果没有安全管理员或
target - 启动此线程时调用其run方法的对象。 如果null ,这个线程的run方法被调用。
name - 新线程的名称
stackSize - 新线程所需的堆栈大小,或为零表示此参数将被忽略。

public Thread(ThreadGroup group, Runnable target, String name,
              long stackSize) {
    
    
    init(group, target, name, stackSize);
}
public class Thread_0508 {
    
    
  public static void main(String[] args) {
    
    
    /**测试第八种构造方法*/
    ThreadGroup threadGroup = new ThreadGroup("new_threadGroup");
    Thread thread = new Thread(threadGroup,()->{
    
    
      try {
    
    
        Thread.sleep(10_000);
      } catch (InterruptedException e) {
    
    
        e.printStackTrace();
      }
      System.out.println("Runnable");
    },"threadName",0);
    thread.start();
    System.out.println("thread name -> "+ thread.getName());
    System.out.println("threadGroup -> "+ thread.getThreadGroup());
    System.out.println("threadGroup name -> "+ thread.getThreadGroup().getName());
  }
 }

输出结果:
在这里插入图片描述

6、总结

1、java应用程序的main函数是一个线程,是被JVM启动的时候调用,线程的名字叫做main

public class Thread_0508 {
    
    
  public static void main(String[] args) {
    
    
    System.out.println("thread name -> "+ Thread.currentThread().getName());
 }}

输出结果:
在这里插入图片描述
2、实现一个线程必须创建Thread实例,重写run方法,并且调用start方法启动
3、在JVM启动后,实际上有很多个线程,但是至少有一个非守护线程

public class Thread_0508 {
    
    
  public static void main(String[] args) {
    
    
    boolean daemon = Thread.currentThread().isDaemon();
    System.out.println("current Thread whether it is daemon -> "+daemon);
 }
}

输出结果:
在这里插入图片描述
4、当你调用一个线程start方法的时候此时至少有两个线程,一个是调用你的线程,还有一个就是被调用要执行的线程

public class Thread_0508 {
    
    
  public static void main(String[] args) {
    
    
    new Thread(()->{
    
    
      System.out.println("run ......");
    }).start();
    ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
    System.out.println("thread active num -> " + threadGroup.activeCount());
    // create thread array
    Thread[] threads =new Thread[threadGroup.activeCount()];
    // 将此线程组及其子组中的每个活动线程复制到指定的数组中
    threadGroup.enumerate(threads);
    Arrays.asList(threads).forEach(thread -> System.out.println("thread name -> " + thread.getName()));
 }
}

输出结果:
在这里插入图片描述
5、线程的生命周期分为创建、可运行、运行中、阻塞(等待阻塞、同步阻塞、其他阻塞)、死亡
6、创建线程对象的时候,默认会给定一个线程名称:Thread-开头,从0开始计数
7、如果在构造线程对象时未传入Runable或者没有复写Thread的run方法,该Thread将不会调用任何东西,如果传入了Runable的实例或者复写了Thread的run方法,则会执行该方法的逻辑代码
8、如果构造线程对象的时候未传入ThreadGroup,Thread会默认获取父线程的ThreadGroup作为该线程的 ThreadGroup,此时子线程和父线程将会在同一个ThreadGroup中
9、构造线程对象的时候如果没有传入stackSize,其代表着该线程占用的栈的大小,如果没有传入stackSize 则默认是0,0代表着忽略该参数

关注我,我会一直持续输出… 期待下一期的多线程的学习
与君共勉 …

猜你喜欢

转载自blog.csdn.net/weixin_38071259/article/details/106025190