关于多线程创建的几个问题

多线程编程是一个优秀程序员必备的能力,多线程是解决项目中性能问题的一个重要技术利器,现在的计算机基本都是多核处理器,使用多线程编程可以大大提高处理器的使用效率,提升系统的吞吐率。

1

线程的创建

01、方式一:继承Thread类

继承Thread类创建多线程的步骤:

1、定义子类,继承Thread类;

2、子类中重写Thread类中的run方法;

3、创建Thread子类对象,也就是创建线程对象;

4、调用线程对象的start方法启动线程。

示例:使用继承Thread类的方式创建一个线程,实现从1输出到5。

//定义线程类Task继承Thread类
public class Task extends Thread {


  // 子类中重写Thread类中的run方法
  public void run() {
    for (int i = 1; i <= 5; i++) {
      System.out.println("线程输出:" + i);
    }
  }
}


//测试类
public class TaskTest {


  public static void main(String[] args) {
    // 创建线程对象
    Task task = new Task();
    // 调用线程对象的start方法启动线程
    task.start();
  }
}


程序运行结果:
线程输出:1
线程输出:2
线程输出:3
线程输出:4
线程输出:5

02、方式二:实现Runnable接口

实现Runnable接口创建多线程的步骤:

1、定义子类,实现Runnable接口;

2、子类中重写Runnable接口中的run方法;

3、创建Runnable接口的子类对象;

4、通过Thread类的构造器创建线程对象;

5、调用Thread类的start方法启动线程。

示例:使用实现Runnable接口的方式创建一个线程,实现从1输出到5。

//定义线程类Task1实现Runnable接口
public class Task1 implements Runnable {


  @Override
  //子类中重写Runnable接口中的run方法
  public void run() {
    for (int i = 1; i <= 5; i++) {
      System.out.println("线程输出:" + i);
    }
  }


}


//测试类
public class Task1Test {


  public static void main(String[] args) {
    // 创建Runnable接口的子类对象
    Task1 task = new Task1();
    // 通过Thread类创建线程对象
    // 将Runnable接口的子类对象作为参数传递给Thread类的构造方法
    Thread taskThread = new Thread(task);
    // 调用Thread类的start方法启动线程
    taskThread.start();
  }
}


程序运行结果:
线程输出:1
线程输出:2
线程输出:3
线程输出:4
线程输出:5

03、方式三:通过Callable和Future创建线程

通过Callable和Future创建多线程的步骤:

1、定义子类,实现Callable接口,带有返回值;

2、子类中重写Callable接口中的call方法;

3、创建Callable接口的子类对象;

4、使用FutureTask类来包装Callable对象

5、通过Thread类的构造器创建线程对象,使用FutureTask象作为Thread对象的 target创建;

6、调用Thread类的start方法启动线程;

7、调用FutureTask对象的get方法来获得线程执行结束后的返回值;

示例:使用Callable和Future的方式创建一个线程,实现从1输出到5。

import java.util.concurrent.Callable;


//定义线程类Task2实现Callable接口,带有Integer返回值
public class Task2 implements Callable {


  @Override
  // 子类中重写Callable接口中的call方法
  public Integer call() throws Exception {
    Integer num = 0;
    for (int i = 1; i <= 5; i++) {
      System.out.println("线程输出:" + i);
      num = i;
    }
    return num;
  }
}


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;


// 测试类
public class Task2Test {


  public static void main(String[] args) {
    // 创建Callable接口的子类对象
    Callable task2 = new Task2();
    // 使用FutureTask类来包装Callable对象
    FutureTask task = new FutureTask(task2);
    // 通过Thread类的构造器创建线程对象
    // 使用FutureTask象作为Thread对象的 target创建
    Thread taskThread = new Thread(task);
    // 调用Thread类的start方法启动线程
    taskThread.start();
    try {
      // 输出线程执行后的返回值
      System.out.println("线程执行后的返回值:" + task.get());
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
  }
}


程序运行结果:
线程输出:1
线程输出:2
线程输出:3
线程输出:4
线程输出:5
线程执行后的返回值:5

2

设置线程名称

01、获取线程的名称

通过Thread.currentThread().getName()来获取线程名称。

public class Task3 extends Thread {


