Java:线程的创建和使用

Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread
类来体现


0:总结

部分总结
当Java程序开始运行后,程序至少会创建一个主线程,主线程的线程执行体main()方法确定的,main()方法的方法体代表主线程的线程执行体
Thread对象的getName()返回当前该线程的名字
调用Thread的currentThread方法获取当前线程
一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出异常“IllegalThreadStateException”
run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定
继承于Thread类:不能共享该实例属性
实现Runnable接口:所以多个线程可以共享同一个线程类即线程的target类的实例属性,故更适合来处理多个线程有共享数据的情况
实现Runnable接口:可通过new Thread(target , name)方法创建新线程的同时,给线程名赋值
对打印方法设置Synchronized,让每次只能有一个进程能够访问打印

1:方式一:继承于Thread类

  • 1.创建一个继承于Thread类的子类
  • 2.重写Thread类的run() --> 将此线程执行的操作声明在run()中
  • 3.创建Thread类的子类的对象
  • 4.通过此对象调用start()
 例子:遍历100以内的所有的偶数

//1. 创建一个继承于Thread类的子类
class MyThread extends Thread {
    
    
    //2. 重写Thread类的run()
    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            if(i % 2 == 0){
    
    
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}


public class ThreadTest {
    
    
    public static void main(String[] args) {
    
    
        //3. 创建Thread类的子类的对象
        MyThread t1 = new MyThread();
        //4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
        t1.start();
        //问题一:我们不能通过直接调用run()的方式启动线程。
//       t1.run();
        //问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException
//        t1.start();
        //我们需要重新创建一个线程的对象
        MyThread t2 = new MyThread();
        t2.start();      
    }

}


// 通过继承Thread类来创建线程类
public class MyThreadTest extends Thread {
    
    
    private int i;
    // 重写run方法,run方法的方法体就是线程执行体
    public void run() {
    
    
        for (; i < 100; i++) {
    
    
            // 当线程类继承Thread类时,直接使用this即可获取当前线程
            // Thread对象的getName()返回当前该线程的名字
            // 因此可以直接调用getName()方法返回当前线程的名
            System.out.println(getName() + "" + i);
        }
    }
    public static void main(String[] args) {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            // 调用Thread的currentThread方法获取当前线程
            System.out.println(Thread.currentThread().getName() + "" + i);
            if (i == 20) {
    
    
                // 创建、并启动第一条线程
                new MyThreadTest().start();
                // 创建、并启动第二条线程
                new MyThreadTest().start();
            }
        }
    }
}


在这里插入图片描述虽然上面程序只显式地创建并启动了2个线程,但实际上程序有3个线程,即程序显式创建的2个子线程和1个主线程。前面已经提到,当Java程序开始运行后,程序至少会创建一个主线程,主线程的线程执行体不是由run()方法确定的,而是由main()方法确定的,main()方法的方法体代表主线程的线程执行体。

该程序无论被执行多少次输出的记录数是一定的,一共是300条记录。主线程会执行for循环打印100条记录,两个子线程分别打印100条记录,一共300条记录。因为i变量是MyThreadTest的实例属性,而不是局部变量,但因为程序每次创建线程对象时都需要创建一个MyThreadTest对象,所以Thread-0和Thread-1不能共享该实例属性,所以每个线程都将执行100次循环。


2:方式二:实现Runnable接口

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

    //2. 实现类去实现Runnable中的抽象方法:run()
    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            if(i % 2 == 0){
    
    
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }

        }
    }
}


public class ThreadTest1 {
    
    
    public static void main(String[] args) {
    
    
        //3. 创建实现类的对象
        MThread mThread = new MThread();
        //4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(mThread);
        t1.setName("线程1");
        //5. 通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
        t1.start();

        //再启动一个线程,遍历100以内的偶数
        Thread t2 = new Thread(mThread);
        t2.setName("线程2");
        t2.start();
    }

}

public class MyRunnableTest implements Runnable {
    
    
    private int i;
    void print(){
    
    
         System.out.println(Thread.currentThread().getName() + "" + i);
    }
    // run方法同样是线程执行体
    public void run() {
    
    
        for (; i < 100; i++) {
    
    
            // 当线程类实现Runnable接口时,
            // 如果想获取当前线程,只能用Thread.currentThread()方法。
            print();
        }
    }
    public static void main(String[] args) {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            System.out.println(Thread.currentThread().getName() + "" + i);
            if (i == 20) {
    
    
                MyRunnableTest st = new MyRunnableTest();
                // 通过new Thread(target , name)方法创建新线程
                new Thread(st, "新线程-1").start();
                new Thread(st, "新线程-2").start();
            }
        }
    }
}

在这里插入图片描述从该运行结果中我们可以看出,控制台上输出的内容是乱序的,而且每次结果不尽相同。这是因为:

  1. 在这种方式下,程序所创建的Runnable对象只是线程的target,而多个线程可以共享同一个target。
  2. 所以多个线程可以共享同一个线程类即线程的target类的实例属性。
  3. 往控制台窗口print()输出的过程并不是多线程安全的,在一个线程输出过程中另一个线程也可以输出。

