JUC基础(学习笔记)

1、JUC概述

进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。

线程:系统分配处理器时间资源的基本单位,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。

线程状态:NEW(新建)、RUNNABLE(准备就绪)、BLOCK(阻塞)、WAITING(不见不散)、TIMED_WAITING(过时不候)、TERMINATED(终结)

wait和sleep的区别

  • (1)sleep是Thread静态方法,wait是Object的方法,任何对象实例都能调用。
  • (2)sleep不会释放锁,他也不需要占用锁。wait会释放锁,但调用它的而前提是当前线程占有锁(即代码要在synchronized中)。
  • (3)他们都可以被interrupted方法打断。

并发和并行

串行是一次只能取得一个任务,并执行这个任务。

并行:多项工作一起执行,最后再汇总。

并发:同一时刻多个线程在访问同一个资源,多个线程对一个点。

管程:在操作系统中叫监视器,在java中叫锁。是一种同步机制,保证同一时间,只有一个线程访问被保护的数据或者代码。 在JVM同步基于进入和退出,使用管程对象实现的。

用户线程:自定义线程,new Thread()。主线程结束了,用户线程还在运行,jvm存活。

守护线程:后台特殊的线程,比如垃圾回收。没有用户线程了,都是守护线程,jvm结束。

2、Lock接口

Synachronized

可重入锁(ReentrantLock):来一个等待,上一个执行完再进行下一个。

lock和synachronized区别?

  • 1、Lock是一个接口,而synachronized是Java中的关键字,synchronized是内置的语言实现;
  • 2、synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unlock()去释放锁,则很可能造成死锁现象,因此使用lock时需要在finally块中释放锁;
  • 3、Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  • 4、通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  • 5、Lock可以提高多个线程进行读操作的效率。
    从性能上来说,如果竞争资源不是很激烈,性能差不多,如果竞争激烈,lock性能更好。

3、线程间通信

wait()
notify()
notiyAll()

package com.atguigu.sync;


class Share {
    private int number =0;

    public synchronized void incre() throws InterruptedException{
        while(number!=0){
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"::"+number);
        this.notifyAll();
    }
    public synchronized void decr() throws InterruptedException {
        while(number!=1){
            this.wait();
        }
        number--;
        this.notifyAll();
        System.out.println(Thread.currentThread().getName()+"::"+number);
    }
}

public class ThreadDemo1 {
    public static void main(String[] args) {
        Share share = new Share();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incre();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"aa").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"bb").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incre();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"cc").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"dd").start();
    }

}

虚假唤醒问题

判断条件用if的话,wait在哪里等在哪里唤醒,直接往下执行,出现虚假唤醒问题。所以判断条件用while判断。(安检的例子,如果下了飞机再上飞机没有进行第二次安检,就会出现虚假唤醒的问题)。

4、线程间定制化通信

启动三个线程,按照如下要求:
AA打印5次,BB打印10次,CC打印15次。
每个线程按照顺序执行。

package com.atguigu.lock;


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

class ShareResource{
    //定义标志位  1AA 2BB 3CC
    private int flag = 1;

    private Lock lock = new ReentrantLock();

    //创建三个condition
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    public void print5(int loop) throws Exception{
        //上锁
        lock.lock();
        try {
            //判断
            while(flag != 1){
                c1.await();
            }
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName()+"::"+i+":轮数:"+loop);
            }
            flag =2;
            c2.signal();
        }finally {
            lock.unlock();
        }
    }
    public void print10(int loop) throws Exception{
        lock.lock();
        try {
            while (flag!=2){
                c2.await();
            }
            for (int i =1;i<=10;i++){
                System.out.println(Thread.currentThread().getName()+"::"+i+"轮数:"+loop);
            }
            flag=3;
            c3.signal();
        }finally {
            lock.unlock();
        }
    }
    public void print15(int loop) throws Exception{
        lock.lock();
        try {
            while (flag!=3){
                c3.await();
            }
            for (int i =1;i<=15;i++){
                System.out.println(Thread.currentThread().getName()+"::"+i+"轮数:"+loop);
            }
            flag=1;
            c1.signal();
        }finally {
            lock.unlock();
        }
    }



}
public class ThreadDemo3 {
    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    shareResource.print5(i);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();
        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    shareResource.print10(i);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();
        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    shareResource.print15(i);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();
    }
}