  public void run() {
    for (int i = 1; i <= 5; i++) {
      System.out.println(Thread.currentThread().getName() + ":" + i);
    }
  }
}


// 测试类
public class Task3Test {


  public static void main(String[] args) {
    Task3 task = new Task3();
    task.start();
  }
}


程序运行结果:
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-0:5

从上面的运行结果可以看出,在没有指定线程名称的时候,线程的名称默认是:Thread-0。

02、设置线程的名称

通过Thread类的构造函数,可以设置线程的名称。

示例1:通过继承Thread类创建线程类Car,创建3辆宝马车、4辆奔驰车。

public class Car extends Thread {


  private int number;


  public Car(String name, int number) {
    super(name); // 通过父类的构造函数设置线程的名称
    this.number = number;
  }


  public void run() {
    for (int i = 1; i <= number; i++) {
      System.out.println(Thread.currentThread().getName() + "启动,number="
          + i);
    }
  }
}


// 测试类
public class CarTest {


  public static void main(String[] args) {
    Car bmw = new Car("宝马", 3);
    Car benz = new Car("奔驰", 4);
    bmw.start();
    benz.start();
  }
}


程序运行结果(程序运行的结果可能有多个):
奔驰启动,number=1
宝马启动,number=1
奔驰启动,number=2
宝马启动,number=2
宝马启动,number=3
奔驰启动,number=3
奔驰启动,number=4


以上程序创建了两个Car的线程对象,程序每次执行的结果可能不一样,这就是多线程的作用,运行结果取决于哪个线程抢到了CPU。

示例2:通过实现Runnable接口创建线程类Dog,哈士奇吼叫3声、萨摩耶吼叫4声。

public class Dog implements Runnable {


  private int number; // 吼叫次数


  public Dog(int number) {
    this.number = number;
  }


  @Override
  public void run() {
    for (int i = 1; i <= number; i++) {
      System.out.println(Thread.currentThread().getName() + "吼叫,i=" + i);
    }
  }
}


// 测试类
public class DogTest {


  public static void main(String[] args) {
    Dog dog1 = new Dog(3);
    Dog dog2 = new Dog(4);
    // 通过Thread类的构造函数设置线程名称
    Thread t1 = new Thread(dog1, "哈士奇");
    Thread t2 = new Thread(dog2, "萨摩耶");
    t1.start();
    t2.start();
  }
}


程序运行结果(程序运行的结果可能有多个):
萨摩耶吼叫,number=1
哈士奇吼叫,number=1
哈士奇吼叫,number=2
哈士奇吼叫,number=3
萨摩耶吼叫,number=2
萨摩耶吼叫,number=3
萨摩耶吼叫,number=4

以上程序创建了两个Dog的线程对象,通过Thread类的构造函数设置线程名称。

3

线程的启动和停止


01、线程的启动需要调用Thread的start方法,而不是调用对象的run方法

示例:我们看下调用线程对象的run方法和调用Thread对象的start方法的区别,演示的线程类使用上面的Car类。

// 调用线程对象的run方法
public class CarTest {


  public static void main(String[] args) {
    Car bmw = new Car("宝马", 3);
    Car benz = new Car("奔驰", 4);
    bmw.run();
    benz.run();
  }
}


程序运行结果:
main启动,number=1
main启动,number=2
main启动,number=3
main启动,number=1
main启动,number=2
main启动,number=3
main启动,number=4


// 调用线程对象的start方法
public class CarTest {


