Java 线程 和 锁 基础知识

更多并发相关内容,查看==>Java 线程&并发学习目录

进程是资源分配的最小单元,线程是程序执行的最小单元,通常在一个进程中包含了多个线程。单个CPU在一个时刻只能把时间片分配给一个线程去执行。线程的整个生命周期包含了多个状态,未运行的初始状态,启动后未获取CPU时间片的待运行状态,任务执行完成后的消亡态,和线程紧密工作的锁又是如何控制线程的进入和退出的,接下来就学习和了解下Java的线程和锁的基本特点。

1、线程Thread 生命周期 & 基本使用

线程类中包含了多种状态,可通过getState方法获取到State枚举类信息

public enum State {
    NEW,   
    RUNNABLE,
    BLOCKED,  
    WAITING,  
    TIMED_WAITING,  
    TERMINATED;
}

NEW:线程还未开始的状态
RUNNABLE:在JVM中是运行的状态,但真正执行需要操作系统分配必须的资源才可运行
BLOCKED:等待Monitor Lock(监视器锁) 被阻塞导致,例如synchronized
WAITING:因为非时间因素导致线程等待的,例如Object.wait()、Thread.join()、LockSupport.park()等
TIMED_WAITING:因为时间因素导致线程等待的,例如Thread.sleep()、Object.wait(time)、Thread.join(time)等
TERMINATED:线程执行完成,消亡状态了

实际状态流转中每一种状态的变更都是有着其具体的原因,例如Object.wait()方法执行后,线程一方面会自行暂停,另一方面会进入到当前对象的一个等待集合中,等待超时、notify唤醒又或者中断引发的中断时间而离开等待集合,如下图可能可以更好的说明线程的流转关系。

Java 线程 和 锁 基础知识_第1张图片
image.png

具体方法以及相关关键字在后面再做介绍,先了解下线程如何使用的,线程的使用有两种方法,继承Thread 类实现Runnable 接口,都是重写run方法,Runnable更多的可以看做是一个Task任务,任务可以交由任何可执行的线程去执行,但是继承Thread的在一创建时就已经固定了,没有足够的灵活性,推荐使用Runnable

用java8最新的lambda表达式也可以很方便的写成Runnable runnable = () -> {/* run方法运行实体*/},然后直接交给Thread执行即可。

请勿直接调用线程的run方法,那样无法创建新线程执行。调用start方法会由native方法创建新线程然后调用run方法

2、锁

现实中我们也大量的应用到锁,为了防止家里的财产被盗,离开家门时会锁门,日常用的电脑为了确保资料安全也会给自己的账户设置个密码锁,不过这也意味着我们日常外出需要带着钥匙,记住密码,要不然会导致很多不必要的麻烦。和实际生活一样,Java中的锁也是为了保证数据(服务)安全,也会对服务的性能、吞吐量等造成影响。

那锁是什么呢?

  • Java中的一切都是对象,锁也不例外,这个被锁的对象只能由锁保护资源所在的线程去访问
  • 锁可以控制对数据访问的先后顺序(强行串行化)
  • 锁可以控制对数据是否拥有访问权
  • 锁可以控制服务的活跃性问题
  • 锁也可以抑制指令重排(降低效率)

常见的锁种类

  • synchronized:Java原生语义支持的,内置锁,可重入,悲观的,在JDK1.5以后synchronized在不同情况下从 无锁->偏向锁->轻量级所->重量级锁的转变。
  • ReentrantLock:基于AQS开发的可重入锁,有公平和非公平之分,也可以感知到中断
  • CountDownLatch:基于AQS开发的锁,也叫计时器锁,当计数器减为0后,就可以执行其他任务了,不可重置
  • CyclicBarrier:同样是基于AQS开发的锁,也叫栅栏锁,有换代的操作,可重置(想象成多个栅栏一般)
  • Condition:条件锁,用在阻塞队列中,可控制消费者和生产者的读取进度
  • ReentrantReadWriteLock:读写锁,读和写拆开,可以极大的提高读多写少的场景下的性能

对象锁对应的监视器锁结构如下图所示

Java 线程 和 锁 基础知识_第2张图片
image.png

本图来自:https://www.programcreek.com/2011/12/monitors-java-synchronization-mechanism/

  • Special Room:当前运行的线程所存储的地方
  • Entry Set: 所有需要获取当前锁的线程存储的地方
  • Wait Set:所有被调用wait方法的线程存储的地方

3、Object 的wait 和 notify 方法

wait、notify方法是Object类的final native方法,不能被重写,而且必须有同步器synchronize的辅助才可以使用(这个是JVM规定的)

    synchronized (OBJ) {
        try {
            while (count <=0) {
                 // 当前线程暂停执行
                OBJ.wait();
            }
            ......
            // 具体的任务
            OBJ.notify();
            // 唤醒其他线程工作
        } catch (InterruptedException e1) {
            e1.printStackTrace();
            // 由wait方法触发的中断
        }
    }

wait方法是用来释放锁并且暂停服务的,从上面的线程流转图也可以看出来,对象调用wait()方法后,当前执行的线程会进入到当前对象的监视器对象的Wait Set区域内,执行notify方法,会从Wait Set集合中随便挑选一个然后唤醒操作进入到Entry Set集合中,后续的具体执行还得看具体的资源分配等情况,notifyAll方法可以唤醒所有等待的线程,至于那一个现在真正的执行同样也是无法感知的。

本人微信公众号(搜索jwfy)欢迎关注

微信公众号

你可能感兴趣的:(Java 线程 和 锁 基础知识)