线程的四种创建方式(二)

线程的四种创建方式

第二节 线程的四种创建方式


前言

线程的创建主要有四种方式,如下:
1)继承Thread类创建线程
2)实现Runnable接口创建线程
3)使用Callable和Future创建线程
4)使用线程池创建线程


一、继承Thread类创建线程

创建线程的步骤:

  1. 定义子类继承Thread类。
  2. 子类中重写Thread类中的run方法。
  3. 创建Thread子类对象,即创建了线程对象。
  4. 调用线程对象start方法:启动线程,调用run方法。
//1) 定义子类继承Thread类。
public class ThreadTest1 extends Thread {
    //2) 子类中重写Thread类中的run方法。
    @Override
    public void run() {
    	//线程执行的内容
        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
    public static void main(String[] args) {
    	//3) 创建Thread子类对象,即创建了线程对象。
        ThreadA threadA = new ThreadA();
        ThreadA threadA1 = new ThreadA();
        //设置线程的名字
        threadA.setName("张三");
        threadA1.setName("李四");
        //4) 调用线程对象start方法
        //开始执行线程
        threadA.start();
        threadA1.start();
    }
}

注意:

  1. 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
  2. run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
  3. 想要启动多线程,必须调用start方法。

二、实现Runnable接口创建线程

创建线程的步骤:

  1. 定义子类,实现Runnable接口。
  2. 子类中重写Runnable接口中的run方法。
  3. 通过Thread类含参构造器创建线程对象。
  4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
  5. 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
//1) 定义子类,实现Runnable接口。
public class ThreadTest2 implements Runnable {
	//2) 子类中重写Runnable接口中的run方法。
    @Override
    public void run() {
        for (int i = 0; i <1000 ; i++) {
            System.out.println(Thread.currentThread().getName()+"---->"+i);
        }
    }
    public static void main(String[] args) {
        ThreadTest2 threadTest2 = new ThreadTest2();
        //3) 通过Thread类含参构造器创建线程对象。
        //4) 将Runnable接口的子类对象(threadTest2)作为实际参数传递给Thread类的构造器中。
        Thread thread1 = new Thread(threadTest2);
        Thread thread2 = new Thread(threadTest2);
        //设置名字
        thread1.setName("张三");
        thread2.setName("李四");
        //5) 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
        //启动线程
        thread1.start();
        thread2.start();
    }
}

1.继承方式和实现方式的联系与区别

区别

  1. 继承Thread:线程代码存放Thread子类run方法中。
  2. 实现Runnable:线程代码存在接口的子类的run方法中。实现Runnable方式的好处
  3. 避免了单继承的局限性
  4. 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。

三、实现Callable接口。 — JDK 5.0新增

  1. Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。
  2. ExecutorService:线程池类
  3. Future<T> submit(Callable<T> task):获取线程池中的某一个线程对象,并执行线程中的call()方法
  4. Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用
//1.创建一个实现Callable的实现类
public class ThreadTest3 implements Callable {
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        //该方法为打印100内每个偶数,同时返回的结果是100内所有偶数的总和
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }

    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        ThreadTest3 threadTest3 = new ThreadTest3();
        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(threadTest3);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();
        try {
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("总和为:"+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

1.与使用Runnable相比, Callable功能更强大些

  1. 相比run()方法,可以有返回值
  2. 方法可以抛出异常
  3. 支持泛型的返回值
  4. 需要借助FutureTask类,比如获取返回结果

四、使用线程池方式。 — JDK 5.0新增

线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。
线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

使用线程池的好处:

  1. 提高响应速度(减少了创建新线程的时间)
  2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  3. 便于线程管理
public class ThreadTest4 implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
    public static void main(String[] args) {
        ThreadTest4 t1 = new ThreadTest4();
        ThreadTest4 t2 = new ThreadTest4();
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor poolExecutor = (ThreadPoolExecutor)service;
        //设置线程池的属性
        //核心池的大小
        poolExecutor.setCorePoolSize(15);
        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(t1);//适合适用于Runnable
        service.execute(t2);//适合适用于Runnable
        //service.submit(Callable callable);//适合使用于Callable
        //3.关闭连接池
        service.shutdown();
    }
}

版权声明:本文为my2889214412原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。