Java_多线程

文章目录

  • Java_进程的概念
    • Java_单核和多核的理解
    • Java_并行与并发
  • Java_多线程
    • Java_线程的生命周期
  • Java_线程的创建
    • Java_Thread类的概念
    • Java_实现Runable接口创建线程
    • Java_继承Thread创建线程
    • Java_Thread类的特点
    • Java_Thread类的重要方法
    • Java_线程的优先级
  • Java_线程池的概念
    • Java_常用线程池接口和类(所在包java.util.concurrent):
    • Java_Callable接口和Future创建线程

Java_进程的概念

  1. 程序 (program)是为完成特定任务、用某种语言编写的一组指令的集合。程序 - 数据结构 + 算法,主要指存放在硬盘上的可执行文件。

  2. 进程(process)是 主要指运行在内存中的可执行文件。。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期(如:运行中的QQ,运行中的MP3播放器)

  3. 一个进程包括由操作系统分配的内存空间,包含一个或多个线程。

  4. 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

  5. 线程就是进程内部的程序流,也就是说操作系统内部支持多进程的,而每个进程的内部又是支持多线程的,线程是轻量的,新建线程会共享所在进程的系统资源,因此目前主流的开发都是采用多线程。

  6. 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小,但是线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束

  7. 一个进程中的多个线程共享相同的内存单元/内存地址空间,多个线程从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。

  8. 多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
    Java_多线程_第1张图片

Java_单核和多核的理解

  1. 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。即同一时间处理一个事情,有其他事情了,先排队,然后一个个处理,但是运行很快
  2. 多核的CPU话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
  3. Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

Java_并行与并发

  1. 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
  2. 并发:一个CPU(采用时间片)同时执行多个任务。比如:多个人做同一件事。

Java_多线程

多线程的优点

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验
  2. 提高计算机系统CPU的利用率
  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
  4. 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销

多线程的使用场景;

  1. 程序需要同时执行两个或多个任务。
  2. 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
  3. 程序需要一些后台运行的程序时。

Java_线程的生命周期

  1. 线程是一个动态执行的过程

Java_多线程_第2张图片

  1. 新建状态:使用new关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。此时线程并没有开始执行。它保持这个状态直到程序 start() 这个线程。
  2. 就绪状态:当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。此时线程还是没有开始执行。
  3. 运行状态: 如果就绪状态的线程获取了CPU资源,就可以执行run(),此时线程开始执行,处于运行状态,运行状态的线程最为复杂,可以变为阻塞状态,就绪状态和消亡状态
  4. 消亡状态 : 当线程的任务执行完成后进入的状态,此时线程已经终止。
  5. 阻塞状态 :当线程执行的过程中发生了阻塞事件进入的状态,如:sleep方法。阻塞状态解除后进入就绪状态。

补充:线程的阻塞状态

  1. 如果一个线程执行了sleep睡眠,suspend(挂起)等方法,在线程失去所占用的资源后,该线程就成运行状态进入阻塞状态,在睡眠时间完毕或者获得设备资源之后,就可以重新进入就绪状态

补充:线程的阻塞状态分为三种:

  1. 等待阻塞:运行状态中的线程执行wait()方法,使得线程进入等待阻塞状态
  2. 同步阻塞:线程在获取synchronized同步锁失败(因为同步锁被其他线程占用)
  3. 其他阻塞:通过调用线程的sleep()join()发出了I/O请求时,线程就会进入阻塞状态,当sleep()状态超时时,join()等待线程终止或超时,或者I/O处理完毕,线程重新转入就绪状态

补充:线程的消亡状态

  1. 一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态

Java_线程的创建

Java_Thread类的概念

  1. java.lang.Thread类代表线程,任何线程对象都是Thread类(子类)的实例。
  2. Thread类是线程的模板,封装了复杂的线程开启等操作,封装了操作系统的差异性。

创建方式

  1. 自定义类继承Thread类并重写run方法,然后创建该类的对象调用start方法。
  2. 自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对象,然后使用Thread类型的对象调用start方法。
  3. 通过CallableFuture创建线程

继承方式和实现方式的区别:

  1. 继承 Thread:线程代码存放Thread子类run方法中。
  2. 实现Runnable:线程代码存在接口的子类的run方法。

实现方式的好处:

  1. 避免了单继承的局限性
  2. 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。

Java_实现Runable接口创建线程

  1. 最简单的方法是创建一个实现 Runnable 接口的类。
  2. 为了实现Runnable,一个类只需要执行一个方法调用run():public void run(),可以重写run方法,同时 run() 可以调用其他方法,使用其他类,并声明变量,就像主线程一样。
  3. 在创建一个实现 Runnable 接口的类之后,可以在类中实例化一个线程对象。
  4. 新线程创建之后,可以调用线程的start()方法运行新县城

方式一:实现Runnable接口

  1. 定义子类,实现Runnable接口。
  2. 子类中重写Runnable接口中的run方法。
  3. 通过Thread类含参构造器创建线程对象。
  4. Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
  5. 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
