Java线程

线程和进程

进程

运行中的程序,就称为一个进程,把操作系统做的某件事称为一个任务,一个进程可以对应一个任务,也可以对应多个任务,进程是用来管理内存、管理指令、管理I/O的,是资源分配的最小单位。

通信方式

1. 管道(pipe)及有名管道(named pipe):管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。

2. 信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。

3. 消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。

4. 共享内存(shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。

5. 信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。

6. 套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。

线程

线程是进程中的一个或多个实体,一个线程一定会有一个父进程,线程是cpu调度的最小执行单位。

同步互斥

同步:指的是一个线程的执行需要依赖另一个线程的消息,当另一个线程没有消息返回,该线程应该等待,直到收到消息后才被唤醒。

互斥:指的是当某一个共享资源被一个线程访问时,其他的线程不能访问,直到该资源被释放。

同步互斥控制方法

  • 临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。(在一段时间内只允许一个线程访问的资源就称为临界资源)。
  • 互斥量:为协调共同对一个共享资源的单独访问而设计的。
  • 信号量:为控制一个具有有限数量用户资源而设计。
  • 事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。

上下文切换

指的是CPU从一个进程或线程到另一个进程或线程的切换,是操作系统对CPU上进程(包含线程)的一个操作,其步骤如下:

1. 暂停一个进程的处理,并将该进程的CPU状态(即上下文)存储在内存中的某个地方

2. 从内存中获取下一个进程的上下文,并在CPU的寄存器中恢复它

3. 返回到程序计数器指示的位置(即返回到进程被中断的代码行)以恢复进程。

上下文切换的场景

  • 发生下内核模式
  • 一般在多任务操作系统下发生
  • 通常是计算密集下的情况

应用程序切换到内核态的情况

  1. 系统调用
  2. 异常事件
  3. 中断

Linux查看上下文切换命令

vmstat [时间间隔/单位s]

查看具体进程的命令

pidstat [选项1] [选项2] [PID] [时间]

常用的参数: 
-u 默认参数,显示各个进程的 CPU 统计信息 
-r 显示各个进程的内存使用情况 
-d 显示各个进程的 IO 使用 
-w 显示各个进程的上下文切换 
-p PID 指定 PID

进程和线程的区别

  1. 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
  2. 进程拥有共享的资源,如内存空间等,供其内部的线程共享
  3. 进程间通信较为复杂
  • 同一台计算机的进程通信称为 IPC(Inter-process communication)
  • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
  1. 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
  2. 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低

查看进程线程的方法

windows

  • 任务管理器可以查看进程和线程数,也可以用来杀死进程
  • tasklist 查看进程
  • taskkill 杀死进程linux
  • ps -fe 查看所有进程
  • ps -fT -p <PID> 查看某个进程(PID)的所有线程
  • kill 杀死进程
  • top 按大写 H 切换是否显示线程
  • top -H -p <PID> 查看某个进程(PID)的所有线程

linux

  • ps -fe 查看所有进程
  • ps -fT -p <PID> 查看某个进程(PID)的所有线程
  • kill 杀死进程
  • top 按大写 H 切换是否显示线程
  • top -H -p <PID> 查看某个进程(PID)的所有线程

Java

  • jps 命令查看所有 Java 进程
  • jstack <PID> 查看某个 Java 进程(PID)的所有线程状态
  • jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)

线程的生命周期

操作系统层面

  • 初始状态:指的是线程在编程语言的层面已经创建,而不是操作系统。
  • 可运行状态:操作系统创建了线程并,可以分配CPU进行执行。
  • 运行状态:操作系统将空闲的CPU分配给了线程。
  • 睡眠状态:运行状态的线程调用了阻塞的API,状态就会变为休眠,并释放CPU的时间片。
  • 终止状态:线程执行结束或发生异常。

Java层面

  • NEW(初始化状态)
  • RUNNABLE(可运行状态+运行状态)
  • BLOCKED(阻塞状态)
  • WAITING(无时限等待)
  • TIMED_WAITING(有时限等待)
  • TERMINATED(终止状态)

java层面的阻塞、无时限等待、有时限等待,对应的就是操作系统中的睡眠状态。只要线程处于这三种状态中的一种,就无法获得cpu时间片执行线程。

Thread常用方法

sleep方法

public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {

                System.out.println("=======");
            }
        });
        log.debug("线程状态:{}", thread.getState());
        thread.start();
        log.debug("线程状态:{}", thread.getState());
        Thread.sleep(100);

        log.debug("线程状态:{}", thread.getState());


    }

未调用sleep之前

调用之后运行结果

调用 sleep 会让当前线程从 Running 进入TIMED_WAITING状态,不会释放对象锁其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出InterruptedException,并且会清除中断标志

