美文网首页spring
Java基础——多线程

Java基础——多线程

作者: So_ProbuING | 来源:发表于2019-10-19 23:14 被阅读0次

    Java语言提供了非常优秀的多线程支持。可以通过非常简单的方式来启动多线程

    线程概述

    线程和进程

    • 进程:所有运行中的任务通常对应一个进程(Process)。当一个程序进入内存运行时,即变成一个进程。进程是处于运行过程中的程序,进程具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位

    进程的特征

    • 独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源。每一个进程都拥有自己私有的地址空间。在没有经过允许的情况下一个用户进程不可以直接访问其他进程的地址

    • 动态性:进程具有自己的生命周期和各种不同状态

    • 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响


    • 线程:线程也被称作轻量级进程,线程是进程的执行单元。线程在程序中是独立的、并发的执行流。当进程被初始化后,主线程就被创建了,一般对于一个应用程序来说,通常仅要求有一个主线程,但也可以在进程内创建多条顺序执行流,这些顺序执行流就是线程,每个线程也是互相独立的

      • 线程是进程的组成部分,一个进程可以拥有多个线程,但是一个线程必须有一个父进程。线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源

      • 线程的执行时抢占式的,线程是独立运行的,线程并不知道进程中是否还有其他线程的存在

      多线程的优势

    • 多线程程序并发性高

    • 创建线程消耗的资源要比创建进程更加省

    • 多线程之间共享内存更加容易

    • Java语言内置了多线程功能的支持

    多线程的创建和启动

    Java中使用Thread来代表线程,所有的线程对象都必须是Thread类或其子类的实例

    继承Thread类创建线程类

    1. 定义Thread类的子类,重写该类run()方法

    2. 创建该Thread子类的实例

    3. 调用线程对象的start()方法来启动线程
      class ThreadDemo extends Thread{

    public void run(){
    System.out.println(this.getName+"run");
    }

    public static void main(String[] args){
    new ThreadDemo().start();
    }

    }</pre>

    实现Runnable接口创建线程类

    1. 定义Runnable接口的实现类,并重写该接口的run()方法。run()方法的方法体同样是该线程的线程执行体

    2. 创建Runnable实现类的实例

    3. 调用线程对象的start()方法来启动线程
      class RunnableDemo implements Runnable{
      public void run(){
      System.out.println(this.getName()+"run");
      }

    public static void main(String[] args){
    new RunnableDemo().start();
    }
    }</pre>

    使用Callable和Future创建线程

    自Java5 开始 Java提供了Callable接口,Callable接口提供了一个call()方法作为线程的执行体

    • call()方法可以有返回值

    • call()方法可以声明抛出异常

    Java5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该类实现Future接口并实现runnable,可以作为Thread类的执行target

    在Future接口里定义了如下的API来控制关联的Callable任务

    • boolean cancel(boolean mayInterruptIfRunning) 试图取消该Future里关联的Callable任务

    • V get() 返回Callable任务里call()方法的返回值,调用该方法将导致程序阻塞,必须等待子线程结束后才会得到返回值

    • V get(long timeout,TimeUnit unit):返回Callable任务里call()方法的返回值,让程序最多阻塞timeout 和unit指定的时间,如果在指定时间之后仍然没有返回值,将抛出TimeOutException异常

    • boolean isCancelled() 如果在Callable任务正常完成前被取消,则返回true

    • boolean isDone() 如果Callable任务已完成,则返回true

    创建并启动有返回值的线程步骤

    1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建Callable实现类的实例

    2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法

    3. 使用FutureTask对象作为Thread对象的target创建并启动新线程

    4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

    public static void main(String[] args) throws ExecutionException, InterruptedException {
    //创建callable
    FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
    Thread.sleep(2000);
    return 20;
    }
    });
    new Thread(futureTask).start();
    System.out.println("线程的返回值"+futureTask.get());</pre>

    //创建callable

    //lambda
    FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
    Thread.sleep(2000);
    return 20;
    }
    });
    new Thread(futureTask).start();
    System.out.println("线程的返回值"+futureTask.get());
    FutureTask<String> futureTask = new FutureTask<>((Callable<String>) () -> {
    return "thread return";
    });
    new Thread(futureTask).start();
    System.out.println(futureTask.get());

    // Thread pool
    ExecutorService threadPool = Executors.newFixedThreadPool(5);
    System.out.println(threadPool.submit((Callable<Integer>) () -> {
    return 20;
    }).get());
    threadPool.shutdown();</pre>

    线程的生命周期

    线程被创建并启动后,线程不是立即进入就绪状态,也不是一直处于执行状态,需要经过 新建(New) 就绪(Runnable) 运行(Running) 阻塞(Blocked) 死亡(Dead) 5种状态

    新建和就绪状态

    • 当程序使用new关键字创建线程后,线程就处于新建状态,此时线程对象和java对象一样,仅仅由JAVA虚拟机为其分配内存,并初始化成员变量的值

    • 当线程对象调用start()方法后,线程处于就绪状态,Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的程序并没有开始运行,只是表示该线程可以运行了,至于程序何时开始运行取决于JVM里线程调度器的调度 只能对处于新建状态的线程调用start()方法,否则将引发IllegalThreadStateException

    运行和阻塞状态

    • 如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态

    • 当线程开始运行后,它不可能一直处于运行状态,线程在运行过程中需要被中断,使得其他线程获得执行机会。中断线程运行状态就会使线程进入阻塞状态

    当发生如下情况使,线程将会进入阻塞状态:

    • 线程调用sleep()方法主动放弃所占用的处理器资源

    • 线程调用了阻塞式I/O方法,在方法返回前处于阻塞状态

    • 线程试图获得锁对象,但该锁被其他线程所持有

    • 线程正在等待唤醒

    当发生如下的情况会让线程重新进入就绪状态

    • 调用sleep()方法的线程经过了指定的时间

    • 线程调用的阻塞式方法已经返回

    • 线程成功的获得了锁对象

    • 线程被唤醒

    • 挂起状态的线程被调用resume()方法

    [图片上传失败...(image-5f7d60-1571407713512)]

    线程死亡

    • run()或call()方法执行完成,线程正常结束

    • 线程抛出一个未捕获异常

    • 直接调用线程的stop()方法

    isAlive() 当线程处于就绪、运行、阻塞三种状态时返回true 当线程处于新建、死亡状态时返回false

    控制线程

    join线程

    Thread提供了一个让一个线程等待另一个线程完成的方法——join

    当在某个程序执行流中调用其他线程的Join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止

    注意:这里是调用线程将被阻塞

    join() 方法的重载

    • join() 等待被join的线程执行完成

    • Join(long millis) 等待被join的线程的时间最长为millis毫秒,如果时间过后还没执行结束则不再等待

    • join(long millis,int nanos) 等待被join的线程的时间最长为millis毫秒加nanos毫微秒

    public class JoinThread extends Thread {
    private String name;

    public JoinThread(String name) {
    setName(name);
    }

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

    }

    //main
    public static void main(String[] args) throws InterruptedException {
    //创建执行线程
    new JoinThread("线程1").start();
    //主线程
    for (int i = 0; i < 100; i++) {
    if (i == 30) {
    //创建要加入的线程
    JoinThread joinThread = new JoinThread("join线程");
    joinThread.start();
    joinThread.join();

    }
    System.out.println(Thread.currentThread().getName() + " "+i);
    }
    }
    }</pre>

    上面的代码中 在main线程中调用了joinThread的join方法 所以需要主线程等待joinThread执行结束后方可继续执行

    后台线程

    有一种线程,它是在后台运行的,它的任务是为其他线程提供服务,这种线程称为守护线程。典型的守护线程就是JVM

    • 特征:如果所有的前台线程都死亡,后台线程会自动死亡

    • 方式:调用线程对象的setDaemon(true)方法可以将指定线程设置成后台线程

    public class DaemonDemo extends Thread {
    public DaemonDemo(String name) {
    setName(name);
    }

    @Override
    public void run() {
    for (int i = 0; i < 10000; i++) {
    System.out.println(getName()+"\t"+i);
    }
    }

    public static void main(String[] args) {
    DaemonDemo daemonDemo = new DaemonDemo("守护线程");
    daemonDemo.setDaemon(true);
    daemonDemo.start();
    for (int i = 0; i < 100; i++) {
    System.out.println(Thread.currentThread().getName() + "\t" + i);
    }
    }
    }</pre>

    需要设置为守护线程必须在线程start()之前设置,否则会引发异常

    线程睡眠 sleep

    使当前正在执行的线程暂停一段时间,进入阻塞状态。

    sleep()重载:

    • sleep(long millis):让当前正在执行的线程暂停millis毫秒。并进入阻塞状态

    • sleep(long millis,int nanos)

    当当前线程调用sleep()方法进入阻塞状态后,在睡眠时间内,该线程不会获得执行激活,即使没有线程可以执行,处于sleep的线程也不会执行

    线程优先级

    每个线程都有一定的优先级,优先级高的线程会获得较高的执行机会,优先级低的线程则获得较少的执行机会

    • 每个线程默认的优先级都与创建它的父线程的优先级相同,main线程具有普通优先级

    Thread类提供了setPriority(int new)、getPriority()来设置和返回指定线程的优先级

    • setPriority(int priority) 参数可以是一个整数,范围是1~10之间

      • MAX_PRIORITY 10 最高优先级

      • MIN_PRIORITY 1 最低优先级

      • NORM_PRIORITY 普通优先级

    线程同步

    同步代码块

    为解决线程同步问题,java多线程支持引入了同步监视器来解决

    • 同步代码块语法

    synchronized(obj){
    ...
    }</pre>

    sychronized后面的参数obj就是同步监视器。当线程开始执行同步代码块之前,必须先获得同步监视器的锁定

    任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,线程会释放对该同步监视器的锁定

    • 任何对象都可以作为同步监视器,但是推荐使用可以被功能访问的共享资源作为同步监视器

    同步方法

    同步方法就是使用synchronized关键字来修饰的方法

    用synchronized修饰的实例方法不需要指定锁对象,同步方法的监视器就是this。

    语法格式

    public synchronized void method(param){
    ...
    }

    tips 线程何时释放同步锁?
    • 同步方法、代码块执行结束

    • 同步方法、代码块中遇到reture终止执行

    • 在同步方法、代码块中出现异常

    • 在同步方法、代码块中执行时,程序执行了同步监视器对象的wait()方法,当前线程暂停,并释放同步监视器

    tips 线程不会释放同步锁
    • 使用Thread.sleep() 来暂停当前线程的执行

    • 线程被挂起

    JAVA5 LOCK

    java5开始 java提供了一种功能强大的线程同步机制——通过显式定义同步锁对象来实现同步,在这种机制下,同步锁由Lock对象充当

    在实现线程安全的控制中,比较常用的是ReentrantLock(可重入锁),使用该Lock对象可以显式地加锁、释放锁

    class X{
    //定义锁对象
    private final ReentrantLock lock = new ReentrantLock();
    public void m(){
    //加锁
    lock.lock();
    try{
    //需要保证线程同步的代码
    }finally{
    //释放锁
    lock.unlock();
    }
    }
    }</pre>

    ReentrantLock锁具有可重入性,一个线程可以对已被加锁的ReentrantLock锁再次加锁。ReentrantLock对象会维持一个计数器来追踪lock()方法的嵌套调用,线程每次显式调用lock()加锁后,必须显式调用unlock()来释放锁

    死锁

    当两个线程相互等待对方释放同步监视器时就会发生死锁。

    线程通信

    传统方式

    传统方式中可以使用Object类提供的wait() notify() notifyAll()来进行线程通信和控制

    • wait():导致当前线程等待,直到其他线程调用该锁对象的notify()方法或notifyAll()来唤醒该线程

    • notify():唤醒在此同步监视器上等待的单个的线程。有多个可唤醒的线程时,选择是任意的

    • notifyAll():唤醒此同步监视器上等待的所有线程。

    相关文章

      网友评论

        本文标题:Java基础——多线程

        本文链接:https://www.haomeiwen.com/subject/idewmctx.html