Condition 将 Object 监视器方法(wait 、notify 和 notifyAll )分解为不同的对象,通过将它们与任意 Lock 实现的使用相结合,从而产生每个对象具有多个等待集的效果。其中 Lock 替换了 synchronized 方法和语句的使用,Condition 替换了对象监视器方法的使用。
条件(也称为 condition queues 或 condition variables )为一个线程提供了一种暂停执行(“等待”)的方法,直到另一个线程通知某个状态条件现在可能为真。因为对这种共享状态信息的访问发生在不同的线程中,它必须受到保护,所以某种形式的锁与条件相关联。等待条件提供的关键属性是它 atomically 释放关联的锁并挂起当前线程,就像 Object.wait 一样。

Condition 实例本质上绑定到锁。要获取特定 Lock 实例的 Condition 实例,请使用其 newCondition() 方法。

例如,假设我们有一个支持 put 和 take 方法的有界缓冲区。如果在空缓冲区上尝试take,则线程将阻塞直到有项可用;如果尝试对满缓冲区执行 put,则线程将阻塞直到有空间可用。我们希望在单独的等待集中等待 put 个线程和 take 个线程,以便我们可以使用优化,在缓冲区中的项目或空间可用时一次只通知一个线程。这可以使用两个 Condition 实例来实现。

5、集合的线程安全

ArrayList是线程不安全的。

解决方案一:

Vector(jdk1.0)方法使用了synchronized

Vector<String> list = new Vector<>();

解决方案二:

Collections(不常用)

List<String> list = Collections.synchronizedList(new ArrayList<>());

解决方案三:

CopyOnWriteArrayList(JUC的解决方案,常用)

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

原理:写时复制技术。读是并发读。写的时候复制和之前集合一样的,写入新内容,写完后与之前的合并,读取新的集合。兼顾了并发读,也照顾到了独立写操作。

HashSet是线程不安全的。HashSet的底层实现原理是HashMap,放的值就是HashMap的键,是不可重复的,无序的。

解决方案:CopyOnWriteArraySet

Set<String> set  = new CopyOnWriteArraySet<>();

HashMap的线程不安全问题

解决方案:

Map<String,String> map = new ConcurrentHashMap<>();

6、多线程锁

锁的八种情况

synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体如下:

- 对于普通同步方法,锁是当前实例对象。

- 对于静态同步方法,锁是当前类的Class对象。

- 对于同步方法快,锁是synchronized括号里配置的对象。

synchronized锁的是对象。所以同一个对象,会先执行第一个加synchronized方法,在执行第二个加synchronized方法。
如果一个synchronized方法,一个普通方法,使用同一个对象,synchronized方法停留4s,则先执行普通方法,在执行synchronized的方法。
两个对象,两个synchronized方法,就不是同一把锁。
静态方法 static,锁的就不是this,而是锁的当前类Class。不管一个对象还是两个对象,都会等待第一个static synchronized方法。
一个静态synchronized方法,一个普通静态方法,不是一把锁,一个是Class,一个是this,当前对象。

公平锁和非公平锁

非公平锁 new ReentrantLock()或者new ReentrantLock(false); 可能造成一个线程把所有活都干了,其他线程出现被饿死的情况。好处是执行效率高。

公平锁 new ReentrantLock(true); 阳光普照,效率相对低。

可重入锁

synchronized(隐式,上锁解锁隐式进行)和Lock(显示)都是可重入锁。
递归锁
进入了第一道大门,里面的锁都能进入。

package com.atguigu.sync;

public class SynLockDemo {
    public static void main(String[] args) {
        Object o = new Object();
        new Thread(() -> {
            synchronized (o){
                System.out.println(Thread.currentThread().getName()+"外层");
                synchronized (o){
                    System.out.println(Thread.currentThread().getName()+"中层");
                    synchronized (o){
                        System.out.println(Thread.currentThread().getName()+"内层");
                    }
                }
            }
        },"AA").start();
    }
}