  public static void main(String[] args) {
    Car bmw = new Car("宝马", 3);
    Car benz = new Car("奔驰", 4);
    bmw.start();
    benz.start();
  }
}


程序运行结果(程序运行的结果可能有多个):
宝马启动,number=1
奔驰启动,number=1
宝马启动,number=2
奔驰启动,number=2
宝马启动,number=3
奔驰启动,number=3
奔驰启动,number=4

从上面程序的运行结果可以看出,调用run方法其实没有启动多线程执行,相当于执行了对象的普通方法,线程是主线程(main);而真正能启动多线程的则是调用Thread类对象的start方法。

02、调用Thread类对象的start方法两次,是否可以启动两个线程

答案是不可以,因为start方法执行时,会判断线程的状态,当线程启动后再次调用start方法则会抛出IllegalThreadStateException异常,这是运行时异常,在代码编写的时候调用两次start方法,编译是没有问题的。

示例1:用同一个线程对象调用两次start方法,演示的线程类使用上面的Car类。

public class CarTest {


  public static void main(String[] args) {
    Car bmw = new Car("宝马", 3);
    bmw.start();
    bmw.start();  // 运行时异常,不应该这么调用
  }
}


程序运行结果:
Exception in thread "main" 宝马启动,number=1
宝马启动,number=2
宝马启动,number=3
java.lang.IllegalThreadStateException
  at java.lang.Thread.start(Unknown Source)
  at testThread.CarTest.main(CarTest.java:8)

示例2:调用多个Thread子类对象的start方法是创建多个线程的正确方式,演示的线程类使用上面的Car类和Dog类。

// 继承Thread类的线程创建2个线程的方式
public class CarTest {


  public static void main(String[] args) {
    Car bmw = new Car("宝马", 3);
    Car benz = new Car("奔驰", 4);
    bmw.start();
    benz.start();
  }
}


// 实现实现Runnable接口的线程创建2个线程的方式
public class DogTest {


  public static void main(String[] args) {
    Dog dog1 = new Dog(3);
    Dog dog2 = new Dog(4);
    // 通过Thread类的构造函数设置线程名称
    Thread t1 = new Thread(dog1, "哈士奇");
    Thread t2 = new Thread(dog2, "萨摩耶");
    t1.start();
    t2.start();
  }
}

大家不要觉得这个知识点没什么,我见过工作三年多的程序员现场写代码,想启动两个线程,就用了同一个线程对象调用两次start方法。

03、线程的停止

我们可以通过一个布尔变量来控制线程的停止。

示例:通过改变布尔变量的值使线程停止

public class StopThread implements Runnable {
  private boolean flag = true; // 控制线程停止的变量,初始为true
  private int i = 0;


  @Override
  public void run() {
    while (this.flag) {  // flag为true时,一直输出i的值
      System.out.println(i++);
    }
  }


  void stop() {
    this.flag = false;  // 修改布尔变量的值为false,让while循环停止
  }
}


// 测试类
public class StopThreadTest {


  public static void main(String[] args) throws InterruptedException {
    StopThread mt = new StopThread();
    new Thread(mt).start();
    Thread.sleep(1); // 主线程休眠,可以更好的观察子线程停止的效果
    mt.stop();
  }
}


程序运行结果:
0
1

从上面的程序运行结果来看,线程启动后,通过stop方法修改布尔变量的值为false,从而达到线程run方法里while循环的退出,进而使线程停止运行。

以上内容对多线程的基本知识进行了讲解,希望大家可以熟练掌握。

追梦人

知识指导行动,行动决定命运。

长按二维码关注追梦人

1、设计模式概述

2、设计模式之单例模式

3、设计模式之工厂模式

4、设计模式之代理模式

5、设计模式之模板模式

6、设计模式之策略模式

7、设计模式之观察者模式

8、设计模式之原型模式

9、面向对象设计的七大原则

10、入职新公司,如何快速凸显个人价值

11、Java 开发分享

12、Java 开发经验分享

13、反向代理介绍,Nginx在分布式架构中的作用

14、Nginx的配置文件介绍

15、Nginx 的反向代理演示

16、Nginx 负载均衡的演示

17、Nginx 的动静分离演示

18、Nginx 解决跨域访问问题演示

19、Nginx 的防盗链配置演示

20、Java 异常处理从入门到实战

21、Redis从入门到实战

22、半小时学会正则表达式(上)

23、半小时学会正则表达式(下)

右下角点个在看,少个bug ????

你可能感兴趣的:(关于多线程创建的几个问题)