多线程入门

线程应用

异步调用

public class Test1 {
     
    public static void main(String[] args) {
     
        new Thread(()->{
     
            try {
     
                Thread.sleep(1000);   // 休眠一秒
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"发短息----------------");
        },"thread1").start();

        System.out.println("主线程输出..........................");

    }
}

多线程入门_第1张图片

提高效率
充分利用多核cpu的优势,提高运行效率。想象下面的场景,执行三个计算,最后将计算结果汇总。
多线程入门_第2张图片

创建和运行线程

1. 继承Thread类

实现案例:

public class Test2 {
     
    public static void main(String[] args) {
     
        // 创建线程对象
        Thread t = new Thread() {
     
            public void run() {
     
                // 要执行的任务
                System.out.println("执行任务。。。。");
            }
        };
        // 启动线程
        t.start();
    }
}

这里继承Thread类 用的是匿名内部类的形式。(使用匿名内部类有个前提条件:必须继承一个父类或实现一个接口)

2. 使用Runnable配合Thread(常用)

把【线程】和【任务】(要执行的代码)分开

  • Thread代表线程
  • Runnable可运行的任务(线程要执行的代码)

Runnable是一个接口,且只有一个实现方法,如下图源码所示:
多线程入门_第3张图片

实现案例:

public class Runnable_Thread_test {
     
    public static void main(String[] args) {
     
        // 1. 编写Runnable的实现类
        Runnable runnable = new Runnable() {
      //这里实现Runnable接口 用的是匿名内部类的形式
            public void run(){
     
                // 要执行的任务
                System.out.println("执行任务-Runnable_Thread。。。。。。。");
            }
        };
        // 2. 创建线程对象
        Thread t = new Thread(runnable);
        // 3. 启动线程
        t.start();
    }
}

Java 8 以后可以使用lambda精简代码:

// 1. 编写Runnable的实现类
Runnable runnable = ()->{
     System.out.println("执行任务-Runnable_Thread 哈哈。。。。。。。");};
// 2. 创建线程对象
Thread t = new Thread(runnable);
// 3. 启动线程
t.start();

3. 使用Callable、FutureTask配合Thread(可以接收返回值)

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class FutureTask_Callable_Thread_test {
     
    public static void main(String[] args) throws ExecutionException, InterruptedException {
     

        // 1. 编写FutureTask类
        FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
       // 2. FutureTask对象的构造方法参数是一个 Callable接口的实现对象
            @Override
            public Integer call() throws Exception {
     
                System.out.println("Callable——FutureTask执行中。。。。。");
                Thread.sleep(5000);  // 休眠5秒
                return 100; // 这里有一个返回值
            }
        });

        // 3. 创建Thread线程类
        Thread t = new Thread(task);
        // 4. 执行
        t.start();
        // 5. 获取线程执行后的返回值
        Object result = task.get();   // 此刻主线程会一直等待线程任务结果的返回(阻塞)

        System.out.println(result);
        System.out.println("哈哈哈");
    }
}

执行结果为:
在这里插入图片描述

终端下查看进程线程的方法

windows

  • tasklist 查看所有进程
  • tasklist | findstr xxx 查看某些相关的线程
  • taskkill 杀死进程

linux

  • ps -ef 查看所有进程
  • ps -ef | grep xxx 查看某些相关进程
  • kill xxx 杀死进程

Java

  • jps命令查看所有java进程
  • jstack 查看某个java进程(PID)的所有线程状态

线程的上下文切换(Thread Context Switch)

因为以下一些原因导致cpu不再执行当前的线程,转而执行另一个线程的代码

  • 线程的cpu时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用sleep、yield、wait、join、park、sychronized、lock等方法

当Context Switch 发生时,需要有操作系统保存当前线程的状态,并恢复另一个线程的状态,java中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条jvm指令的执行地址,是线程私有的

  • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  • Context Switch频繁发生会影响性能

操作线程常见方法

start() : 启动一个新线程,在新的线程运行run方法中的代码。该启动的线程不会马上运行,start方法只是让线程进入就绪状态,会放到等待队列中等待 CPU 调度,只有线程真正被 CPU 调度时才会调用 run() 方法执行。 每个线程对象的start方法只能调用一次,否则就会出现IllegalThreadStateException

run() : 新线程启动后会调用的方法。如果在构造Thread对象时传递了Runnable参数,则线程启动后会调用Runnable中的run方法,否则默认不执行任何操作。但可以创建Thread的子类对象,来覆盖默认行为。

join(): 等待线程运行结束
join(long n): 等待线程运行结束,最多等待n毫秒
getId(): 获取线程唯一的id
getName: 获取线程名
setName(String): 修改线程名
getPriority(): 获取线程优先级
setPriority(int): 修改线程优先级
getState(): 获取线程状态
currentThread(): static方法,获取当前正在执行的线程
sleep(long n): static方法,让当前执行的线程休眠n毫秒,休眠时让出cpu的时间片给其他线程
yield(): static方法,提示线程调度器让出当前线程对CPU的使用(主要是为了测试和调试)