死锁:两个或者两个以上的进程在执行过程中,因为争夺资源造成相互等待的线程,如果没有外力干涉,他们无法再执行下去。

死锁代码

package com.atguigu.lock;

public class DeadLock {
    static Object a = new Object();
    static Object b = new Object();
    public static void main(String[] args) {
        new Thread(()->{
            synchronized (a){
                System.out.println(Thread.currentThread().getName()+" 持有锁a,尝试获取锁b");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b){
                    System.out.println(Thread.currentThread().getName()+"获取锁b");
                }
            }
        },"A").start();

        new Thread(()->{
            synchronized (b){
                System.out.println(Thread.currentThread().getName()+" 持有锁b,尝试获取锁a");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a){
                    System.out.println(Thread.currentThread().getName()+"获取锁a");
                }
            }
        },"B").start();
    }
}

查看是否发生死锁

  • jps -l 查看目前的进程
  • jstack 进程号 堆栈跟踪工具
  • 在这里插入图片描述

7、Callable接口

创建线程的多种方式

第一种:继承Thread类(当线程终止,无法使线程返回结果)

package atguigu.java;

//1.创建一个继承于Thread类的子类
class MyThread extends Thread {
    //2.重写Thread类的run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}


public class ThreadTest {
    public static void main(String[] args) {
        //3.创建Thread类的子类的对象
        MyThread t1 = new MyThread();

        //4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
        t1.start();

        /*问题一:我们不能通过直接调用run()的方式启动线程,
        这种方式只是简单调用方法,并未新开线程*/
        //t1.run();

        /*问题二:再启动一个线程,遍历100以内的偶数。
        不可以还让已经start()的线程去执行。会报IllegalThreadStateException*/
        //t1.start();

        //重新创建一个线程的对象
        MyThread t2 = new MyThread();
        t2.start();

        //如下操作仍然是在main线程中执行的。
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************");
            }
        }
    }
}


第二种:实现Runnable接口(当线程终止,无法使线程返回结果)

package atguigu.java;

//1.创建一个实现了Runnable接口的类
class MThread implements Runnable {

    //2.实现类去实现Runnable中的抽象方法:run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

public class ThreadTest1 {

    public static void main(String[] args) {
        //3.创建实现类的对象
        MThread mThread = new MThread();

        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(mThread);
        t1.setName("线程1");

        //5.通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
        t1.start();

        //再启动一个线程,遍历100以内的偶数
        Thread t2 = new Thread(mThread);
        t2.setName("线程2");
        t2.start();
    }

}

第三种:Callable接口

Runnable和Callable接口的区别:
  • 是否有返回值
  • 是否会抛出异常,Callable无法计算结果会抛出异常。
  • 实现方法名称不同,一个run方法,一个call方法

第四种:线程池的方式

JUC强大的辅助类

减少计数 CountDownLatch

// 6个同学都走了,班长锁门
for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");
            },String.valueOf(i)).start();
        }
        System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");
//出现一个问题,main方法执行的快,一些还没走,班长就锁门了

使用CountDownLatch类

//定义初始值
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");
                //减1操作
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }
        //计数器不为0等待,为0继续执行阻塞的线程
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");

循环栅栏CyclicBarrier

栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生。栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。

CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。

