Java多线程 -- 线程基础

Java多线程

用户态和内核态,如何切换,为什么要切换?

什么是系统中断?

进程与线程的区别?

串行 -> 批处理 -> 进程(进程独占内存空间,保存各自运行状态,相互间互不干扰且可以互相切换,使得操作系统可以并发处理任务) -> 线程(共享进程的内存资源,相互间切换更快速,比进程粒度更小,使进程内的子任务可以并发执行)

  • 进程是资源分配的最小单位,线程是CPU调度的最小单位;

    • 所有与进程相关的资源,都被记录在PCB(进程控制块)中:

在这里插入图片描述

  • 进程是抢占处理机的调度单位,不同的进程有不同的虚拟地址空间,同一进程内的不同线程共享该地址空间;线程属于某个进程,同一进程下线程将共享该进程资源;

  • 线程只由堆栈寄存器、程序计数器和TCP(线程控制块)组成:

在这里插入图片描述

寄存器可以存储线程内的局部变量,但不能存储其它线程的相关变量。

区别

  1. 进程是资源分配的基本单位;线程是CPU调度的基本单位;
  2. 进程有独立的地址空间,相互之间并不影响,某个进程崩溃之后并不会影响其它进程的运行;线程没有独立的地址空间,只是进程的不同执行路径,当某个线程崩溃后,其所在进程也会挂掉,因此同一进程下的所有线程也都将停止运行;因此多进程程序比多线程程序更健壮;
  3. 进程的切换比线程的切换开销大。

Java中进程和线程的关系

  • Java对操作系统提供的功能进行封装,包括进程和线程;
  • 每运行一个Java程序会产生一个进程,每个进程包含至少一个线程(主线程);
  • 每个进程对应一个JVM实例,多个线程共享JVM里的堆
  • Java采用单线程编程模型,程序会自动创建主线程;
  • 主线程可以创建子线程,原则上要后于子线程完成执行。

线程的start()run()方法的区别

  1. run()方法并不会创建一个线程,将在调用线程中执行,仅仅只是一个普通方法;
  2. start()方法真正创建一个线程,并会在新建线程中调用run()方法。

从源码层面来看:

  1. Thread类中找到start()方法,发现该方法中调用了一个native方法start0()

  2. Open JDK上找到该方法,以jdk8u版本为例:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

然后进入src/share/native/java/lang/目录,点击Thread.c文件:

在这里插入图片描述

可以看到start0()方法调用了jvm.h中的JVM_StartThread方法,然后我们定位到JVM的jvm.cpp文件:

返回到http://hg.openjdk.java.net/jdk8u/,点击hotspot

在这里插入图片描述

然后点击browse,进入到src/share/vm/prims目录,打开jvm.cpp文件,定位到JVM_StartThread方法:

在这里插入图片描述

发现该方法中会通过传入thread_entry来新建一个新的Java线程,然后定位到thread_entry

在这里插入图片描述

可以看到,该方法会调用run()方法。

ThreadRunnable是什么关系

  1. Runnable是一个接口,只声明了一个run()方法,而Thread是一个实现了Runnable接口的类;
  2. 由于Java是单继承的,推荐多使用Runnable接口。
  3. 如何给run()方法传参:
    1. 构造函数传参;
    2. 成员变量传参(setter);
    3. 回调函数传参:在run()方法中调用。

创建线程的三种方法

  1. 实现Runnable接口,并实现run()方法,然后作为参数传递给Thread实例:

    MyRunnable.java

    public class MyRunnable implements Runnable {
        
        @Override
        public void run() {
            // do something
        }
    }
    

    Main.java

    public class Main {
        public static void main(String[] args) {
            Thread thread = new Thread(new MyRunnable());
            thread.start();
        }
    }
    
  2. 继承Thread类:

    public MyThread extends Thread {
        
        @Override
        public void run() {
            // do something
        }
    }
    

    Main.java

    public class Main {
        public static void main(String[] args) {
            Thread thread = new MyThread();
            thread.start();
        }
    }
    
  3. 实现Callable接口:

    MyCallable.java

    public class MyCallable implements Callable<String> {
        
        @Override
        public String call() throws Exception {
            Thread.currentThread().sleep(5000);
            return "Hello World";
        }
    }
    

    Main.java

    public class Main {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            FutureTask<String> task = new FutureTask<String>(new MyCallable());
            new Thread(task).start();
            // 当call()未执行完毕时,get()会阻塞直到其执行完毕
            System.out.println(task.get());
        }
    }
    

