多线程创建

程序、进程、线程的区别

程序:是为完成特定任务、用某种语言编写的一组指令的集合。

进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态
的过程,有它自身的产生、存在和消亡的过程(即生命周期)。

举例:运行中的QQ,360杀毒软件。

线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。

  • 若一个进程同一时间并行执行多个线程,就是支持多线程的。
  • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。
  • 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。

举例:360运行时,同时进行病毒查杀,垃圾清理,优化加速等等功能时,每一个功能都是一个线程。

单核CPU和多核CPU的理解

  1. 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。但是因为CPU时间单元特别短,因此感觉不出来。也就是说看起来是同时进行,其实是在不停切换。
  2. 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)。
  3. 一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

并行与并发

并行:多个CPU同时执行多个任务。
并发:一个CPU(采用时间片)同时执行多个任务。

多线程程序的优点

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  2. 提高计算机系统CPU的利用率。
  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。

多线程的创建的4种方式

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口
  4. 线程池

①继承Thread类

步骤

  1. 创建一个继承于Thread类的子类
  2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
  3. 创建Thread类的子类的对象
  4. 通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
//继承Thread类
public class Thread01 extends Thread {
    @Override
    //重写run方法
    public void run() {
    //this.getName()也可以
        System.out.println(Thread.currentThread().getName()+ "多线程启动啦");
    }
}
public class MyTest {
    public static void main(String[] args) {
    //创建Thread子类对象
        Thread01 td = new Thread01();
        //通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
        td.start();
        //td.run();
        System.out.println(Thread.currentThread().getName()+"我是主程序");
    }
}

结果

main我是主程序
Thread-0多线程启动啦

注意

1.我们启动一个线程,必须调用start(),不能调用run()的方式启动线程。

当我们把td.start()注释掉,换成td.run()。结果:

    main多线程启动啦
    main我是主程序

全都变成主线程执行了,这是因为通过run方法启动线程其实就是调用一个类中的方法,当作普通的方法的方式调用。并没有创建一个线程。只有start方法才会在启动线程的同时,创建线程。

2.如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start()。
我们在测试类中多次调用td.start()
结果

swl.practise.thread.MyTest
Exception in thread "main" java.lang.IllegalThreadStateException
Thread-0多线程启动啦
    at java.lang.Thread.start(Thread.java:708)
    at swl.practise.thread.MyTest.main(MyTest.java:7)

原因

多线程创建_第1张图片
image.png
问题就在这里,线程第一次启动时threadStatus为0,再次启动时不为0了,所以直接抛异常了。
Thread类中方法
void run()  将子线程要执行的操作写在方法体中。

void start()  启动线程,并且调用线程的run()方法。

①线程名称

String getName()  返回该线程的名称。

void setName(String name)  设置线程名称,使之与参数 name 相同。

如果不设置名字,线程也会有默认的名称。

②线程优先级

int getPriority()   返回线程的优先级。

void setPriority(int newPriority)   更改线程的优先级。

线程优先级的范围由高到低为1-10,默认优先级为5,高优先级的线程并不是会一定被执行,只是抢占CPU执行权的几率变大。

③守护线程与用户线程

boolean isDaemon()   测试该线程是否为守护线程。

void setDaemon(boolean on)  将该线程标记为守护线程或用户线程。

Java平台把操作系统的底层进行了屏蔽,在JVM虚拟平台里面构造出对自己有利的机制,这就是守护线程的由来。Daemon的作用是为其他线程的运行提供服务,比如说GC线程。如果用户线程结束,守护线程也会退出,因为他没什么好服务的了。setDaemon()方法必须在线程启动之前执行。

public final native boolean isAlive() 判断当前线程是否存活。

public static native Thread currentThread(); 获取当前执行的分线程。子线程中可以直接用this关键字,就代表当前线程。
④线程通信
static void sleep(long millis) 线程睡眠指定毫秒,时间结束后,线程结束阻塞状态。

