小言_互联网的博客

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

261人阅读  评论(0)

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

 

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<Integer> {	

	
  @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<Integer> task2 = new Task2();	
    // 使用FutureTask类来包装Callable对象	
    FutureTask<Integer> task = new FutureTask<Integer>(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循环的退出,进而使线程停止运行。

 


转载:https://blog.csdn.net/z123456789XDW/article/details/101188522
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场