直接调用run和调用start的区别

上代码:

public class startVsRunTest {
     
    public static void main(String[] args) {
     
        Thread t1 = new Thread(){
     
            public void run(){
     
                System.out.println(Thread.currentThread().getName());  // 输出调用当前run方法的线程
                try {
     
                    Thread.sleep(5000);     // 阻塞5秒
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
            }
        };
        t1.run();               // 直接调用t1的run方法
        System.out.println("我是主线程输出");
    }
}

运行结果如下:
在这里插入图片描述

结论:
如果直接调用run方法,main线程会将其当做普通方法对待(还是main线程调用)
如果想另开启线程应当调用对应线程的start方法

sleep与yield

sleep:

  1. 调用sleep会让当前线程从Running进入Timed Waiting(阻塞)状态
  2. 其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用TimeUnit的sleep代替Thread的sleep来获得更好的可读性

yield:

  1. 调用yield会让当前线程从Running进入Runnable(就绪)状态,然后调度执行其他线程

join方法

join:等待其他线程运行结束

public class JoinTest {
     
    static int r = 0;
    public static void main(String[] args) {
     

        Thread t = new Thread(() -> {
     
            System.out.println("开始休眠");
            try {
     
                Thread.sleep(1000);
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
            System.out.println("结束休眠");
            r = 10;
        });

        t.start();
        System.out.println("最终的结果为"+r);
    }
}

上图代码有没有人认为输出结果r会是10的? 哈哈 应该会有不认真的朋友这样认为吧
上图看结果:
多线程入门_第4张图片
可以看出,最后的执行结果是0,而不是10。 其实很好明白,因为给r赋值为10那个线程休眠了一秒,这里主线程是跑得比较快的,t线程还没来得及修改,主线程就直接输出r的值了。
那么问题来了,怎么实现让线程t执行完,主线程再输出r的值呢?
解决这个问题我们可以使用join方法

public class JoinTest {
     
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
     

        Thread t = new Thread(() -> {
     
            System.out.println("开始休眠");
            try {
     
                Thread.sleep(1000);
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
            System.out.println("结束休眠");
            r = 10;
        });

        t.start();
        t.join();   // t线程加入主线程,等t线程结束后再执行后面代码
        System.out.println("最终的结果为"+r);
    }
}

可以在主线程中加入xxx.join(),可以理解为xxx线程加入了主线程

如果主线程中加入多个线程,那么主线程应该等待多长时间呢?

上代码:

public class JoinTest {
     
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
     