void interrupt()  中断线程。这个之后再看,目前没时间。

static void yield()  暂停当前正在执行的线程对象,并执行其他线程。

void join()  等待该线程终止。

从Object类继承来的方法  void notify() void wait()

②实现Runnable接口

步骤

  1. 创建一个实现了Runnable接口的类。
  2. 实现类去实现Runnable中的抽象方法:run()。
  3. 创建实现类的对象。
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象。
  5. 通过Thread类的对象调用start()。
public class Thread02 implements Runnable {
    @Override
    public void run() {
        //这里不能this.getName() 因为接口里没有~~~~~
        System.out.println(Thread.currentThread().getName() + "启动了");
    }
}
public class MyTest {
    public static void main(String[] args) {
        Thread02 thread02 = new Thread02();
        //创建多个子线程,只需要用同一个子类对象就可以了
        //可以在创建时直接给线程起名字,继承Thread类也可以,不过需要在子类中提供参数为name的构造器
        Thread td1 = new Thread(thread02,"线程一号");
        Thread td2 = new Thread(thread02,"线程二号");
        td1.start();
        td2.start();

    }
}

结果

swl.practise.thread.MyTest
线程二号启动了
线程一号启动了

③实现callable接口

步骤

  1. 创建Callable子类的实例化对象
  2. 创建FutureTask对象,并将Callable对象传入FutureTask的构造方法中(注意:FutureTask实现了Runnable接口和Future接口)
  3. 实例化Thread对象,并在构造方法中传入FurureTask对象
  4. 启动线程
/*
 * 实现Callable接口创建子线程,指明范型为返回的数据类型
 * */
public class CallDemo implements Callable {

    @Override
    public String call() throws Exception {
        for (int i=0; i < 100 ; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
        return "执行完毕";
    }
}
public class TestCallable {
    public static void main(String[] args) {
        CallDemo cl = new CallDemo(); // 实例化Callable子类对象
        //要几个线程就要几个FutureTask对象哦
        FutureTask ft1 = new FutureTask(cl); // 实例化FutureTask对象,并将Callable子类对象传入FutureTask的构造方法中
        FutureTask ft2 = new FutureTask(cl); // 实例化FutureTask对象,并将Callable子类对象传入FutureTask的构造方法中
        Thread t1 =  new Thread(ft1, "1号线程输出——>"); // 启动线程
        Thread t2 = new Thread(ft2, "2号线程输出——>"); // 启动线程
        t1.start();
        t2.start();
    }
}

Callable接口相较于Runnable接口的好处:
1.重写的call()相较于run()可以返回值
2.返回值的类型可以通过泛型的方式指定
3.重写的call()可以throws的方式处理异常

④线程池

class MyThread implements Runnable {

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

}
public class ThreadPool {
    public static void main(String[] args) {
        // 1.调用Executors的newFixedThreadPool(),返回指定线程数量的ExecutorService
        ThreadPoolExecutor pool = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
        //设置管理参数
        pool.setMaximumPoolSize(20);
        // 2.将Runnable实现类的对象作为形参传递给ExecutorService的execute()方法中,开启线程
        // 并执行相关的run()
        pool.execute(new MyThread());
        pool.execute(new MyThread());
        pool.execute(new MyThread());
        
        // 3.结束线程的使用
        pool.shutdown();

    }
}

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

面试

继承Thread类和实现Runnable接口对比
实现Runnable的方式相较于继承Thread类的方式更合适一些

① 实现的方式避开了类的单继承性的局限性
② 实现的方式更适合处理多个线程共享数据的情况(继承Thread类如果想实现资源共享,需要给共享的变量加static关键字)

联系:public class Thread implements Runnable
相同点:
① 创建的都是线程类的对象
② 启动线程,都是调用的Thread类中的start()
说一下 runnable 和 callable 有什么区别?
Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
线程的 run()和 start()有什么区别?

每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。

start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码; 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。

run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。

你可能感兴趣的:(多线程创建)