java基础14-java 多线程1

    科技2022-08-01  91

    Java 多线程

    一个线程的生命周期sleep()、wait()和notify()、notifyAll()、yield()、join()的区别: 线程的优先级创建线程的方式一、通过实现Runnable接口来创建线程二、通过继承Thread来创建线程Thread 方法 三、通过 Callable 和 Future 创建线程创建线程的三种方式的对比 Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。 这里定义和线程相关的另一个术语 - 进程: 一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。 多线程能满足程序员编写高效率的程序 来达到充分利用 CPU 的目的。

    一个线程的生命周期

    线程是一个动态执行的过程,它也有一个从产生到死亡的过程。 下图显示了一个线程完整的生命周期。

    五种状态:新建new 就绪Runnable 运行Running 阻塞Blocked 死亡Dead start不是进入运行状态,而是就绪排队状态

    新建状态: 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

    就绪状态: 当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

    运行状态: 如果就绪状态的线程获取 CPU 资源,就可以执行run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

    阻塞状态: 如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

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

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

    sleep()、wait()和notify()、notifyAll()、yield()、join()的区别:

    sleep 方法是属于 Thread 类中的,sleep 过程中线程不会释放锁,只会阻塞线程,让出cpu给其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态,可中断,sleep 给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会

    wait 方法是属于 Object 类中的,wait 过程中线程会释放对象锁,只有当其他线程调用 notify 才能唤醒此线程。wait 使用时必须先获取对象锁,即必须在 synchronized 修饰的代码块中使用,那么相应的 notify 方法同样必须在 synchronized 修饰的代码块中使用,如果没有在synchronized 修饰的代码块中使用时运行时会抛出IllegalMonitorStateException的异常 wait()和notify()、notifyAll(),这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用也就是说,调用wait(),notify()和notifyAll()的任务在调用这些方法前必须拥有对象锁 wait()和notify()、notifyAll()它们都是Object类的方法,而不是Thread类的方法 当调用某一对象的wait() 方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用 notify() 方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获得锁标志,他们随时准备争夺锁的拥有权,当调用了某个对象的notifyAll() 方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池 1、wait():调用该方法使持有该对象的线程把该对象的控制权交出去,然后处于等待状态 2、notify():调用该方法就会通知某个正在等待这个对象的控制权的线程可以继续运行 3、notifyAll():调用该方法就会通知所有等待这个对象控制权的线程继续运行

    yield方法 和 sleep 一样都是 Thread 类的方法,都是暂停当前正在执行的线程对象,不会释放资源锁,和 sleep 不同的是 yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。还有一点和 sleep 不同的是 yield 方法只能使同优先级或更高优先级的线程有执行的机会

    join方法:等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景。例如:主线程创建并启动了子线程,如果子线程中要进行大量耗时运算计算某个数据值,而主线程要取得这个数据值才能运行,这时就要用到 join 方法了

    线程的优先级

    每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。 Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。 默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。 具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

    创建线程的方式

    Java 提供了三种创建线程的方法: 通过实现 Runnable 接口; 通过继承 Thread 类本身; 通过 Callable 和 Future 创建线程。

    一、通过实现Runnable接口来创建线程

    创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。 为了实现 Runnable,一个类只需要执行一个方法调用 run(),声明如下:

    public void run()

    你可以重写该方法,重要的是理解的 run() 可以调用其他方法,使用其他类,并声明变量,就像主线程一样。 在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。 Thread 定义了几个构造方法,下面的这个是我们经常使用的:

    Thread(Runnable threadOb,String threadName);

    这里,threadOb 是一个实现 Runnable 接口的类的实例,并且 threadName 指定新线程的名字。 新线程创建之后,你调用它的 start() 方法它才会运行。

    void start();

    实例代码:实现两个线程交替执行 Test10_7_2

    package Test; public class Test10_7_2 implements Runnable{ //子线程 线程1: @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 10; i++) { System.out.println("方法一第"+i+"次输出"); try { Thread.sleep(100);//休眠100ms } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } } }

    Test10_7_3

    package Test; public class Test10_7_3 { public static void main(String[] args) { // TODO Auto-generated method stub Test10_7_2 test=new Test10_7_2();//启动子线程 Thread t=new Thread(test); t.start();//执行start方法后才会执行run()方法中的内容 //主线程 线程2 //两个线程交替执行 for (int i = 0; i < 10; i++) { System.out.println("方法二第"+i+"次输出"); try { Thread.sleep(100); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } } } 输出: 方法二第0次输出 方法一第0次输出 方法二第1次输出 方法一第1次输出 方法二第2次输出 方法一第2次输出 方法二第3次输出 方法一第3次输出 方法二第4次输出 方法一第4次输出 方法二第5次输出 方法一第5次输出 方法二第6次输出 方法一第6次输出 方法二第7次输出 方法一第7次输出 方法二第8次输出 方法一第8次输出 方法二第9次输出 方法一第9次输出

    二、通过继承Thread来创建线程

    创建一个线程的第二种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。 继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。 该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。

    Test10_7_4

    package Test; public class Test10_7_4 extends Thread{//继承不了别的类 @Override public void run() { // TODO Auto-generated method stub //super.run(); for (int i = 0; i < 10; i++) { System.out.println("子线程第"+i+"次输出"); try { Thread.sleep(100);//休眠100ms } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } } }

    Test10_7_5

    package Test; public class Test10_7_5 { public static void main(String[] args) { //启动子线程 Test10_7_4 t4=new Test10_7_4(); t4.start();//不能调run方法,实现不了多线程交替执行 //t4.run(); for(int i=0;i<10;i++) { System.out.println("主线程第"+i+"次输出"); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }//两个进程交替执行 } } 输出: 主线程第0次输出 子线程第0次输出 子线程第1次输出 主线程第1次输出 主线程第2次输出 子线程第2次输出 子线程第3次输出 主线程第3次输出 子线程第4次输出 主线程第4次输出 主线程第5次输出 子线程第5次输出 主线程第6次输出 子线程第6次输出 主线程第7次输出 子线程第7次输出 主线程第8次输出 子线程第8次输出 子线程第9次输出 主线程第9次输出
    Thread 方法

    Thread t=Thread.currentThread();//当前线程 System.out.println(t);//Thread[main,5,main] 参数:线程名,优先级,执行类 t.setName("主线程");//线程取名 t.setPriority(6);//线程优先级设置 System.out.println(t); 输出: Thread[main,5,main] Thread[主线程,6,main]

    三、通过 Callable 和 Future 创建线程

    创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。 package Test; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class Test10_7_6 implements Callable<Integer> { public static void main(String[] args) { Test10_7_6 t6 = new Test10_7_6(); FutureTask<Integer> ft = new FutureTask<>(t6); for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i); if (i == 20) { new Thread(ft, "有返回值的线程").start(); } } try { System.out.println("子线程的返回值:" + ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } @Override public Integer call() throws Exception { int i = 0; for (; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } return i; } }

    创建线程的三种方式的对比

    采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
    Processed: 0.012, SQL: 8