美文网首页
Java 并发编程

Java 并发编程

作者: BigCoder | 来源:发表于2022-06-22 14:53 被阅读0次

[TOC]

进程与线程

进程是程序运行资源分配的最小单位。

进程是操作系统进行资源分配的最小单位,其中资源包括CPU、内存空间、磁盘等,同一进程中的多条线程共享该进程中的全部系统资源,而进程和进程之间是相互独立的。进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。进程分为系统进程和用户进程,凡是用于完成操作系统的各种功能的进程就是系统进程;用户进程就是所有由你启动的进程。

线程是CPU调度的最小单位,必须依赖于进程而存在。

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源,比如程序计数器(一组寄存器和栈),但是它可与同属一个进程的其他线程共享进程所有的全部资源。

任何一个程序都要创建线程,特别是Java,不管任何程序都要启动一个main函数的主线程。JavaWeb开发里面的定时任务、定时器、JSP和Servlet、异步消息处理机制、远程访问接口RM等,任何一个监听事件等都离不开线程和并发的知识。

CPU与线程

CPU核心数与线程数的关系

目前主流CPU有双核、三核和四核,六核也在2010年发布,增加核心数目就是为了增加线程数目,因为操作系统是通过线程来执行任务的,一般情况下它们是1:1的关系,也就是说四核CPU一般拥有四个线程,但Intel引入超线程技术后,使核心数与线程数形成1:2的关系。

CPU时间片轮转机制

我们平时在开发的时候,感觉并没有受CPU核心数的限制,想启动线程就启动线程,哪怕是在单核CPU上,为什么?这就要靠CPU的时间片轮转机制了。

时间片轮转调度是一种最古老、最简单、最公平且使用最广的算法,又称RR调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。当该进程的时间片运行完以后,将让出CPU执行权,给其他进程使用。

并行与并发

并行:指应用能够同时执行不同的任务。想象一条高速公路,有四个车道,那么此条高速公路可以并排行驶小于等于四辆车。CPU也是这个原理,核心数或者线程数就相当于高速公路上的车道,能同时处理的任务需要线程数或者核心数支持。

并发:指应用能够交替执行不同的任务。当谈论并发的时候,一定要加个单位时间。比如单CPU核心下执行多线程并非是同时执行多个任务,而是CPU以不可察觉到的速度在进行时间片轮转。

Java里的线程

Java是一门支持多线程的语言,运行中的Java程序至少有一个主线程。

启动线程的方式

  1. 继承Thread
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("执行线程--------------");
    }
}

public static void main(String[] args) {
     MyThread myThread = new MyThread();
     myThread.start();
}
  1. 实现Runnable(实现Callable,这种方式其实可以看做是实现Runnable,不过它能拿到异步的返回结果)
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("执行线程--------------");
    }
}

public static void main(String[] args) {
    Thread thread = new Thread(new MyRunnable());
    thread.start();
}
public class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        System.out.println("执行线程--------------");
        return 1;
    }
}

public static void main(String[] args) {
    //FutureTask实现了RunnableFuture,RunnableFuture实现了Runnable,所以可以看做是实现Runnable启动线程。
    FutureTask<Callable> future = new FutureTask(new MyCallable());
    Thread thread = new Thread(future);
    thread.start();
}

线程的结束

  1. run方法执行完

  2. run方法中抛出了一个未处理的异常导致线程结束。

线程的方法

interrupt():对线程进行中断操作,将会设置线程的中断状态为true,至于中断后线程是死亡,还是等待,还是继续运行,取决于线程本身。线程会时不时的检测这个中断标志位,以判断线程是否应该中断。更确切地说,如果线程被wait,join和sleep三种方法之一阻塞,此时调用interrupt,那么该线程将抛出一个InterrputedException中断异常,从而提早地终结被阻塞状态。如果线程没有被阻塞,这时调用interrupt不会起作用,直到执行wait,join,sleep时才马上抛出InterrputedException。

Thread.interrupted():静态方法,内部实现是调用isInterrupted(),并且会重置当前线程的中断状态。

isInterrupted():实例方法,不会重置当前线程的中断状态。作用与Thread.interrupted()相同,获取调用线程的中断状态。

yield():静态方法,使当前线程让出CPU占有权,但让出的时间是不可设定的,也不会释放锁资源。所有执行yield()的线程有可能在进入到可执行态后马上又被执行。

join():实例方法,把指定线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如线程A中调用了线程B的join(),直到线程B执行完后才会继续执行线程A。

线程的生命周期

线程生命周期.png

线程的状态

初始(NEW):新创建了一个线程对象,还没有调用start()。

运行(RUNNABLE):Java线程中将就绪(READY)和运行中(RUNNING)两种状态统称为运行。就绪中的线程在获取到CPU时间片后进入运行中。

阻塞(BLOCKED):表示线程阻塞于锁。

等待(WATING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

超时等待(TIMED_WATING):该状态不同于WATING,它可以在指定时间后自行返回。

终止(TERMINATED):表示该线程已经执行完毕。

典型线程问题

  1. 死锁

    两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或者说产生了死锁。

    死锁发生的条件:

    1). 互斥条件:指线程对所分配的资源进行排他性使用,即在一段时间内某资源只由一个线程占用,如果此时还有其他线程请求该资源,则请求者只能等待,直至占有该资源的线程释放资源。

    2). 请求和保持条件:指线程已经至少保持一个资源,但又提出了新的资源请求,而该资源已经被其他线程占有,此时请求线程阻塞,但又对自己已获得的其他资源保持不释放。

    3). 不剥夺条件:指线程已获得资源,在未使用完之前,不能被剥夺,只能在使用完后由自己释放。

    4). 环路等待条件:指在发生死锁时,必然存在一个线程与资源的环形链,即线程P0正在等待P1占用的资源,P1正在等待P2占用的资源,P2正在等待Pn占用的资源,而Pn则正在等待P1占用的资源。

    解决死锁的方案

    1). 内部通过顺序比较,确定拿锁的顺序。

    2). 采用尝试拿锁的机制。

  2. 活锁

    两个线程在尝试拿锁的机制中,发生多个线程之间互相谦让,同一个线程总是拿到同一把锁,当这个线程继续往下执行想要拿到另一把锁时,却因为拿不到而释放第一把锁,下次还是这个线程拿到了第一把锁,却又不能往下执行,如此周而复始,导致线程并未阻塞,但是两个线程都不会往下执行了。

    解决办法:每个线程休眠随机的时间,错开拿锁时间。

  3. 线程饥饿

    低优先级的线程总是拿不到CPU执行时间。

ThreadLocal详解

相关文章

网友评论

      本文标题:Java 并发编程

      本文链接:https://www.haomeiwen.com/subject/ghsamltx.html