package com.company;
// Java_线程样例 使用 Runnable 接口实现线程
public class Java_27 {
    public static void main(String args[]) {
        RunnableDemo R1 = new RunnableDemo( "Thread-1");
        R1.start();

        RunnableDemo R2 = new RunnableDemo( "Thread-2");
        R2.start();
    }
}

class RunnableDemo implements Runnable {
    private Thread t;
    private String threadName;

    RunnableDemo( String name) {
        threadName = name;
        System.out.println("创建线程 " +  threadName );
    }

    public void run() {
        System.out.println("启动线程 " +  threadName );
        try {
            for(int i = 4; i > 0; i--) {
                System.out.println("线程执行: " + threadName + ", " + i);
                // 让线程睡眠一会
                Thread.sleep(50);
            }
        }catch (InterruptedException e) {
            System.out.println("线程执行出现异常 " +  threadName + " interrupted.");
        }
        System.out.println("线程执行 " +  threadName + " exiting.");
    }

    public void start () {
        System.out.println("开始执行线程 " +  threadName );
        if (t == null) {
            t = new Thread (this, threadName);
            t.start ();
        }
    }
}

Java_继承Thread创建线程

  1. 创建线程的第二种方法是创建一个新的类,该类继承Thread类,然后创建类的实例
  2. 继承Thread的类必须重写run()方法,run()方法是新线程的入口点,继承Thread的类新建的线程也必须通过start()方法启动
  3. 继承Thread创建线程尽管被列为一种多线程实现方式,但是本质上也是实现了Runnable接口的一个实例。

方式二:继承Thread类:

  1. 定义子类继承Thread类。
  2. 子类中重写Thread类中的run方法
  3. 创建Thread子类对象,即创建了线程对象。
  4. 调用线程对象start方法:启动线程,调用run方法

注意:

  1. 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
  2. run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
  3. 想要启动多线程,必须调用start方法。
  4. 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”。
package com.company;

// 通过继承 Thread 类的方式新建一个线程
public class Java_28 {
    public static void main(String args[]) {
        ThreadDemo T1 = new ThreadDemo( "Thread-1");
        T1.start();

        ThreadDemo T2 = new ThreadDemo( "Thread-2");
        T2.start();
    }
}
class ThreadDemo extends Thread {
    private Thread t;
    private String threadName;

    ThreadDemo( String name) {
        threadName = name;
        System.out.println("创建线程 " +  threadName );
    }

    public void run() {
        System.out.println("执行线程 " +  threadName );
        try {
            for(int i = 4; i > 0; i--) {
                System.out.println("线程名为: " + threadName + ", " + i);
                // 让线程睡眠一会
                Thread.sleep(50);
            }
        }catch (InterruptedException e) {
            System.out.println("线程出现异常 " +  threadName + " interrupted.");
        }
        System.out.println("线程名为 " +  threadName + " exiting.");
    }

    public void start () {
        System.out.println("启动线程 " +  threadName );
        if (t == null) {
            t = new Thread (this, threadName);
            t.start ();
        }
    }
}

Java_Thread类的特点

  1. Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现。
  2. 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常
    run()方法的主体称为线程体
  3. 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()

Java_Thread类的重要方法

调用线程的重要方法

  1. public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
  2. public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
  3. public final void setName(String name) 改变线程名称,使之与参数 name 相同。
  4. public final void setPriority(int priority) 更改线程的优先级。
  5. public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
  6. public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。
  7. public void interrupt() 中断线程。
  8. public final boolean isAlive() 测试线程是否处于活动状态。

暂停线程的重要方法

  1. public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
  2. public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
  3. public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
  4. public static Thread currentThread() 返回对当前正在执行的线程对象的引用。
  5. public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。

Java_线程的优先级

  1. 每个线程都有一个优先级,优先级是从1-10之间的数字表示,
  2. 大多数情况下,线程调用程序根据线程的优先级(称为抢占式调用)来调用线程,但是这种情况下,不能选择调用方式

在Thread类中定义的三个常量:

  1. public static int MIN_PRIORITY
  2. public static int NORM_PRIORITY
  3. public static int MAX_PRIORITY
  4. 线程的默认优先级为5(NORM_PRIORITY)MIN_PRIORITY的值为1MAX_PRIORITY的值为10。
package com.company;
// 线程的优先级
public class Java_29 {
    public static void main(String args[]) {
            TestMultiPriority1 m1 = new TestMultiPriority1();
            TestMultiPriority1 m2 = new TestMultiPriority1();
            m1.setPriority(Thread.MIN_PRIORITY);
            m2.setPriority(Thread.MAX_PRIORITY);
            m1.start();
            m2.start();

        }
    }
class TestMultiPriority1 extends Thread {
    public void run() {
        System.out.println("正在运行的线程名:" + Thread.currentThread().getName());
        System.out.println("正在运行的线程的优先级:" + Thread.currentThread().getPriority());

    }
}