package com.atguigu.juc;

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    private static final int NUMBER = 7;
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> {
            System.out.println("******集齐七颗龙珠就可以召唤神龙");
        });

        for (int i = 1; i <= 7; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + " 集了龙珠");
                try {
                    //等待
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

信号灯 Semaphore

//6辆汽车,3个停车位
public class SemaphoreDemo {

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+" 获取到了停车位");
                    TimeUnit.SECONDS.sleep(new Random().nextInt(4));
                    System.out.println(Thread.currentThread().getName()+" ------离开了停车位");
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

读写锁

悲观锁 (只能一个人操作)

乐观锁 (通过版本号进行控制)

表锁 (不会发生死锁)

行锁(会发生死锁)

读锁(共享锁)会发生死锁,线程1读的时候会改,线程2读的时候也会改,线程1等线程2读完再改,线程2等线程1读完再改,发生死锁。

写锁(独占锁)会发生死锁,线程1写操作要操作第2条记录,要等线程2释放才能操作,线程2写操作要操作第1条记录,要等线程1释放才能操作。

读写锁

package com.atguigu.readwrite;


import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class MyCache{
    private ReadWriteLock rwlock = new ReentrantReadWriteLock();
    public volatile Map<String,Object> map =new HashMap<>();
    public void put(String key, Object o){
        try {
            rwlock.writeLock().lock();
            System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);
            TimeUnit.MILLISECONDS.sleep(300);
            map.put(key, o);
            System.out.println(Thread.currentThread().getName()+ "完成了写操作");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            rwlock.writeLock().unlock();
        }
    }

    public Object get(String key){
        Object o = null;
        try {
            rwlock.readLock().lock();
            System.out.println(Thread.currentThread().getName()+" 正在读操作"+key);
            o = map.get(key);
            TimeUnit.MILLISECONDS.sleep(100);
            System.out.println(Thread.currentThread().getName()+" 完成了读操作");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            rwlock.readLock().unlock();
        }
        return o;
    }
}

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for (int i = 1; i <= 6  ; i++) {
            final int num = i;
            new Thread(()->{
                myCache.put(num+"",num);
            },String.valueOf(i)).start();
        }
        for (int i = 1; i <=6 ; i++) {
            final int m = i;
            new Thread(()->{
                Object o = myCache.get(m+"");
            },String.valueOf(i)).start();
        }
    }
}

读写锁:一个资源,可以被多个读的线程进行访问,可以被一个写的线程进行访问,但是不能同时存在读和写的线程,读写互斥,读读共享。

第一 无锁:多线程抢夺资源,乱
第二 添加锁:使用synchronized和ReentrantLock 都是独占的,每次只能来一个操作。
第三 读写锁:ReentrantReadWriteLock 读读可以共享,提升性能,同时多人进行读操作。缺点:(1)造成锁饥饿,一直读。没有写操作。(2)读时候,不能写,只有读完成之后,才可以写,写操作可以读。
锁降级:将写入锁降级为读锁 :获取写锁-》获取读锁-》释放写锁-》释放读锁。在写操作的过程也能进行读操作。!!!但是读锁不能升级为写锁。不能先读再写。
public static void main(String[] args) {
        ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.WriteLock writeLock = rwlock.writeLock();
        ReentrantReadWriteLock.ReadLock readLock = rwlock.readLock();
        
        writeLock.lock();
        System.out.println("----写操作");
        readLock.lock();
        System.out.println("!!!读操作");
        writeLock.unlock();
        readLock.unlock();
    }

BlockingQueue阻塞队列

阻塞队列分类

ArrayBlockingQueue:基于数组的阻塞队列实现,维护了一个定长的数组,以便换缓存队列种的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。

LinkedBlockingDueue:由链表结构组成的有界的阻塞队列。

阻塞队列核心方法

在这里插入图片描述

ThreadPool线程池

线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建和销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

线程池底层的七个参数:ThreadPoolExecutor

int corePoolSize 常驻线程数量(核心)
int maxmumPollSize 最大线程数量
long keepAliveTime(值) TimeUnit unit(单位) 线程存活时间
BlockingQueue workQueue, 阻塞队列
ThreadFactory threadFactory,线程工厂,用于创建线程的
RejectedExecutionHandler handler 拒绝策略。

线程池底层的工作原理

在这里插入图片描述
AbortPolicy(默认):直接抛出RejectedExcutionException异常阻止系统正常运行
CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
DiacardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是一种很好的策略。

自定义线程池

package com.atguigu.pool;

import java.util.concurrent.*;

public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        ExecutorService threadPool1 = new ThreadPoolExecutor(
                3,
                10,
                3L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        try {
            for (int i = 1; i <=10 ; i++) {
                threadPool1.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"办理业务");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            threadPool1.shutdown();
        }

    }
}

分支合并框架

CompletableFuture异步回调

同步:

异步:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值