美文网首页
多线程基础篇

多线程基础篇

作者: _Rice_ | 来源:发表于2019-01-22 11:16 被阅读0次

基本概念

进程:一个执行单元,PC和移动设备指一个程序或者应用
线程:操作系统能进行运算调度的最小单位。是进程的实际运作单位。
多线程:并发执行,比如单线程完成一个任务100毫秒,十个线程并发执行只需10毫秒

image.png

补充:
1、join底层是调用wait,会释放锁

2、sleep、yield不会释放锁

线程包括五种状态:

  • 新建状态(New):线程被创建
  • 就绪状态(Runnable):线程调用start方法,随时可获取CPU使用权
  • 运行状态(Running):已获得CPU使用权的线程
  • 阻塞状态(Blocked):失去CPU使用权
  • 死亡状态(Dead):线程执行完或者异常退出

1、实现线程的方式:

  • 继承Thread重写run方法
  • 实现Runnable--run方法
  • 实现Callable--call方法

2、synchrized关键字

2.1、实例锁和全局锁

实例锁:是对象锁
全局锁:是类锁,无论有多少个实例对象,都共享一个锁

2.2、当一个线程获取了锁,其他线程访问synchrized修饰的方法或者代码块将会被阻塞,但仍然可以访问非同步代码。被阻塞的线程需要占有锁线程释放锁,才有可能重新获取锁,进入就绪状态。

2.3、当占有锁的线程出现异常,JVM会自动释放锁

建议:synchrized代码块使用起来会比synchronized方法要灵活得多,更高效。因为synchronized方法是对整个方法进行synchronized同步,而synchronized代码块只需对部分代码同步

3、lock

3.1、synchrized和lock的区别

  • Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现
  • synchrized自动释放锁,lock手动释放锁,需要在finally块中释放锁(unlock)
  • lock可以知道有没有成功获取锁,如果没有获取锁可以执行其他代码,synchrized不行(tryLock)
  • lock可以让等待锁的线程相应中断(lockInterruptibly),而synchrized会一直等待
  • Lock可以提高多个线程进行读操作的效率。(ReadWriteLock)

3.2、lock相关方法

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

lock() 获取锁

tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。

tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。

unlock 释放锁。获取锁和释放锁是配对使用的,如果没有及时释放锁,那么线程就会发生死锁。unlock通常是放在finally中。

newCondition 创建Condition对象,Condition会在4.6讲到

ReentrantLock是lock唯一实现类
3.4、ReadWriteLock读写锁

ReadWriteLock也是一个接口

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

readLock()获取读取锁
writeLock()获取写锁

可以实现多线程进行读操作,值得注意的是

  • 如果有一个线程占用了读操作,申请写操作的线程,必须等待读线程释放锁。
  • 如果有写线程在操作,其他申请读锁或者写锁的线程必须等待写线程释放写锁
ReentrantReadWriteLock实现ReadWriteLock接口
3.5 公平锁和非公平锁

公平锁:多个线程等待一个锁,等待最久的线程先获取锁
非公平锁:跟公平锁相反,无法保证等待最久的线程先获取锁。

synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。
对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。

ReentrantLock和ReentrantReadWriteLock相似,以ReentrantLock为例:

在ReentrantLock中定义了2个静态内部类,一个是NotFairSync,一个是FairSync,分别用来实现非公平锁和公平锁。

可以通过以下构造函数设置锁的公平性

ReentrantLock lock = new ReentrantLock(true);

另外在ReentrantLock类中定义了很多方法,比如:

  • isFair() //判断锁是否是公平锁
  • iisLocked() //判断锁是否被任何线程获取了
  • iisHeldByCurrentThread() //判断锁是否被当前线程获取了
  • ihasQueuedThreads() //判断是否有线程在等待该锁

4、Thread

4.1 start方法

start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。

4.2 run方法

run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

4.3 yield

线程让步,让出CPU权,从运行状态进入到就绪状态,不会释放锁。让其他具有相同优先级的线程获取执行权,但不保证其他具有相同优先级的线程一定能获取执行权,也可以是当前线程重新获取执行权继续运行

4.4 sleep

线程休眠,从运行状态进入进入阻塞状态,但不会释放锁,休眠结束,会由阻塞状态变成就绪状态

4.5 join

让子线程执行完,再执行父线程,由于底层是调用wait方法,所以会释放锁

4.6 wait、norify

在Object.java中,定义了wait(), notify()和notifyAll()等接口

  • notify()//唤醒在此对象监视器上等待的单个线程。
  • notifyAll() //唤醒在此对象监视器上等待的所有线程。
  • wait() // 让当前线程处于“等待(阻塞)状态”(当前线程指正在cpu上运行的线程),“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
  • wait(long timeout) // 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。

注意:wait、notify调用的前提是当前线程是synchrized锁的拥有者

为什么notify(), wait()等函数定义在Object中,而不是Thread中?

notify和wait是通过“同步锁进行关联的”,只有notify才能唤醒wait线程,但是等待线程还不能执行,必须等到唤醒线程释放锁,才可能重新获取锁从而继续运行。

补充学习condition

condition和wait、notify区别

  • 1、Condition的await()、signal()、signalAll()对应Object的wait(), notify()和notifyAll()
  • 2、wait、norify是和“同步锁”synchronized关键字捆绑使用,condition是和"互斥锁"/"共享锁"ReentrantLock捆绑使用。
  • 3、Condition依赖于Lock接口,通过lock.newCondition获取,Condition可以有多个。(例如生产消费者问题的仓库空消费者线程等待,仓库满生产者线程等待)

