java多线程小结

## 一、并行 并发 和线程 进程

  1. 并行与并发

    • 并发:指两个或多个事件在同一个时间段内**发生。
    • 并行:指两个或多个事件在同一时刻发生(同时发生)。
  2. 线程 进程

    • 一个程序运行后至少有一个进程,一个进程中可以包含多个线程
  3. 线程调度:

    • 分时调度

      所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

    • 抢占式调度

      优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

      • 设置线程的优先级

二、多线程

  1. 原理:

    • 多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈
    • 当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了
  2. Thread类:

    1. 构造方法:
      • public Thread() :分配一个新的线程对象。
      • public Thread(String name) :分配一个指定名字的新的线程对象。
      • public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
      • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
    2. 常用方法:
      • public String getName() :获取当前线程名称。
      • public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
      • public void run() :此线程要执行的任务在此处定义代码。
      • public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
      • public static Thread currentThread() :返回对当前正在执行的线程对象的引用。
  3. Runnable类

    Runnable对象仅仅作为Thread对象的target,

    Runnable实现类里包含的run()方法仅作为线程执行体。

    ** 而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法**

  4. Thread类和Runnable类的区别(实现Runnable接口比继承Thread类所具有的优势)

    1. 适合多个相同的程序代码的线程去共享同一个资源。
    2. 可以避免java中的单继承的局限性。
    3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
    4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
  5. 拓展:

    在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。每当使用 java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程

三、线程安全

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写 操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步, 否则的话就可能影响线程安全

线程同步:

  1. 同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问

    • 格式:
    synchronized(同步锁){
    	需要同步操作的代码
    }
    
    • 同步锁:
      • 在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着 (BLOCKED)
      • 锁对象可以是任意类型
      • 多个线程对象要使用同一把锁
  2. 同步方法

    同步锁:

    • 对非静态方法,同步锁就是this
    • 对静态方法,使用当前方法所在类的字节码对象
  3. 锁机制(LOCK)

    public void lock() //加同步锁
    public void lock() //释放同步锁
    

    线程状态:

    当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态

    • NEW(新建)
    • Runnable(可运行)
    • Blocked(锁阻塞)
    • Waitting(无线等待)
    • Timed Waitting(计时等待)
    • Timinated(被终止)

    image-20200508192410088

四、等待唤醒机制

等待唤醒中的方法

等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:

  1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
  2. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
  3. notifyAll:则释放所通知对象的 wait set 上的全部线程。

注意:

哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。

总结如下:

  • 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
  • 否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态

调用wait和notify方法需要注意的细节

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

五、线程池

  1. 使用:

    Java里面线程池的顶级接口是java.util.concurrent.Executors,但是严格意义上讲Executors并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService

  2. Executors类中有个创建线程池的方法如下:

    • public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)

    获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:

    • public Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行

      Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。

    使用线程池中线程对象的步骤:

    1. 创建线程池对象。
    2. 创建Runnable接口子类对象。(task)
    3. 提交Runnable接口子类对象。(take task)
    4. 关闭线程池(一般不做)。
posted @ 2020-05-08 19:26  归来也无风雨也无晴  阅读(132)  评论(0编辑  收藏  举报