睡眠结束后的线程未必会立刻得到执行sleep当传入参数为0时,和yield相同

yield方法

   public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                LockSupport.park();
            }
        });
        log.debug("线程状态:{}", thread.getState());
        thread.start();
        log.debug("线程状态:{}", thread.getState());
        Thread.yield();
        log.debug("线程状态:{}", thread.getState());


    }

yield会释放CPU资源,让当前线程从 Running 进入 Runnable状态,让优先级更高(至少是相同)的线程获得执行机会,不会释放对象锁; 假设当前进程只有main线程,当调用yield之后,main线程会继续运行,因为没有比它优先级更高的线程;具体的实现依赖于操作系统的任务调度器。

join方法

package bat.ke.qq.com.thread;

public class ThreadJoinDemo {

    public static void main(String[] sure) throws InterruptedException {

        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t begin");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t finished");
            }
        });
        long start = System.currentTimeMillis();
        t.start();
        //主线程等待线程t执行完成
        t.join();

        System.out.println("执行时间:" + (System.currentTimeMillis() - start));
        System.out.println("Main finished");
    }
}

等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景,join可以理解成是线程合并,当在一个线程调用另一个线程的join方法时,当前线程阻塞等待被调用join方法的线程执行完毕才能继续执行,所以join的好处能够保证线程的执行顺序,但是如果调用线程的join方法其实已经失去了并行的意义,虽然存在多个线程,但是本质上还是串行的,最后join的实现其实是基于等待通知机制的。

停止线程

stop方法

stop()方法已经被jdk废弃,原因就是stop()方法太过于暴力,强行把执行到一半的线程终止

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + "获取锁");
                    try {
                        Thread.sleep(60000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + "执行完成");
            }
        });
        thread.start();
        Thread.sleep(2000);
        // 停止thread,并释放锁
        thread.stop();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "等待获取锁");
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + "获取锁");
                }
            }
        }).start();

    }

 中断机制

Java没有提供一种安全、直接的方法来停止某个线程,而是提供了中断机制。中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理。被中断的线程拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。

API的使用

interrupt(): 将线程的中断标志位设置为true,不会停止线程

isInterrupted(): 判断当前线程的中断标志位是否为true,不会清除中断标志位

Thread.interrupted():判断当前线程的中断标志位是否为true,并清除中断标志位,重置为fasle

static int i = 0;

    public static void main(String[] args)  {
        System.out.println("begin");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public  void run() {
                while (true) {
                    i++;
                    System.out.println(i);

                    if (Thread.interrupted()  ) {
                        System.out.println("=========");
                    }
                    if(i==10){
                        break;
                    }

                }
            }
        });

        t1.start();
        //不会停止线程t1,只会设置一个中断标志位 flag=true
        t1.interrupt();

    }

结果,中断标志位生效为true,且线程未停止

sleep感受中断

 

public static void main(String[] args)  {
        System.out.println("begin");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public  void run() {
                while (true) {
                    i++;
                    System.out.println(i);
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        //重新设置线程中断状态为true 
                        //Thread.currentThread().interrupt();
                    }

                    //Thread.interrupted()  清除中断标志位
                    //Thread.currentThread().isInterrupted() 不会清除中断标志位
                    if (Thread.interrupted()  ) {
                        System.out.println("=========");
                    }
                    if(i==10){
                        break;
                    }

                }
            }
        });

        t1.start();
        //不会停止线程t1,只会设置一个中断标志位 flag=true
        t1.interrupt();

    }

结果

处于休眠中的线程被中断,线程是可以感受到中断信号的,并且会抛出一个InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false,可在在catch的时候重新设置

public static void main(String[] args)  {
        System.out.println("begin");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public  void run() {
                while (true) {
                    i++;
                    System.out.println(i);
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        //重新设置线程中断状态为true 
                        Thread.currentThread().interrupt();
                    }

                    //Thread.interrupted()  清除中断标志位
                    //Thread.currentThread().isInterrupted() 不会清除中断标志位
                    if (Thread.interrupted()  ) {
                        System.out.println("=========");
                    }
                    if(i==10){
                        break;
                    }

                }
            }
        });

        t1.start();
        //不会停止线程t1,只会设置一个中断标志位 flag=true
        t1.interrupt();

    }

sleep可以被中断 抛出中断异常:sleep interrupted, 清除中断标志位

wait可以被中断 抛出中断异常:InterruptedException, 清除中断标志位

线程间通信

volatile

private static volatile boolean flag = true;

    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    if (flag){
                        System.out.println("trun on");
                        flag = false;
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    if (!flag){
                        System.out.println("trun off");
                        flag = true;
                    }
                }
            }
        }).start();
    }

