JUC并发编程

JUC并发编程,第1张

JUC并发编程 学习狂神说JUC并发编程笔记

视频地址:遇见狂神说的个人空间_哔哩哔哩_bilibili

1.什么是juc?

juc就是java.util.concurrent下的包,专门处理多线程开发。

学习该课程需要对java语言和java多线程编程有一定的了解

2.进程和线程

进程:程序执行的过程。一个程序往往可以包含多个线程(至少包含一个)

线程:是进程的一个执行单元,是进程内调度的实体,比进程更小的独立运行的基本单位。比如一个开启了一个网易云音乐,显示歌词,可能就是一个线程处理的。

java默认有两个线程:GC线程,main线程。

java开启线程的方式

1.继承Thread类,重写run方法

2.实现Runnable接口,重写run方法

3.实现Callable接口,重写call方法

那么java真的可以开启线程吗?

我们知道java在开启一个线程是,需要使用线程类中的start方法

public synchronized void start() {
       
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);

        boolean started = false;
        try {
            //调用start0方法
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                
            }
        }
    }

private native void start0();//native关键子是去调用本地方法库中c++的方法

我们发现,他底层调用了start0方法,这个方法有一个native关键字。

如果不熟悉native关键字的含义可以去了解下JVM方面的知识。

3.并发和并行

并发:学习过 *** 作系统的我们都知道cpu是在多个线程之间来回切换,这就是并发,它只是在逻辑上同时发生,其实只是cpu在多个线程之间快速的切换,我们人眼分辨不出来,实际上并不是同时发生。

并行:多个cpu同时 *** 作多个线程,所以这是逻辑上和实际上都是同时发生。这就是并发和并行的区别

并发编程的本质:充分利用cpu资源

说到线程,那么线程有几个装态呢?

我们从 *** 作系统上来说,线程是有5个状态

1.新建状态

2.就绪状态

3.运行状态

4.阻塞状态

5.死亡状态

那么java中线程的状态是这样的吗?我们通过观察源码发现,java中线程是有6种状态的。

1.新建状态

2.运行状态

3.阻塞状态

4.等待状态

5.超时等待状态

6.死亡状态

口说无凭,我把源码贴在了下方

public enum State {

//这是官方源码,不过我将一些注释删除掉了。
       
     //新建
        NEW,

     //运行
        RUNNABLE,
     //阻塞
        BLOCKED,

     //等待
        WAITING,
     //超时等待   过期不候
        TIMED_WAITING,

     //终止
        TERMINATED;
    }

在面试种我们常常会被问到wait和sleep的区别,那么我们来总结一下吧?

  1. wait方法是来自Object类的,sleep方法是来自Thread类的,我们后面会学习TimeUnit工具类,来进行线程的休眠
  2. wait方法会释放锁资源,而sleep方法不会释放锁资源
  3. wait方法必须写在synchronized同步代码块中,但是sleep方法可以在任何地方使用
  4. 我在网上看其他博客中提到,wait方法不需要捕获异常,sleep方法需要捕获,然后我下去在验证的时候发现wait方法也是需要捕获异常的
Lock锁

我们在前面学习到了,让一个方法变成同步方法需要在方法上面加上synchronized关键字,那么今天我们来学习一种新的方式Lock锁(可重入锁)

首先Lock是一个接口,它下面有一些实现类ReentrantLock,ReentranReadWriteLock(读写锁)等

我们先来看下以前我们解决买票例子的代码吧

package com.mc;

public class Day1 {
    public static void main(String[] args) {
        B b = new B();
        new Thread(()->{
            for (int i = 0; i < 60; i++) {
                b.getNumber();
            }

        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 60; i++) {
                b.getNumber();
            }
        },"B").start();
    }
}
class B {
    private int number = 1;
       //使用Synchronized关键字,解决并发问题
    public synchronized void getNumber() {
        if(number <= 50) {
            System.out.println(Thread.currentThread().getName()+"卖出第"+(number++)+"张票");
        }
    }
}

 这样做我们只知道了这个方法加了锁,却不知道,锁的状态,它是给整个方法都加上了锁,如果我想只对代码中一部分代码加锁怎么办?

Lock锁就是为了解决这些问题的

首先Lock锁也是一个可重入锁,那么我们来看下Lock锁是怎样使用的

package com.mc;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo1 {
    public static void main(String[] args) {
        A a = new A();
        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                a.getNumber();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                a.getNumber();
            }
        },"B").start();
    }
}
class A {
    private int number = 30;

    public void getNumber() {
        //新建锁
        Lock lock = new ReentrantLock();
        try{
            //加锁
            lock.lock();
            //业务,买票
            if(number > 0){
                System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票");
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            //解锁
            lock.unlock();
        }
    }
}

这样是不是可以看出锁的状态了呢?也可以实现局部加锁了

其实在官方文档中,都明确写了这种锁如何使用