        Thread t1 = new Thread(() -> {
     
            try {
     
                Thread.sleep(1000);  // 休眠一秒
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
        });
        Thread t2 = new Thread(() -> {
     
            try {
     
                Thread.sleep(2000); // 休眠两秒
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
        });
        Thread t3 = new Thread(() -> {
     
            try {
     
                Thread.sleep(3000); // 休眠三秒
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
        });
        t1.start();
        t2.start();
        t3.start();
        long start = System.currentTimeMillis();
        t1.join();      // 线程1加入
        t2.join();      // 线程2加入
        t3.join();      // 线程3加入
        long end = System.currentTimeMillis();
        System.out.println("主线程总共等待时间:"+(end-start));
    }
}

最后运行结果:
在这里插入图片描述
可以看出 最后等待的结果是三秒。
也就是说当主线程中有多个线程的join时,主线程等待时间是其他多个线程中最大的执行完成时间。 因为其他的线程执行是相互独立的,互不影响

interrupt方法详解

打断阻塞线程:
像某些线程调用了sleep、wait、join等方法进入阻塞状态时,如果被interrute打断就会抛出异常,并将打断标记设为false

public class Interrupt_test {
     
    public static void main(String[] args) throws InterruptedException {
     
        Thread t1 = new Thread(()->{
     
            try {
     
                Thread.sleep(6000);     // wait、join
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
        });
        t1.start();
        Thread.sleep(1000);
        System.out.println("interrupt");
        t1.interrupt();
        System.out.println("打断标记:---"+t1.isInterrupted());
    }
}

多线程入门_第5张图片

打断正常运行的线程:
正常运行的线程(非阻塞状态下)被调用interrupt()方法,线程不会被打断,只是将线程的打断标记设成true
如果想打断的话需要在线程里写逻辑手动退出

public class Interrupt_test {
     
    public static void main(String[] args) throws InterruptedException {
     
        Thread t1 = new Thread(()->{
     
            while(true){
     
                boolean interrupted = Thread.currentThread().isInterrupted();
                if (interrupted){
     
                    System.out.println("有被打断-- 退出循环");
                    break;
                }
            }
        });
        t1.start();
        Thread.sleep(1000);
        System.out.println("interrupt");
        t1.interrupt();
        System.out.println("打断标记:---"+t1.isInterrupted());
    }
}

在这里插入图片描述

两阶段终止模式

在一个线程T1中如何“优雅”的终止线程T2?这里的【优雅】指的是给T2一个料理后事的机会

错误思路:

  1. 使用线程对象的stop() 方法停止线程
    stop方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就不 能释放锁,就会出现死锁现象,其他线程将无法获取锁
  2. 使用System.exit(int) 方法停止线程
    目的仅是停止一个线程,但这种做法会将整个程序停止

正确方式:

public class Interrupt_test {
     
    public static void main(String[] args) throws InterruptedException {
     
        TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
        twoPhaseTermination.start();

        Thread.sleep(3000);
        twoPhaseTermination.stop();
    }

}
class TwoPhaseTermination {
     
    private Thread monitor;

    // 启动监控线程
    public void start(){
     
        monitor = new Thread(()->{
     
            while (true){
     
                Thread current = Thread.currentThread();
                System.out.println(current.getName());
                if(current.isInterrupted()){
     
                    System.out.println("料理后事");
                    break;
                }
                try {
     
                    Thread.sleep(1000);  // 如果在这个位置被打断 会进入catch块儿
                    System.out.println("执行监控");
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                    current.interrupt(); // 重新设置打断标记
                }
            }
        },"t1");
        monitor.start();
    }

    // 停止监控线程
    public void stop(){
     
        monitor.interrupt();
    }
}

多线程入门_第6张图片

线程的状态

操作系统层面来描述:分为5种

1. 【初始状态】: 仅是在语言层面创建了线程对象,还未与操作系统线程关联
2. 【可运行状态】(就绪状态):指线程已经被创建(与操作系统线程关联),可以由CPU调度执行
3. 【运行状态】: 指获取了CPU时间片运行中的状态
当CPU时间片用完,会从【运行状态】转换至【就绪状态】,会导致线程的上下文切换
4. 【阻塞状态】
1. 如果调用了阻塞API,如BIO读写文件,这时该线程实际不会用到CPU,会导致线程的上下文切换,进入【阻塞状态】
2. 等BIO操作完毕,会有操作系统唤醒阻塞的线程,转换至【就绪状态】
3. 与【就绪状态】的区别是,对【阻塞状态】的线程来说只要他们一直不唤醒,调度器就一直不会考虑调度他们。
5. 【终止状态】: 表示线程已经执行完毕,生命周期已经结束,不会再转换为其他状态

多线程入门_第7张图片

Java API层面描述: 分为6种
根据Thread.State 枚举,分为六种状态

  public enum State {
     

        NEW, 	

        RUNNABLE,	

        BLOCKED,

        WAITING,

        TIMED_WAITING,

        TERMINATED;
    }

1. NEW 线程刚被创建,但还没有调用start()方法
2. RUNNABLE 当调用start() 方法后,注意,Java API 层面的RUNNABLE状态涵盖了操作系统层面的【可运行状态】(就绪)、【运行状态】
3. BLOCKED、WAITING、TIMED_WAITING都是Java API层面对【阻塞状态】的细分
4. TERMINATED 当线程代码运行结束

演示代码看这些状态对应哪些情况:

public class Thread_state {
     
    public static void main(String[] args) {
     
        Thread t1 = new Thread("t1"){
     	// 不调用start方法
            public void run(){
     
               
            }
        };

        Thread t2 = new Thread("t2"){
     	// 一直循环一直执行下去
            public void run(){
     
                while (true){
     }
            }
        };
        t2.start();

        Thread t3 = new Thread("t3"){
     	// 最后会执行结束 会终止
            public void run(){
     
            }
        };
        t3.start();

        Thread t4 = new Thread("t4"){
     	// 加了一个Thread.sleep(100000000) 有时限的sleep
            public void run(){
     
                synchronized (Thread_state.class){
     
                    try {
     
                        Thread.sleep(100000000);    //  time_waiting 有时限的等待
                    } catch (InterruptedException e) {
     
                        e.printStackTrace();
                    }
                }
            }
        };
        t4.start();

        Thread t5 = new Thread("t5"){
     
            public void run(){
     	// 加了t2.join();会阻塞
                try {
     
                    t2.join();      // waiting 没有时间限制的等待
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
            }
        };
        t5.start();

        Thread t6 = new Thread("t6"){
      	// 加了锁由于t4线程先获取锁,所以t6也会阻塞
            public void run(){
     
                synchronized (Thread_state.class){
       // BLOCKED 锁阻塞
                    try {
     
                        Thread.sleep(100000000);    //  time_waiting 有时限的等待
                    } catch (InterruptedException e) {
     
                        e.printStackTrace();
                    }
                }
            }
        };
        t6.start();

        try {
     
            Thread.sleep(1000);
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        }

        System.out.println("t1 state---"+t1.getState());
        System.out.println("t2 state---"+t2.getState());
        System.out.println("t3 state---"+t3.getState());
        System.out.println("t4 state---"+t4.getState());
        System.out.println("t5 state---"+t5.getState());
        System.out.println("t6 state---"+t6.getState());
    }
}

最后执行结果为:
多线程入门_第8张图片


你可能感兴趣的:(多线程,多线程,java,多进程,thread)