如何实现主线程等待子线程返回处理结果

  1. 主线程等待法:当待处理变量太多时,会导致代码变得臃肿,降低程序可读性,且无法做到精准控制主线程等待的时间;

  2. 使用Thread类中的join()方法阻塞当前线程以等待子线程处理完毕:比主线程等待法实现简单,控制粒度不够细;

  3. 实现Callable接口,通过FutureTask或线程池获取:

    FutureTask见上面创建线程方式三;

    线程池:

    public class Main {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            ExecutorService cachedThreadPool = new Executors.newCachedThreadPool();
            Future<String> future = cachedThreadPool.submit(new MyCallable());
            
            try {
            	System.out.println(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } finally {
                cachedThreadPool.shutdown();
            }
        }
    }
    

线程的状态

  1. NEW:新建态,创建后未启动的线程的状态,即new Thread()创建线程对象后调用start()方法启动线程前;
  2. RUNNABLE:运行态,调用start()后正在JVM中执行的线程,包含操作系统中线程的RunningReady态,即正在占用CPU或者等待CPU为其分配时间片;
  3. WAITING:无限期等待,不会被分配CPU执行时间,需要显式被唤醒;
    • 没有设置timeout参数的Object.wait()方法;
    • 没有设置timeout参数的Thread.join()方法;
    • LockSupport.park()方法。
  4. TIMED_WAITING:有限期等待,在一定时间后由系统自动唤醒;
    • Thread.sleep(timeout),通过sleep()方法进入休眠的线程不会释放持有的锁;
    • 设置了timeout参数的Object.wait(timeout)方法,通过wait()方法,在调用wait()方法之前必须获得对象上的锁,否则程序会在运行时抛出IllegalMonitorStateException异常,通过wait()方法进入休眠的线程,会释放该对象的锁;
    • 设置了timeout参数的Thread.join(timeout)方法;
    • LockSupport.parkNanos()方法;
    • LockSupport.parkUntil()方法。
  5. BLOCKED:阻塞态,等待获取排它锁的线程的状态;
  6. TERMINATED:已终止线程的状态,线程已经结束执行,在一个已经终止的线程上调用start()方法会抛出java.lang.IllegalThreadStateException

sleep()wait()的区别

  1. sleep()方法属于Thread类,且必须传入参数,表示休眠时长,线程会进入TIMED_WAITING有限期等待状态;
  2. wait()方法属于Object类,当调用无参wait()方法时,线程会进入WAITING无限期等待状态,当调用有参wait(long timeout)时,线程将进入TIMED_WAITING有限期等待状态。

二者都会让出CPU的使用权:

  1. 调用Thread.sleep(milli)方法进入休眠的线程不会释放其持有的锁;

  2. 调用thread.wait()方法进入休眠的线程会释放其持有的锁。

  3. sleep()方法可以在任何地方使用;

  4. wait()方法必须在synchronized方法或synchronized块中使用,即在调用wait()方法之前必须获得对象上的锁,否则程序会在运行时抛出IllegalMonitorStateException异常。

notify()notifyAll()的区别

JVM中的每个对象都会有两个数据结构:

  • 锁池:EntryList;
  • 等待池:WaitSet。

对象的锁池会存储等待该对象锁的线程,如线程A已经拥有了某个对象(不是类)的锁,而其它线程B、C想要调用这个对象的某个synchronized方法(或者块),由于B、C线程在进入对象的synchronized方法(或者块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正被线程A所占用,此时B、C线程就会被阻塞,进入该对象的锁池。

对象的等待池会存储不会竞争该对象锁的线程,如线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁,同时线程A就会进入该对象的等待池中,进入到等待池中的线程不会去竞争该对象的锁。

notify()notifyAll()的区别:

  1. obj.notifyAll()会让等待池的所有线程全部进入锁池去竞争获取锁的机会;
  2. obj.notify()只会从等待池中随机选取一个线程进入锁池去竞争获取锁的机会。

Thread.yield()方法

让当前调用Thread.yield()方法的线程让出CPU的执行权,与其它线程一起争夺CPU使用权。

interrupt()方法

已经被抛弃的方法:stop()方法,因为由外部stop()来停止线程,可能会导致正在工作的线程突然被中断,导致不可预估的后果。

调用t1.interrupt(),通知线程t1应该中断了:

  • 如果线程已处于被阻塞状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常;
  • 如果线程处于正常活动状态,那么会将线程的中断标志设置为true,被设置中断标志的线程将继续正常运行,不受影响

由目标线程自主检查本线程的中断标志位,如果被设置为中断就自行停止线程。

线程状态转换

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值