java学习之路

Java Util Concurrent并发编程(一) 基础知识

2020-11-05  本文已影响0人  唯有努力不欺人丶

前言:现在是十一月一号。今天给接下来的三个半月做了下规划,初步计划是三大必学模块:juc并发,netty通讯框架,spring boot源码。如果时间还有富余我是打算再去看下es的。
其实说实话,上面这些知识我都使用过,spring boot更是现在工作的基石。但是仅仅是使用功能,一些源码解读,实现原理什么的直白点说是很难过得去面试的。
同样juc我更是学烂了,不管是实体书,还是视频教程我都看过学过,但是并没有什么实际的用处。知识有一点特性:单纯的肯书本而不使用最容易被遗忘。我记得今年四月份才从头又看了一遍java并发编程这本书,但是现在也就那些出现率比较高的名词术语记住了。所以说只能像是回锅肉一样来回滚了。
而netty以前也仅仅是使用,也会遇到什么理解不了的去百度一下,当时恍然大悟,事后忘得一干二净。所以这三个月打算从新整理一下这些知识。

如果时间富裕的话,余下来的时间我大概会去看看es(es可以称我是接口调用工程师,crud是会的。没有实践应用)。刷题也会保持每周刷5+题目。大概接下来三个半月的计划就是这样。下面开始进入正式的笔记。

线程和进程

说到并发,这两个概念是一切的基础。当然了一般稍微会点java基础的也都知道这两个概念,但是能不能说清和知不知道不一样。
首先附上一个最适合背书的一句话:

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

当然了要是就这么一句话你就能明白了,那我敬你是个天才。反正我是做不到。至于具体说怎么理解,我附上一个知乎上赞同数比较高,我也觉得很形象的一个例子:
做个简单的比喻:进程=火车,线程=车厢

上面就是知乎大佬的回答,我觉得真的挺好理解的,比较形象。当然了百分之百正确肯定不可能,比如之前说线程崩了的那个,还有很多说的可能不是那么准确,但是起码对于我们理解来说是够了的。起码当有人问你一些线程和进程之间的问题,我们带入到火车和车厢,这样方便记忆的多。
除了进程和线程的理解,结合java中的实际,还有几个常识要知道:

并发/并行/串行

这几个概念也比较基础,但是这里就是要把基础重新理一遍。所以一一说说。

//获取cpu核数
Runtime.getRuntime().availableProcessors();
本地运行结果
我的电脑性能
并发编程的本质就是充分利用cpu的资源!(ps:这个可以想想请阿姨给孩子喂饭。是不是一个人喂的孩子越多,需要的人越少,就越省钱啊。)

线程

线程有几个状态?正确答案是java中是6个。操作系统层是5个。下面一一介绍。
在java的Thread源码中,一个线程有六种状态,如下截图:

线程6种状态
其实源码中注解写的挺全的,但是我这个英语渣要一个个贴到百度翻译里,哈哈。下面是具体的状态和翻译:
wait和sleep的区别
  1. 来自不同的类。Wait是Object的 ,sleep是Thread的
  2. 关于锁的释放:wait会释放锁,sleep不会。
  3. 使用的范围是不同的。wait必须在同步代码块使用。sleep可以在任何地方用。

Lock锁(重点)

线程就是一个单独的资源类,没有任何附属操作。
在Lock之前是重量级锁Sychronized。属于悲观锁。每次都要一个一个进入锁住的代码块。虽然比较安全,但是性能却是大问题,所以尽量不用。
相比于sychronized,lock算是一个轻量级的锁。(可以去java8的api文档中查看,https://www.matools.com/api/java8

java8的中文api文档
在官方文档上也简单说了lock怎么用:

随着这种增加的灵活性,额外的责任。 没有块结构化锁定会删除使用synchronized方法和语句发生的锁的自动释放。 在大多数情况下,应使用以下惯用语:

   Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); } 

当在不同范围内发生锁定和解锁时,必须注意确保在锁定时执行的所有代码由try-finally或try-catch保护,以确保在必要时释放锁定。
其实看着就能发现这个lock的用法很简单的。加锁,然后执行代码。最后记得解锁。为了解锁必然执行,放在finally中比较稳妥。

Lock使用三部曲:
    public static void main(String[] args) {
        Test test = new Test();
        for(int j = 0;j<100;j++) {
            new Thread(()->{for(int i = 0;i<20000;i++) test.incr();}).start();
        }
    }
class Test{
    int i = 0;
    Lock lock = new ReentrantLock();
    public void incr() {
        lock.lock();
        try {
            i++;
            System.out.println("当前i的值是:"+i);
            System.out.println("<<<<<<<<"+i);
        } finally {
            lock.unlock();
        }
    }
}