Java_线程池的概念

  1. Java线程池表示一组正在等待作业并重复使用多次的工作线程
  2. 在线程池的情况下,创建一组固定大小的线程,来自线程池中的线程被拉去并由服务提供者分配作业,完成作业后,线程将会被再次包含在线程池中

线程池的优点:线程池提供了更好的新能,因为不需要重新创建新线程,所以节省了时间
在Servlet和JSP中使用线程池,容器创建一个线程来处理请求

package com.company;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 线程池实例


class WorkerThread implements Runnable {
    private String message;

    public WorkerThread(String s) {
        this.message = s;
    }

    public void run() {
        System.out.println(Thread.currentThread().getName() + " 开始信息 = " + message);
        processmessage();// call processmessage method that sleeps the thread for 2 seconds
        System.out.println(Thread.currentThread().getName() + " 结束");// prints thread name
    }

    private void processmessage() {
        try {
            Thread.sleep(2000); // 休眠 2 秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}



public class Java_30 {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);//creating a pool of 5 threads
        for (int i = 0; i < 10; i++) {
            Runnable worker = new WorkerThread("" + i);
            executor.execute(worker);//calling execute method of ExecutorService
        }
        executor.shutdown();
        while (!executor.isTerminated()) {   }

        System.out.println("所有线程执行完毕");
    }


}

Java_常用线程池接口和类(所在包java.util.concurrent):

  1. Executor:线程池顶级接口
  2. ExecutorService:线程池接口,可通过submit(Runnable task)提交任务。
  3. Executors工具类:通过此类可以获得一个线程池。
  4. 通过newFixedThreadPool(int nThreads):获取固定数量的线程池。参数:指定线程池中线程的数量。
  5. newCashedThreadPool():获得动态数量的线程池,如不够则重新创建新的,没有上限。
package com.company;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 线程池实例二 :详细使用线程池
public class Java_31 {
    public static void main(String[] args){
        //创建固定个数的线程池
        // ExecutorService es=Executors.newFixedThreadPool(5);
        //创建动态线程池
        ExecutorService es= Executors.newCachedThreadPool();
        //创建单线程线程池
        //Executors.newSingleThreadExecutor();
        //创建调度线程池
        //Executors.newScheduledThreadPool();
        // 创建任务
        Runnable runnable=new Runnable() {
            private int ticket=100;
            @Override
            public void run() {
                while (true){
                    if (ticket<=0){
                        break;
                    }
                    System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
                    ticket--;
                }
            }
        };
        //提交任务
        for (int i =0;i<5;i++){//5=上面的线程池个数
            es.submit(runnable);
        }
        //关闭线程池
        es.shutdown();
    }
}

Java_Callable接口和Future创建线程

  1. 创建 Callable 接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
  2. 创建Callable实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
  3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
  4. 调用FutureTask对象的 get() 方法来获得子线程执行结束后的返回值。

Callable接口:

  1. Callable接口 与Runnable接口类似,实现之后代表一个接口任务。
  2. Callable具有泛型返回值,可以声明异常
package com.company;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// Callable 接口样例

/**
 * 演示Callable接口的使用
 * Callable与Runnable接口的区别:
 * ​    1、Callable中call方法具有泛型返回值、Runnable接口中run方法没有返回值
 *     2、Callable中call方法可以声明异常,Runnable接口中run方法没有异常
 */
public class Java_33 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //功能需求,使用Callable实现1-10的和
        //1、创建Callable对象
        Callable callable=new Callable() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"开始计算");
                int sum=0;
                for (int i=0;i<=10;i++){
                    sum+=i;
                }
                return sum;
            }
        };
        //把Callable对象转成任务
        FutureTask task=new FutureTask<>(callable);
        //创建线程
        Thread thread=new Thread(task);
        thread.start();
        //获取结果
        Integer sum = task.get();
        System.out.println("计算结果是:"+sum);
    }
}

Future接口:表示将要完成任务的结果
线程池+Callable接口+Future+接口的综合使用

package com.company;

import java.util.concurrent.*;

// 线程池+Callable+Future+接口的综合使用
/**
 * 要求:计算1-100的和
 *    使用两个线程,并发计算1-50、51-100的和,再进行汇总统计。
 */
public class Java_34 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            //创建线程池
            ExecutorService es = Executors.newFixedThreadPool(2);
            //提交任务
            Future future=es.submit(new Callable() {
                private int sum=0;
                @Override
                public Integer call() throws Exception {
                    System.out.println(Thread.currentThread().getName()+"开始计算");
                    for (int i=0;i<=50;i++){
                        sum+=i;
                    }
                    return sum;
                }
            });
            Future future2=es.submit(new Callable() {
                private int sum=0;
                @Override
                public Integer call() throws Exception {
                    System.out.println(Thread.currentThread().getName()+"开始计算");
                    for (int i=51;i<=100;i++){
                        sum+=i;
                    }
                    return sum;
                }
            });
            //汇总
            int sum=future.get()+future2.get();
            System.out.println("计算结果是:"+sum);
        }
    }

你可能感兴趣的:(Java,java)