【多线程与高并发】2、线程的启动方式

时间:2020-12-26 01:28:31

1、继承Thread类,重写run()方法

public class Thread01 {
    public static void main(String[] args) throws InterruptedException {
        /**
         * 1、请编写一个程序,开启一个线程,该线程每隔一秒,在控制台输出“ hello,world!”
         */
        //创建一个Cat对象,当做线程使用
        Cat cat = new Cat();
        cat.start();
        //说明:当main线程启动一个子线程时,也就是Thread-0子线程,主线程不会阻塞,会继续执行下面的方法
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程:" + Thread.currentThread().getName() + " 正在打印: 我是主线程");
            Thread.sleep(1000);
        }
    }
}
//继承 Thread 类
//1、当一个类继承了Thread,该类就可以当做线程使用
//2、一般我们会重写run方法,写上自己的逻辑
//3、run Thread类 实现了 Runnable 接口的run方法
class Cat extends Thread{
    int time = 0;
    //重写run方法
    @Override
    public void run() {
        while (time < 10){
            //线程名称
            System.out.println("线程名称:=====>"+Thread.currentThread().getName() + " 正在打印:====> hello world!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            time++;
        }
    }
}

2、实现Runable接口方式

1.创建一个实现了Runable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()

ublic class ThreadDemo01 {
	    
	    public static  void main(String[] args){
	        window1 w = new window1();
	        
	        //虽然有三个线程,但是只有一个窗口类实现的Runnable方法,
            //由于三个线程共用一个window对象,所以自动共用100张票
	        
	        Thread t1=new Thread(w);
	        Thread t2=new Thread(w);
	        Thread t3=new Thread(w);
	
	        t1.setName("窗口1");
	        t2.setName("窗口2");
	        t3.setName("窗口3");
	        
	        t1.start();
	        t2.start();
	        t3.start();
	    }
	}
	
	class window1 implements Runnable{
	    
	    private int ticket = 100;
	
	    @Override
	    public void run() {
	        while(true){
	            if(ticket>0){
	//                try {
	//                    sleep(100);
	//                } catch (InterruptedException e) {
	//                    e.printStackTrace();
	//                }
	                System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
	                ticket--;
	            }else{
	                break;
	            }
	        }
	    }
	}

比较创建线程的两种方式:
开发中,优先选择实现Runable接口的方式
原因:

1:实现的方式没有类的单继承性的局限性
2:实现的方式更适合用来处理多个线程有共享数据的情况
联系:Thread也是实现自Runable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中

3.新增的两种创建多线程方式

3.1 实现callable接口方式

与使用runnable方式相比,callable功能更强大些:

  • runnable重写的run方法不如callaalbe的call方法强大,call方法可以有返回值
  • 方法可以抛出异常
  • 支持泛型的返回值
  • 需要借助FutureTask类,比如获取返回结果
object ThreadTest {
  def main(args: Array[String]): Unit = {

    val numCallable = new NumCallable

    val futureTask = new FutureTask[Int](numCallable)

    val t1 = new Thread(futureTask)
    t1.setName("线程1")
    t1.start()

    try {
      //get返回值即为FutureTask构造器参数callable实现类重写的call的返回值
      val sum = futureTask.get()
      println(Thread.currentThread().getName() + ":" + sum);
    } catch {
      case e1: ExecutionException => {
        e1.printStackTrace()
      }
      case e2: InterruptedException => {
        e2.printStackTrace()
      }
      case e: Throwable => {
        e.printStackTrace()
      }
    }

  }
}

class NumCallable extends Callable[Int] {

  private var sum: Int = 0

  override def call(): Int = {

    for (i <- 0 to 100 if i % 2 == 0) {
      println(Thread.currentThread().getName() + ":" + i)
      sum += i
    }

    sum

  }
}

3.2 使用线程池的方式

后续会在专门的线程池专题讲解

3.3 Runnable接口和Callable接口的区别

  • Runnable接口中的唯一抽象方法run()方法没有返回值,Callable接口中的唯一抽象方法call()方法有返回值;
  • Runnable接口的run()方法没有受检异常的异常声明,即异常只能在内部捕获,不能继续上抛, Callable接口的call()方法声明了受检异常,可直接抛出Exception异常,并且可以不予捕获;
  • Callable实例不能和Runnable实例一样,直接作为Thread线程实例的target来使用;
  • 异步执行任务在大多数情况下是通过线程池去提交的,而Runnable接口和Callable接口都可以应用于线程池;
  • Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!

注意:异步执行任务在大多数情况下是通过线程池去提交的,而很少通过创建一个新的线程去提交(即通过Thread类的方式执行start()方法)。