 所以我们需要学会查阅官方文档

Lock锁还有一个特殊的地方,Synchronized,必须等待一个方法执行完,才会释放锁,但是Lock锁有一个tryLock()方法可以去尝试获取锁

 

 当然Lock锁还有很多用法,比如Synchronized默认是一个非公平锁,那么Lock锁呢?

 public ReentrantReadWriteLock() {
        this(false);
    }

    
    public ReentrantReadWriteLock(boolean fair) {
//可以看到Lock锁可以实现公平锁,在创建时传递一个boolean值为true就可以新建出来了
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

那我们来总结一下Lock锁和Synchronized的区别吧。

1.首先Synchronized是一个关键字,Lock锁是一个类。

2.Synchronized无法获取锁的状态,但是Lock锁可以获得锁的状态

3.synchronized 线程1(获得锁、阻塞) 线程2(等待);Lock锁不一定会 lock.tranlock()尝试获得锁

4.synchronized关键字不需要手动释放锁,但是lock锁必须手动释放锁。

5.synchronized是可重入锁,非公平的,lock锁是可重入锁,可以公平也可以不公平(默认非公平)

6.Synchronized适合少量的代码同步问题,Lock适合锁大量的同步代码!

有一个疑问,关于锁是谁,对于lock锁我们很好分辨,但是对于synchronized呢,我们如何去判断,这里大家可以去练习一下8锁问题,小提示,谁调用方法,谁就是锁。

生产者消费者问题

synchronized是怎样写的

package com.mc;
//生产者消费者问题synchronized
public class Demo2 {

    public static void main(String[] args) {
        Resource resource = new Resource();
        new Thread(()->{
            for(int i=0;i<20;i++){
                try {
                    resource.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for(int i=0;i<20;i++){
                try {
                    resource.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for(int i=0;i<20;i++){
                try {
                    resource.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for(int i=0;i<20;i++){
                try {
                    resource.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
class Resource {
    private int number = 0;

    //生产者
    public synchronized void increment() throws InterruptedException {
        //判断
        while (number != 0){
            wait();
        }
        //业务
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        this.notifyAll();
    }
    //消费者
    public synchronized void decrement() throws InterruptedException {
        while (number==0){
            wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        this.notifyAll();
    }
}

注意:有的小伙伴在判断条件时,使用if条件,但是这样有可能会引发问题,有些面试官也特别爱问这一点。官方文档也给我们解释了为什么使用while而不是if

 

 那接下来看看我们Lock版的。

在看Lock版之前,我们发现synchronized是使用了synchronized和wait以及notifyAll来完成的,那么我们lock怎样解决的呢?

 我们lock中有一个方法可以获取一个condition对象,这个对象中有两个方法来解决这个问题

package com.mc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo4 {
    public static void main(String[] args) {
        Data1 data1 = new Data1();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data1.a();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data1.b();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data1.c();
            }
        },"C").start();

    }

}
class Data1{
    private int number = 1;
    private Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();

    public void a(){
        lock.lock();
        try {
            while (number!=1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            number = 2;
            condition2.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void b(){
        lock.lock();
        try {
            while (number!=2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            number = 3;
            condition3.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void c(){
        lock.lock();
        try {
            while (number!=3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            number = 1;
            condition1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

这块我们使用了精确唤醒  A->B->C->A  这就是lock版的强大

集合不安全

首先我们在平时使用的arraylist,hashset,hashmap都是线程不安全的,这点我们在平时学习的时候也都知道。

当然我们也可以使用一些集合安全的类来解决它

如vector、hashtable

或者使用工具类Collection.synchronizedxxxx(list  or  set  or  map)方法来让集合变安全。

那么我们juc是干什么的,是并发编程,那么他如何来解决这些问题呢?

首先juc下面有一些集合类来解决这些问题

1.CopyOnWriteArrayList

写入时赋值  CopyOnWrite

核心思想是,如果有多个调用者同时要求相同的资源(如内存或者磁盘上的数据存储),它们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本给调用者,而其他调用者所见到的最初资源任然保持不变。这过程对其他的调用者都是透明的。此做法主要的优点是如果调用者没有修改资源,就不会有副本被创建,因此多个调用者只是读取 *** 作时可以共享同一份资源

可以理解为,读的时候不加锁,只要在写的时候才会加锁,并赋值一份新的文件,让该线程去 *** 作

2.CopyOnWriteArraySet

3.ConcurrentHashMap

底层使用了CAS,和Synchronized来解决线程安全问题

使用了volatile来保证可见性和禁止指令重排的。

CAS和volatile后面会讲

就是解决这些问题的,并且效率更高

我在这里只介绍两种,有兴趣的朋友可以下去自己研究。

参考:狂神说juc并发编程视频

视频地址:遇见狂神说的个人空间_哔哩哔哩_bilibili

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5693240.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存