另外说个小知识点:公平锁和非公平锁
公平锁是排队锁。非公平锁是每次每个锁获得几率差不多的(公平锁理解成排队买票。非公平锁是一窝蜂一样堵在卖票口买票。)
java默认是非公平锁。其实我们使用非公平锁是为了公平。这句话可能比较绕。因为有的线程时间长,有的时间短。如果公平锁的话,有些线程两s就能完成,硬生生等两个小时,这个就不太好。其实这里面涉及好多东西。比如cpu调度,短作业优先执行(这一块我就简单的查了下资料,也不怎么了解)
而synchronized和lock有什么区别?

  1. synchronized是一个关键字。Lock是java类。
  2. synchronized无法判断锁的状态,Lock可以判断是否获取到了锁。
  3. synchronized会自动释放锁。Lock必须手动释放锁(不释放就死锁了)。
  4. synchronized是会一直等待锁。而Lock可以加一些等待条件(比如tryLock)
  5. synchronized默认是可重入锁。而且非公平的。(这些是不能改的。)Lock是可重入,可以自己设置公不公平(默认非公平,可以在参数中传true即为公平)。
  6. synchronized适合锁少量的代码块,Lock稍微锁大量的代码也ok。

Lock

先说一下synchronized和Lock使用的区别。
synchronized常常要和Object的wait和notify/notifyall方法联合使用,才能起到监视器的作用(这里有一个小知识点:虚假唤醒)。
虚假唤醒:在wait/notifyall中,记得判断不要用if而用while。

虚假唤醒出现的原因及解决办法
而Lock则自带监视器了。这里可以从api文档中学习。
先简单的看下Lock的方法:
Lock方法
这里其实方法名称和形容让我们很容易就知道了每个方法是干什么的。但是注意到这里有个Condition了么?之说返回一个绑定的实例。但是具体是做什么用的我们可以继续往下看。
Condition的作用
注意看我框起来这句话:在拥有锁的时候Condition.await()可以释放锁。感觉熟不熟悉?有没有联想到synchronized中的ojb.wait()?如果你没想到可能是需要一些想象力了。因为着两个除了方法名很想,其实功能也差不多。都是释放锁。只不过一个释放的是synchronized一个是Lock。
下面我们点进Condition这个类瞅瞅:
Conditon的作用
看这句话,官方手册上也是这么说的,Condition之于Lock就是监视器于synchronized。
而Condition相比于传统的wait/notify更加完善的是他有个指定唤醒某资源的功能。比如传统的notify只能随机唤醒一个等待线程。但是Condition其实是可以根据不同的Condition表面上看是唤醒指定的线程的(其实绝对不是唤醒指定线程,这个说法是大大的错误!)。注意看下面的截图:
Condition的唤醒
如果单独这句话很难理解,下面附上一个demo代码:
public class Demo {

    public static void main(String[] args) {
        Test test = new Test();
        new Thread(()->{for(int i = 0;i<200;i++) test.A();},"A").start();
        new Thread(()->{for(int i = 0;i<200;i++) test.B();},"B").start();
        new Thread(()->{for(int i = 0;i<200;i++) test.C();},"C").start();
        new Thread(()->{for(int i = 0;i<200;i++) test.A();},"AA").start();
        new Thread(()->{for(int i = 0;i<200;i++) test.B();},"BB").start();
        new Thread(()->{for(int i = 0;i<200;i++) test.C();},"CC").start();
        new Thread(()->{for(int i = 0;i<200;i++) test.A();},"AAA").start();
        new Thread(()->{for(int i = 0;i<200;i++) test.B();},"BBB").start();
        new Thread(()->{for(int i = 0;i<200;i++) test.C();},"CCC").start();
        new Thread(()->{for(int i = 0;i<200;i++) test.A();},"AAAA").start();
        new Thread(()->{for(int i = 0;i<200;i++) test.B();},"BBBB").start();
        new Thread(()->{for(int i = 0;i<200;i++) test.C();},"CCCC").start();
        new Thread(()->{for(int i = 0;i<200;i++) test.A();},"AAAAA").start();
        new Thread(()->{for(int i = 0;i<200;i++) test.B();},"BBBBB").start();
        new Thread(()->{for(int i = 0;i<200;i++) test.C();},"CCCCC").start();
    }

}
class Test{
    int i = 0;
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    public void A() {       
        lock.lock();
        try {
            while (i!=0) {
                condition1.await();             
            }
            System.out.println("当前操作的线程是:"+Thread.currentThread().getName());
            i = 1;
            condition2.signal();
        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void B() {       
        lock.lock();
        try {
            while (i!=1) {
                condition2.await();             
            }
            System.out.println("当前操作的线程是:"+Thread.currentThread().getName());
            i = 2;
            condition3.signal();
        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void C() {       
        lock.lock();
        try {
            while (i!=2) {
                condition3.await();             
            }
            System.out.println("当前操作的线程是:"+Thread.currentThread().getName());
            i = 0;
            condition1.signal();
        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

这个代码的输入结果一定是A/B/C交替的。但是具体几个A.几个B.几个C是随机的。
而如果只有A,B,C三个线程,我们就可以以为Condition唤醒了指定的线程(这块up主表述的我总觉得有问题。)其实这个指定唤醒的是资源。


官方文档

8锁现象(关于锁的八个问题)

小结:锁的到底是什么?如果是静态代码块,锁的是类。如果是普通代码块,锁的是实例对象。

这篇笔记就到这里,主要是讲了一些并发的基本知识,线程啊,进程啊什么的,还有锁是什么。8锁问题之类的,知识点比较杂,反正如果帮到你了记得点个喜欢点个关注,也祝大家工作顺顺利利!

上一篇 下一篇

猜你喜欢

热点阅读