执行结果

等待唤醒(等待通知)机制

wait+notify(notifyAll)

private static Object lock = new Object();
    private static  boolean flag = true;

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    while (flag){
                        try {
                            System.out.println("wait start .......");
                            //等待
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    System.out.println("wait end ....... ");
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                if (flag){
                    synchronized (lock){
                        if (flag){

                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            //通知
                            lock.notifyAll();
                            System.out.println("notify .......");
                            flag = false;
                        }

                    }
                }
            }
        }).start();
    }

运行结果

使用notify唤醒在并发场景下可能会唤醒其他等待的线程,故多并发场景下一般使用notifyAll

LockSupport.park+unpark

import java.util.concurrent.locks.LockSupport;

public class LockSupportTest {

    public static void main(String[] args) {
        Thread parkThread = new Thread(new ParkThread());
        parkThread.start();

        System.out.println("唤醒parkThread");
        //为指定线程parkThread提供“许可”
        LockSupport.unpark(parkThread);
    }

    static class ParkThread implements Runnable{

        @Override
        public void run() {
            System.out.println("ParkThread开始执行");
            // 等待“许可”
            LockSupport.park();
            System.out.println("ParkThread执行完成");
        }
    }

结果

LockSupport是JDK中用来实现线程阻塞和唤醒的工具,线程调用park则等待“许可”,调用unpark则为指定线程提供“许可”。使用它可以在任何场合使线程阻塞,可以指定任何线程进行唤醒,并且不用担心阻塞和唤醒操作的顺序,但要注意连续多次唤醒的效果和一次唤醒是一样的

线程实现方式

继承Thread类

public class ThreadDemo extends Thread {

    private String name;

    public ThreadDemo(String name) {

        this.name = name;
    }

    @Override
    public void run() {

        System.out.println(name);
    }


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

}

实现Runable接口

   Runnable runnable = new Runnable() {
            @Override
            public void run() {
                log.debug("通过Runnable方式执行任务");
            }
        };
        new Thread(runnable).start();

使用Callable(带返回值)

 FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
                log.debug("通过Callable方式执行任务");
                Thread.sleep(3000);
                return "返回任务结果";
            }
        });

        new Thread(task).start();

使用Lambda 

new Thread(() ‐ > 
System.out.println(Thread.currentThread().getName())).start();

 start源码

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

小结:由上可知,不论创建线程是何种方式,最终都是创建了Thread的实例,调用了start方法进行启动,最终实现的是run方法中的逻辑

线程实现原理

Java线程属于内核级线程,因为java线程在创建的时候,先创建的是java层面的线程实例,然后再创建JVM层面的线程,最后再创建操作系统层面的线程。

内核级线程(Kernel Level Thread ,KLT):它们是依赖于内核的,即无论是用户进程中的线程,还是系统进程中的线程,它们的创建、撤消、切换都由内核实现。

用户级线程(User Level Thread,ULT):操作系统内核不知道应用线程的存在。

线程创建和启动流程

  • 创建Java线程Thread,调用start启动
  • 调用本地方法start0,调用JVM_startThread,创建jvm层面的线程
  • 调用new javaThread(),根据JVM对应的系统调用对应的os:create_thread方法,创建操作系统层面的线程
  • 初始化线程状态,调用sys-wait进行等待
  • 调用本地方法启动JVM层面线程,状态为RUNABLE,然后再调用操作系统层面的线程的start进行启动
  • 唤醒JVM中等待的线程
  • 回调Java中Thread的run方法

线程调度

线程调度是指系统为线程分配处理器使用权的过程,主要调度方式分两种,分别是协同式线程调度和抢占式线程调度

协同式线程调度

线程执行时间由线程本身来控制,线程把自己的工作执行完之后,要主动通知系统切换到另外一个线程上。最大好处是实现简单,且切换操作对线程自己是可知的,没啥线程同步问题。坏处是线程执行时间不可控制,如果一个线程有问题,可能一直阻塞在那里。

抢占式线程调度

每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(Java中,Thread.yield()可以让出执行时间,但无法获取执行时间)。线程执行时间系统可控,也不会有一个线程导致整个进程阻塞。

协程

协程,英文Coroutines, 是一种基于线程之上,但又比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行),具有对内核来说不可见的特性。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。

特点

  • 协程的特点在于是一个线程执行,和多线程比,线程的切换由操作系统调度,协程由用户自己进行调度,因此减少了上下文切换,提高了效率。
  • 线程的默认stack大小是1M,而协程更轻量,接近1k。因此可以在相同的内存中开启更多的协程。
  • 不需要多线程的锁机制:因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

注意: 协程适用于被阻塞的,且需要大量并发的场景(网络io)。不适合大量计算的场景

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值