为能够保证顺序输出,我们可以对打印方法设置Synchronized,让每次只能有一个进程能够访问打印,代码如下:

public class MyRunnableTest implements Runnable {
    
    
    private int i;
    synchronized void print(){
    
    
         System.out.println(Thread.currentThread().getName() + "" + i);
    }
    // run方法同样是线程执行体
    public void run() {
    
    
        for (; i < 100; i++) {
    
    
            // 当线程类实现Runnable接口时,
            // 如果想获取当前线程,只能用Thread.currentThread()方法。
            print();
        }
    }
    public static void main(String[] args) {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            System.out.println(Thread.currentThread().getName() + "" + i);
            if (i == 20) {
    
    
                MyRunnableTest st = new MyRunnableTest();
                // 通过new Thread(target , name)方法创建新线程
                new Thread(st, "新线程-1").start();
                new Thread(st, "新线程-2").start();
            }
        }
    }
}

在这里插入图片描述


3:比较创建线程的两种方式

  • 开发中:优先选择:实现Runnable接口的方式
  • 原因:
    • 1.实现的方式没有类的单继承性的局限性
    • 2.实现的方式更适合来处理多个线程有共享数据的情况。
  • 联系:public class Thread implements Runnable
  • 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。

4:方式三:JDK 5.0新增线程创建方式–实现Callable接口

  1. 创建一个实现Callable的实现类
  2. 实现call方法,将此线程需要执行的操作声明在call()中
  3. 创建Callable接口实现类的对象
  4. 将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
  5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
  6. 获取Callable中call方法的返回值
//1.创建一个实现Callable的实现类
class NumThread implements Callable {
    
    
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
    
    
        int sum = 0;
        for(int i = 1;i<=10;i++){
    
    
            System.out.println(i);
            sum+=i;
        }
        return sum;
    }
}
public class ThreadNew {
    
    
    public static void main(String[] args){
    
    
        //3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
        //FutureTask 同时实现了Runnable, Future接口
        FutureTask futureTask = new FutureTask(numThread);
        //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();
        }
    }
}

Future接口概述

Java 5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口和Runnable接口可以作为Thread类的target。在Future接口里定义了如下几个公共方法来控制它关联的Callable任务

  • 1)boolcan cancel(boolean maylnterruptltRunning):试图取消该Future里关联的Callable任务
  • 2)V get():返回Callable任务里call()方法的返回值。调用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值
  • 3)V get(long timeout,TimeUnit unit):返回Callable任务里call()方法的返回值。该方法让程序最多阻塞timeout和unit指定的时间,如果经过指定时间后Callable任务依然没有返回值, 将会抛出TimeoutExccption异常
  • 4)boolean isCancelled():如果在Callable任务正常完成前被取消,则返回true
  • 5)boolean isDone():妇果Callable任务已完成,则返回true
    注意:Callable接口有泛型限制,Callable接口里的泛型形参类型与call()方法返回值类型相同。

5:如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?

  • call()可以返回值的。
  • call()可以抛出异常,被外面的操作捕获,获取异常的信息
  • Callable是支持泛型的

6:方式四:JDK 5.0新增线程创建方式–使用线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程对性能影响很大。

解决方案:
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

实现方法:

  • 提供指定线程数量的线程池
  • 执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
  • 关闭连接池

相关API:

JDK 5.0起提供了线程池相关AP|: Executor Service和 Executors

Executor Service:真正的线程池接口。常见子类 Thread Poolexecutor
void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
<T> Future<T> submit(Callable<T>task):执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors. newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFⅸedthreadPool(n);创建一个可重用固定线程数的线程池
EXecutors. newSingleThreadEXecutor():创建一个只有一个线程的线程池
Executors. new thread Poo(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
class NumberThread implements Runnable{
    
    
    @Override
    public void run() {
    
    
        for(int i = 0;i<10;i++){
    
    
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

class Number2Thread implements Callable {
    
    
    @Override
    public Object call() throws Exception {
    
    
        int sum = 0;
        for(int i = 1;i<=10;i++){
    
    
            System.out.println(Thread.currentThread().getName()+":"+i);
            sum+=i;
        }
        return sum;
    }
}

public class ThreadPool {
    
    
    public static void main(String[] args) {
    
    
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);//创建一个可重用固定线程数为10的线程池

        //查看该对象是哪个类造的
        System.out.println(service.getClass());//class java.util.concurrent.ThreadPoolExecutor
        //设置线程池的属性
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();

        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable
        Future future = service.submit(new Number2Thread());//适合使用于Callable
        try {
    
    
            System.out.println(future.get());//输出返回值
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } catch (ExecutionException e) {
    
    
            e.printStackTrace();
        }
        //3.关闭连接池
        service.shutdown();
    }
}

应用线程池的好处:

  • 1.提高响应速度(减少了创建新线程的时间)
  • 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 3.便于线程管理
    corePoolSize:核心池的大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没任务时最多保持多长时间后会终止

猜你喜欢

转载自blog.csdn.net/m0_51755061/article/details/115080454