Condition能够更强大的控制多线程的休眠和唤醒,同一个锁,可以创建多个Condition,在不同情况下使用不同Condition

4.7 interrupt 线程中断和线程终止方式

interrupt方法可以中断阻塞线程,不能中断运行线程

4.7.1 终止处于阻塞状态的线程
@Override
public void run() {
    try {
        while (true) {
            // 执行任务...
        }
    } catch (InterruptedException ie) {  
        // 由于产生InterruptedException异常,退出while(true)循环,线程终止!
    }
}
4.7.2 终止处于运行状态的线程

interrupt方法不能中断运行线程,所以如果我们需要中断运行线程,需要额外增加标记(可以借助isInterrupted方法)

@Override
public void run() {
    while (!isInterrupted()) {
        // 执行任务...
    }
}
4.7.3 interrupted() 和 isInterrupted()的区别

interrupted() 和 isInterrupted()都能够用于检测对象的“中断标记”。区别是interrupted()还会清楚中断标记,isInterrupted()不会

4.8 线程属性方法

4.8.3 getId

用来得到线程ID

4.8.3 getName和setName

用来得到或者设置线程名称。

4.8.3 getPriority和setPriority

获取优先级和设置优先级

线程优先级范围是0~10,默认优先级是5,“高优先级线程”会优先于“低优先级线程”执行。

4.8.4 setDaemon和isDaemon

设置守护线程和判断是否是守护线程

用户线程和守护线程(也就是后台线程),垃圾收集器线程就是守护线程,当只有守护线程在运行或者调用exit方法,jvm会自动退出。

5、volatile

由于volatile跟java内存模型有关,先了解内存模型相关概念

内存模式:程序运行过程中的临时变量存储在主存里(物理内存),而变量CPU执行速度远大于从内存中存取数据,如果每次操作数据都要和内存交互,会大大降低指令执行速度。于是就有了高速缓存。当程序运行时,会复制一份到高速缓存中,CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。

JAVA内存模式:每个线程都有自己的工作内存(类似高速缓存),运行时会复制一份到工作内存中,线程对变量的操作必须在工作内存中进行,不能直接对主存进行操作。

5.1 并发的三个概念

想要并发正确执行必须保证其原子性、可见性、有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。

5.1.1 原子性

一个操作要么都执行,要么都不执行。

x = x+1;

举个例子,这里就有三个操作,读取x的值,x+1,写入新值

原子性操作有:读取、赋值、synchrized或者lock实现、JUC原子类

5.1.2 可见性

多个线程共享一个变量,当一个线程改变了这个变量的值,其他线程能立即看到修改的值

volatile具备可见性

可见性操作有:volatile、synchrized或者lock实现

5.1.3 有序性

处理器为了提升运行效率,可能会对执行顺序进行重排序,会保证程序最终结果会和代码顺序执行结果相同。

原因在于,处理器在进行重排序时,会考虑到指令之间的依赖性。如果指令2必须用到指令1的结果,那么处理器会保证指令1在指令2之前执行。

volatile具备有序性

有序性操作有:volatile、synchrized或者lock实现

5.2 volatile

特点:可见性,有序性(禁止指令重排序)

原理和实现机制

下面这段话摘自《深入理解Java虚拟机》:

“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”

lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

  • 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
  • 它会强制将对缓存的修改操作立即写入主存;
  • 如果是写操作,它会导致其他CPU中对应的缓存行无效。其他线程再次读取共享变量会从内存读取

参考:
Java多线程系列目录(共43篇)
随笔分类 - Java并发编程

相关文章

  • java多线程相关

    (一) 基础篇 01.Java多线程系列--“基础篇”01之 基本概念 02.Java多线程系列--“基础篇”02...

  • Java多线程系列目录(共43篇)-转

    最近,在研究Java多线程的内容目录,将其内容逐步整理并发布。 (一) 基础篇 Java多线程系列--“基础篇”0...

  • Unity C#基础之 多线程的前世今生(下) 扩展篇

    在前面两篇Unity C#基础之 多线程的前世今生(上) 科普篇和Unity C#基础之 多线程的前世今生(中) ...

  • OC底层知识(十一) : 多线程

    一、简介:多线程在之前进行过一篇详细的基础博客 iOS多线程 二、多线程的基础知识回顾 1.1、iOS中的常见多线...

  • 多线程(二)、内置锁synchronized

    前言 在上一篇 多线程(一)、基础概念及notify()和wait()的使用 文章中我们讲了多线程的一些基础概念...

  • 多线程基础篇

    多线程 多线程概念 进程:正在进行中的程序 线程:是进程中一个负责程序执行的控制单元(执行路径)一个进行中可以有多...

  • 多线程基础篇

    基本概念 进程:一个执行单元,PC和移动设备指一个程序或者应用线程:操作系统能进行运算调度的最小单位。是进程的实际...

  • 多线程(四)、Android多线程使用及AsyncTask源码分

    本篇是多线程系列的第四篇,如果对前三篇感兴趣的也可以去看看。多线程(一)、基础概念及notify()和wait()...

  • Java基础

    Java基础 集合基础 集合框架 多线程基础 多线程框架 反射 代理 集合基础 ArrayList LinkedL...

  • iOS GCD&&多线程

    iOS GCD&&多线程 基础篇 GCD用途 GCD 是 Grand Central Dispatch 的缩写。 ...

网友评论

      本文标题:多线程基础篇

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