线程安全问题、同步代码块、同步方法、线程池详解

前言

通过本文我们将会了解到基本的多线程的知识。

一、线程安全的问题

在了解线程的安全问题前,我们先来看一个需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟电影院卖票。
分析:
有三个窗口,窗口各自都是独立的,可以将这3个窗口当作3个线程,在线程中执行的是卖票的代码。
1.创建一个类,继承Thread
2.定义变量int ticked,表示票数
3.在run()方法中执行循环,当票数小于100时,票数自减,继续循环,直到票数卖完,循环结束。
若是根据上述分析,最后的结果是三个线程,每个线程都卖了100张票,总共卖了300张票,并不是正确的需求
那么把变量ticked类型改为static int就可以让三个线程共享一个数据
但此时运行代码会发现,三个线程在执行的时候会有重复出现,甚至还有超出100范围的,这不是我们想要的,还是存在问题。

package com.practice.threaddemo1;

/**
 * @Author YJ
 * @Date 2023/7/21 19:07
 * Description:卖票线程代码
 */
public class MyThread extends Thread {
	//通过静态变量实现三个线程共享
    static int ticked = 0;

    @Override
    public void run() {
        while (true) {
            if (ticked < 100) {
                //每次卖票前睡一会
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticked++;
                System.out.println(getName() + "正在卖第" + ticked + "张票!");
            } else {
                break;
            }
        }
    }
}

package com.practice.threaddemo1;

/**
 * @Author YJ
 * @Date 2023/7/21 19:02
 * Description:模拟电影院卖票
 */
public class MyThreadDemo {
    public static void main(String[] args) {
        //1.创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        //2.设置线程名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        //3.开启线程
        t1.start();
        t2.start();
        t3.start();

    }
}

在这里插入图片描述
在这里插入图片描述

通过上面的买票需求,我们发现线程存在的安全问题,会出现重复和超出范围的情况,为什么会出现这种情况呢,我们可以通过线程的执行结合代码分析:

原因分析:
1.票数重复:
在线程开启的时候,三个线程都在抢夺CPU的资源,假设线程一在开始抢到了CPU的执行权,线程一就会继续往下执行,进入线程后,满足判断条件,接着会立马睡10毫秒(自定义的),此时线程一不会抢夺CPU的执行权,线程二和线程三一定会有一个抢到CPU的执行权,,假设是线程二抢到了,它也会继续往下执行,通过判断条件,也同样会睡10毫秒,此时CPU的执行权一定会被其他线程抢到,所以线程三也会睡10毫秒,于是当线程各自醒来后,继续抢夺CPU执行权,假设是线程一抢到了,ticked会自增变成1,此时线程一还没来得及打印,线程二就抢到了CPU的执行权,ticked又自增变成了2,同样的线程三也会抢到CPU执行权,ticked自增到3,接下来无论是哪个线程继续往下打印,ticked结果都是3,这样就出现了重复的情况。

2.票数超出范围:
当票数到达99张时,三个线程还是在抢夺CPU执行权,线程一抢到后进入循环睡10毫秒,线程二抢到同样睡10毫秒,线程三同样进来睡10毫秒,睡完后陆续醒来继续执行下面的代码,线程一醒来后ticked自增变为100,还没来得及打印,线程二醒来执行代码ticked子增变为101,还没来得及打印,线程三醒来执行代码ticked子增变为了102,接下来无论哪个线程打印,结果都是102,票数超出了范围。
上述根本原因是:线程执行时有随机性

解决方案:
将要执行的循环语句起来,这样当第一个线程抢到了CPU执行权,若是线程一的循环还没有执行完,线程二抢到了CPU执行权,由于循环是被锁住的,线程二就必须等待线程一执行完后才能进入循环。

二、同步代码块

2.1同步代码块实现方式

格式:
synchronized(锁对象){操作共享数据的代码}
特点1:锁默认打开,有一个线程进去了,锁自动关闭
特点2:里面的代码全部执行完毕,线程出来,锁自动打开
锁对象一定要是唯一的
通过锁来解决线程安全问题被叫做同步代码块

package com.practice.threaddemo1;

/**
 * @Author YJ
 * @Date 2023/7/21 19:07
 * Description:同步代码块
 */
public class MyThread extends Thread {
    static int ticked = 0;
    //锁对象要唯一
    static Object obj = new Object();

    @Override
    public void run() {
        synchronized (obj) {
        while (true) {
            if (ticked < 100) {
                //每次卖票前睡一会
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticked++;
                System.out.println(getName() + "正在卖第" + ticked + "张票!");
            } else {
                break;
            }
        }
        }
    }
}


在这里插入图片描述

2.1同步代码块实现细节

分析上述同步代码块实现卖票的方式,结果只有一个线程卖完了所有的票,也就是说,一个线程抢到CPU执行权后进入循环,直到这个线程执行完所有的代码后循环结束,票数也增加到了100,后面的线程再进入循环时已经不符合循环条件,所以循环直接结束。
所以要注意的是,synchronized 锁应该放在循环里面。
既然锁对象是唯一的,我们可以直接将当前类的字节码对象作为唯一的锁对象,字节码对象一定是唯一的。

package com.practice.threaddemo1;

/**
 * @Author YJ
 * @Date 2023/7/21 19:07
 * Description:同步代码块
 */
public class MyThread extends Thread {
    static int ticked = 0;

    @Override
    public void run() {
        while (true) {
            synchronized (MyThread.class) {
                if (ticked < 100) {
                    //每次卖票前睡一会
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticked++;
                    System.out.println(getName() + "正在卖第" + ticked + "张票!");
                } else {
                    break;
                }
            }
        }
    }
}

结果:
在这里插入图片描述

二、同步方法

同步方法:就是将synchronized 直接加在方法上
格式:
修饰符 synchronized 返回值类型 方法名(方法参数){...}
**特点1:**同步方法是锁住方法里面所有的代码
**特点2:**锁对象不能自己指定(非静态的:this静态的:当前的字节码对象)
我们可以通过同步方法实现上述卖票需求:

package com.practice.threaddemo2;

/**
 * @Author YJ
 * @Date 2023/7/21 21:00
 * Description:同步方法
 */
public class MyRunnable implements Runnable {
    //只创建一次,不需要static修饰
    int ticket = 0;

    @Override
    public void run() {
        //1.循环
        //2.同步代码块
        //3.判断共享数据是否到了末尾,如果到了末尾
        //4.判断共享数据是否到了末尾,如果没有到末尾
        while (true) {
            //同步方法
            if (method()) break;
        }
    }

    private synchronized boolean method() {
        if(ticket==100) {
            return true;
        } else {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票!!!");
        }
        return false;
    }
}

在这里插入图片描述

StringBuffer线程安全的原因是它的所有方法都有synchronized 修饰而StringBuilder 没有synchronized 修饰,这就是同步方法保证线程安全的原因。

三、Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5之后提供了一个新的锁对象Lock锁
Lock实现提供比使用synchronized 方法和语句获得更广阔的锁定操作
Lock中提供了获得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁
手动上锁,手动释放锁
Lock是接口,不能实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法
ReentrantLock():创建一个ReentrantLock的实例

package com.practice.threaddemo3;

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

/**
 * @Author YJ
 * @Date 2023/7/21 19:07
 * Description:Lock锁
 */
public class MyThread extends Thread {
    static int ticked = 0;

    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
           // synchronized (MyThread.class) {
            lock.lock();
            try {
                if (ticked == 100) {
                    break;
                } else {
                    //每次卖票前睡一会
                    Thread.sleep(10);
                    ticked++;
                    System.out.println(getName() + "正在卖第" + ticked + "张票!");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            //}
        }
    }
}

注意:在多线程使用锁的时候,不能让两个锁嵌套起来,两个锁嵌套有可能导致死锁的产生

四、生产者和消费者(等待唤醒机制)

生产者消费者模式是一个十分经典的多线程协作的模式。

  • void wait()当前线程等待,直到被其他线程唤醒
  • void notify()随机唤醒单个线程
  • void notifyAll()唤醒所有线程

4.1生产者和消费者的思路分析

  • 假设有一个吃货线程表示消费者,厨师线程表示生产者,有一个桌子,桌子上有面条,吃货线程执行吃,厨师线程负责等,桌子上没有面条,吃货就负责等,厨师生产面条。
  • 生产者和消费者的理想情况:
  • 厨师线程生产了一碗面条,放到桌子上,吃货线程吃一碗面条,相当于厨师做一碗面条,吃货吃一碗面条。
    但是线程执行具有随机性,并不一定会是这种理想情况。

生产者和消费者(消费者等待):
当两个线程启动时,若是消费者线程先抢到CPU执行权,但发现并没有任务要执行,这时消费者线程就需要等待wait,此时CPU执行权一定会被生产者线程抢到,生产者开始布置任务,布置完成后,消费者线程还是处于等待状态的,此时生产者线程就需要告诉消费者线程可以执行任务了,这个动作叫做唤醒notify

  • 消费者(消费数据):
  • 1.判断桌子上是否有食物
  • 2.如果没有就等待
  • 生产者(生产数据):
  • 1.制作食物
  • 2.把食物放在桌子上
  • 3.叫醒等待的消费者开吃

生产者和消费者(生产者等待):
当两个线程启动时,生产者抢到了CPU执行权,没有任务要执行,生产者开始布置任务,布置完成后,即使没有消费者在等待,仍然可以执行唤醒notify操作,而在下一步还是生产者抢到了CPU执行权,但此时已经有任务了,生产者就不能再去布置任务了,所以生产者就要等待wait

  • 消费者(消费数据):
  • 1.判断桌子上是否有食物
  • 2.如果没有就等待
  • 生产者(生产数据):
  • 1.判断桌子上是否有食物
  • 2.有:等待
  • 3.没有:制作食物
  • 4.制作食物
  • 5.把食物放在桌子上
  • 6.叫醒等待的消费者开吃

4.2生产者和消费者的代码实现

  • 桌子:
package com.practice.waitandnotify;

/**
 * @Author YJ
 * @Date 2023/7/22 8:12
 * Description:控制生产者和消费者的执行(桌子)
 */
public class Desk {
    //是否有面条:0.没有  1.有
    public static int foodFlag = 0;
    //总个数
    public static int count = 10;
    //锁对象
    public static Object lock = new Object();

}

  • 生产者(厨师):
package com.practice.waitandnotify;

/**
 * @Author YJ
 * @Date 2023/7/22 8:11
 * Description:生产者(厨师)
 */
public class Cook extends Thread{
    @Override
    public void run() {
        /**
         * 1.循环
         * 2.同步代码块
         * 3.判断共享数据是否到了末尾(到了末尾)
         * 4.判断共享数据是否到了末尾(没到末尾)
         */
        while (true) {
            synchronized (Desk.lock) {
                if(Desk.count == 0) {
                    break;
                } else{
                    //1.判断桌子上是否有食物
                    if(Desk.foodFlag == 1) {
                        //2.有:等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        //3.没有:制作食物
                        System.out.println("厨师做了一碗面条");
                        //4.修改食物状态
                        Desk.foodFlag = 1;
                        //5.唤醒等待的消费者
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}

  • 消费者(吃货):
package com.practice.waitandnotify;

/**
 * @Author YJ
 * @Date 2023/7/22 8:11
 * Description:消费者(吃货)
 */
public class Foodie extends Thread{
    @Override
    public void run() {
        /**
         * 1.循环
         * 2.同步代码块
         * 3.判断共享数据是否到了末尾(到了末尾)
         * 4.判断共享数据是否到了末尾(没到末尾)
         */
        while (true) {
            synchronized(Desk.lock) {
                if(Desk.count == 0) {
                    System.out.println("已经吃不下了~~");
                    break;
                } else {
                    //判断桌子上是否有面条
                    if(Desk.foodFlag == 0) {
                        //没有:等待
                        try {
                            Desk.lock.wait();//让当前锁跟这个线程绑定
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        //把吃的总数-1
                        Desk.count--;
                        //有:开吃
                        System.out.println("正在吃,还能再吃" + Desk.count + "碗~");
                        //吃完了:唤醒厨师
                        Desk.lock.notifyAll();
                        //修改桌子状态
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}

  • 代码运行:**
package com.practice.waitandnotify;

/**
 * @Author YJ
 * @Date 2023/7/22 8:34
 * Description:运行
 */
public class ThreadDemo {
    public static void main(String[] args) {
        //创建线程对象
        Cook cook = new Cook();
        Foodie foodie = new Foodie();
        cook.setName("厨师");
        foodie.setName("吃货");
        cook.start();
        foodie.start();
    }
}

  • 结果:
    在这里插入图片描述

4.3等待唤醒机制(阻塞队列方式实现)

  • 阻塞队列的继承结构:
  • Iterable
  • Collection
  • Queue
  • BlockingQueue
  • 实现类:
  • ArrayBlockingQueue:底层是数据,有界,必须指定长度
  • LinkedBlockingQueue:底层是链表,无界,但不是真正的无界,最大为int的最大值

代码实现:

细节:生产者和消费者必须使用同一个阻塞队列。

package com.practice.waitandnotifyblockingqueue;

import java.util.concurrent.ArrayBlockingQueue;

/**
 * @Author YJ
 * @Date 2023/7/22 9:13
 * Description:生产者
 */
public class Cook extends Thread{
    ArrayBlockingQueue<String> queue;

    public Cook(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        while (true) {
            try {
                queue.put("面条");
                System.out.println("厨师放了一碗面条~");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

package com.practice.waitandnotifyblockingqueue;

import java.util.concurrent.ArrayBlockingQueue;

/**
 * @Author YJ
 * @Date 2023/7/22 9:14
 * Description:消费者
 */
public class Foodie extends Thread{
    ArrayBlockingQueue<String> queue;

    public Foodie(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        while (true) {
            try {
                String food = queue.take();
                System.out.println(food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

package com.practice.waitandnotifyblockingqueue;

import java.util.concurrent.ArrayBlockingQueue;

/**
 * @Author YJ
 * @Date 2023/7/22 9:13
 * Description:阻塞队列方式实现
 */
public class ThreadDemo {
    public static void main(String[] args) {
        //1.创建阻塞队列对象
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
        //2.创建线程对象,并把阻塞队列传递过去
        Cook cook = new Cook(queue);
        Foodie foodie = new Foodie(queue);
        //3.开启线程
        cook.start();
        foodie.start();
    }
}

4.4线程的状态

在这里插入图片描述

五、线程池

以前写多线程的弊端:

  • 1.用到线程的时候就创建(效率低)
  • 2.用完后线程消失(浪费资源)

改进:
我们可以准备一个容器,用来存放线程,这个容器就叫做线程池,刚开始,容器中是空的,当给线程池提交一个任务时,线程池会自动地创建一个线程,用这个线程执行任务,执行完后,把线程返回给容器,等到下次再执行任务时,就不需要重新创建线程了。
特殊情况:
当第二个任务执行时,第一个任务还没有执行结束,线程池就要再创建一个新的线程,用这个新的线程执行任务,再来任务,继续创建线程,执行完后,都返回给线程池。
线程池中的线程创建是有上限的,可以自己定义最大线程数量,当任务过多,线程创建也达到上限时,未获取线程的任务只能排队等待。
核心原理:

  • 1.创建一个池子,池子中是空的
  • 2.提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下次再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
  • 3.如果提交任务时,池子中没有空闲的线程,也无法创建新的线程,任务就会排队等待。

代码实现:

  • 1.创建线程池
  • 2.提交任务
  • 3.所有任务执行完毕,关闭线程池
    Excutors:线程池的工具类,通过调用方法返回不同类型的线程池对象。
    public static ExcutorService newCachedThreadPool():创建一个没有上限的线程池
    public static ExcutorService newFixedThreadPool():创建有上限的线程池
package com.practice.mythreadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author YJ
 * @Date 2023/7/22 10:20
 * Description:创建没有上限的线程池
 */
public class MyThreadPoolDemo1 {
    public static void main(String[] args) throws InterruptedException {
        //1.获取线程池对象
        ExecutorService pool1 = Executors.newCachedThreadPool();
        Thread.sleep(1000);
        //2.提交任务
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        //3.销毁线程池
        //pool1.shutdown();
    }
}

package com.practice.mythreadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author YJ
 * @Date 2023/7/22 10:20
 * Description:创建有上限的线程池
 */
public class MyThreadPoolDemo2 {
    public static void main(String[] args) throws InterruptedException {
        //1.获取线程池对象
        ExecutorService pool1 = Executors.newFixedThreadPool(3);
        Thread.sleep(100);
        //2.提交任务
        pool1.submit(new MyRunnable());
        Thread.sleep(100);
        pool1.submit(new MyRunnable());
        Thread.sleep(100);
        pool1.submit(new MyRunnable());
        Thread.sleep(100);
        pool1.submit(new MyRunnable());
        Thread.sleep(100);
        pool1.submit(new MyRunnable());
        //3.销毁线程池
        //pool1.shutdown();
    }
}

六、自定义线程池

核心参数:

  • 1.核心线程的数量(不能小于0)
  • 2.线程池中最大线程数量(最大数量>=核心线程数量)
  • 3.空闲时间(值),如60(不能小于0)
  • 4.空闲时间(单位),如s(用TimeUnit指定)
  • 5.阻塞队列(不能为null)
  • 6.创建线程的方式(不能为null)
  • 7.要执行的任务过多时的解决方案(不能为null)

注意:
自定义线程池可以创建核心线程和临时线程。
假设核心线程有3个,临时线程是3个,队伍长度为3个,表示线程池中最多有6个线程可用,而且其中3个临时线程只有在队伍满的情况下又来了任务才会创建并执行,先提交的任务不一定先执行。
若有8个任务要执行,3个核心线程执行3个任务,三个任务在队伍中等待,此时还有两个任务,那么此时就要创建2个临时线程执行两个任务,队伍中还有3个任务在等待。
若是任务过多,线程池满了,队伍也满了,还是有任务,这时就会触发任务拒绝策略:

  • ThreadPoolExcutor.AbortPolicy:默认策略:丢弃任务并抛出RejectedExecutionException异常
  • ThreadPoolExcutor.DiscardPolicy:丢弃任务,但不抛出异常,不推荐
  • ThreadPoolExcutor.DiscarOldestPolicy:抛弃队列中等待最持久的任务,然后把当前任务加入队列中
  • ThreadPoolExcutor.CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行
package com.practice.mythreadpool2;


import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author YJ
 * @Date 2023/7/22 10:50
 * Description:创建自定义线程池
 */
public class MyThreadPoolDemo1 {
    public static void main(String[] args) throws InterruptedException {
        //创建自定义线程池对象
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心线程的数量(不能小于0)
                6,//线程池中最大线程数量(最大数量>=核心线程数量)
                60,//空闲时间(值)(不能小于0)
                TimeUnit.SECONDS,//空闲时间(单位),如s(用TimeUnit指定)
                new ArrayBlockingQueue<>(3),//阻塞队列(不能为null)
                Executors.defaultThreadFactory(),//创建线程的方式(不能为null)
                // -- Executors.defaultThreadFactory()底层就是new了一个Thread
                new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
        );
        //提交任务
        //...
    }
}

6.1、最大并行数

以4核8线程为例:
4核表示的是电脑有4个大脑,利用超线程技术,就可以把原本的4个大脑虚拟成8个,也就是8线程。
可以在设备管理器或任务管理器中看到自己电脑的最大并行数:


在这里插入图片描述


在这里插入图片描述


也可通过Java虚拟机用代码查看:

package com.practice.mythreadpool2;

/**
 * @Author YJ
 * @Date 2023/7/22 11:50
 * Description:获取电脑最大并行数
 */
public class MyThreadPoolDemo2 {
    public static void main(String[] args) throws InterruptedException {
        int count = Runtime.getRuntime().availableProcessors();
        System.out.println(count);
    }
}

6.2、线程池多大合适

CPU密集型运算: 最大并行数+1
I/O密集型运算:(读取本地文件较多、读取数据库文件较多) 最大并行数 * 期望CPU利用率 * (总时间(CPU计算时间+等待时间)) / CPU计算时间

总结

关于多线程的学习其实还有很多,目前介绍学习的是我们平时会用到的,希望会有帮助,我会继续学习并记录博客的学习笔记,欢迎大家关注+点赞!!!

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
├─第一阶段 │      源码+ppt.rar │      高并发编程第一阶段01讲、课程大纲及主要内容介绍.wmv │      高并发编程第一阶段02讲、简单介绍什么是线程.wmv │      高并发编程第一阶段03讲、创建并启动线程.mp4 │      高并发编程第一阶段04讲、线程生命周期以及start方法源码剖析.mp4 │      高并发编程第一阶段05讲、采用多线程方式模拟银行排队叫号.mp4 │      高并发编程第一阶段06讲、用Runnable接口将线程的逻辑执行单元从控制中抽取出来.mp4 │      高并发编程第一阶段07讲、策略模式在Thread和Runnable中的应用分析.mp4 │      高并发编程第一阶段08讲、构造Thread对象你也许不知道的几件事.mp4 │      高并发编程第一阶段09讲、多线程与JVM内存结构的关系,虚拟机栈实验.mp4 │      高并发编程第一阶段10讲、Thread构造函数StackSize详细讲解.mp4 │      高并发编程第一阶段11讲、Thread构造函数StackSize详细讲解-续.mp4 │      高并发编程第一阶段12讲、Daemon线程的创建以及使用场景分析.mp4 │      高并发编程第一阶段13讲、线程ID,优先级讲解.mp4 │      高并发编程第一阶段14讲、Thread的join方法详细介绍,结合一个典型案例.mp4 │      高并发编程第一阶段15讲、Thread中断Interrupt方法详细讲解.mp4 │      高并发编程第一阶段16讲、采用优雅的方式结束线程生命周期.mp4 │      高并发编程第一阶段17讲、Thread API综合实战,编写ThreadService实现暴力结束线程的综合实战.mp4 │      高并发编程第一阶段18讲、数据同步的引入与Synchronized的简单介绍.mp4 │      高并发编程第一阶段19讲、结合jconsole,jstack以及汇编指令认识synchronized关键字.mp4 │      高并发编程第一阶段20讲、同步代码块以及同步方法之间的区别和关系.mp4 │      高并发编程第一阶段21讲、通过实验分析This锁的存在.mp4 │      高并发编程第一阶段22讲、通过实验分析Class锁的存在.mp4 │      高并发编程第一阶段23讲、多线程死锁分析,案例介绍.mp4 │      高并发编程第一阶段24讲、线程间通信快速入门,使用wait和notify进行线程间的数据通信.mp4 │      高并发编程第一阶段25讲、多Produce多Consume之间的通讯导致出现程序假死的原因分析.mp4 │      高并发编程第一阶段26讲、多线程下的生产者消费者模型,以及详细介绍notifyAll方法.mp4 │      高并发编程第一阶段27讲、wait和sleep的本质区别是什么,深入分析(面试常见问题).mp4 │      高并发编程第一阶段28讲、线程生产者消费者的综合实战结合Java8语法.mp4 │      高并发编程第一阶段29讲、如何实现一个自己的显式锁Lock精讲上.mp4 │      高并发编程第一阶段30讲、如何实现一个自己的显式锁Lock精讲下(让锁具备超时功能).mp4 │      高并发编程第一阶段31讲、如何给你的应用程序注入钩子程序,Linux下演示.mp4 │      高并发编程第一阶段32讲、如何捕获线程运行期间的异常.mp4 │      高并发编程第一阶段33讲、ThreadGroup API介绍之一.mp4 │      高并发编程第一阶段34讲、ThreadGroup API介绍之二.mp4 │      高并发编程第一阶段35讲、线程池原理与自定义线程池.mp4 │      高并发编程第一阶段36讲、自定义个简单的线程池并且测试.mp4 │      高并发编程第一阶段37讲、给线程池增加拒绝策略以及停止方法.mp4 │      高并发编程第一阶段38讲、给线程池增加自动扩充线程数量,以及闲时自动回收的功能.mp4 │      高并发编程第一阶段39讲、课程结束,内容回顾,下季内容预告.mp4 │ ├─第二阶段 │       Java并发编程.png │       ppt+源码.rar │       高并发编程第二阶段01讲、课程大纲及主要内容介绍.wmv │       高并发编程第二阶段02讲、介绍四种Singleton方式的优缺点在多线程情况下.wmv │       高并发编程第二阶段03讲、介绍三种高效优雅的Singleto
├─第一阶段 │      源码+ppt.rar │      高并发编程第一阶段01讲、课程大纲及主要内容介绍.wmv │      高并发编程第一阶段02讲、简单介绍什么是线程.wmv │      高并发编程第一阶段03讲、创建并启动线程.mp4 │      高并发编程第一阶段04讲、线程生命周期以及start方法源码剖析.mp4 │      高并发编程第一阶段05讲、采用多线程方式模拟银行排队叫号.mp4 │      高并发编程第一阶段06讲、用Runnable接口将线程的逻辑执行单元从控制中抽取出来.mp4 │      高并发编程第一阶段07讲、策略模式在Thread和Runnable中的应用分析.mp4 │      高并发编程第一阶段08讲、构造Thread对象你也许不知道的几件事.mp4 │      高并发编程第一阶段09讲、多线程与JVM内存结构的关系,虚拟机栈实验.mp4 │      高并发编程第一阶段10讲、Thread构造函数StackSize详细讲解.mp4 │      高并发编程第一阶段11讲、Thread构造函数StackSize详细讲解-续.mp4 │      高并发编程第一阶段12讲、Daemon线程的创建以及使用场景分析.mp4 │      高并发编程第一阶段13讲、线程ID,优先级讲解.mp4 │      高并发编程第一阶段14讲、Thread的join方法详细介绍,结合一个典型案例.mp4 │      高并发编程第一阶段15讲、Thread中断Interrupt方法详细讲解.mp4 │      高并发编程第一阶段16讲、采用优雅的方式结束线程生命周期.mp4 │      高并发编程第一阶段17讲、Thread API综合实战,编写ThreadService实现暴力结束线程的综合实战.mp4 │      高并发编程第一阶段18讲、数据同步的引入与Synchronized的简单介绍.mp4 │      高并发编程第一阶段19讲、结合jconsole,jstack以及汇编指令认识synchronized关键字.mp4 │      高并发编程第一阶段20讲、同步代码块以及同步方法之间的区别和关系.mp4 │      高并发编程第一阶段21讲、通过实验分析This锁的存在.mp4 │      高并发编程第一阶段22讲、通过实验分析Class锁的存在.mp4 │      高并发编程第一阶段23讲、多线程死锁分析,案例介绍.mp4 │      高并发编程第一阶段24讲、线程间通信快速入门,使用wait和notify进行线程间的数据通信.mp4 │      高并发编程第一阶段25讲、多Produce多Consume之间的通讯导致出现程序假死的原因分析.mp4 │      高并发编程第一阶段26讲、多线程下的生产者消费者模型,以及详细介绍notifyAll方法.mp4 │      高并发编程第一阶段27讲、wait和sleep的本质区别是什么,深入分析(面试常见问题).mp4 │      高并发编程第一阶段28讲、线程生产者消费者的综合实战结合Java8语法.mp4 │      高并发编程第一阶段29讲、如何实现一个自己的显式锁Lock精讲上.mp4 │      高并发编程第一阶段30讲、如何实现一个自己的显式锁Lock精讲下(让锁具备超时功能).mp4 │      高并发编程第一阶段31讲、如何给你的应用程序注入钩子程序,Linux下演示.mp4 │      高并发编程第一阶段32讲、如何捕获线程运行期间的异常.mp4 │      高并发编程第一阶段33讲、ThreadGroup API介绍之一.mp4 │      高并发编程第一阶段34讲、ThreadGroup API介绍之二.mp4 │      高并发编程第一阶段35讲、线程池原理与自定义线程池.mp4 │      高并发编程第一阶段36讲、自定义个简单的线程池并且测试.mp4 │      高并发编程第一阶段37讲、给线程池增加拒绝策略以及停止方法.mp4 │      高并发编程第一阶段38讲、给线程池增加自动扩充线程数量,以及闲时自动回收的功能.mp4 │      高并发编程第一阶段39讲、课程结束,内容回顾,下季内容预告.mp4 │ ├─第二阶段 │       Java并发编程.png │       ppt+源码.rar │       高并发编程第二阶段01讲、课程大纲及主要内容介绍.wmv │       高并发编程第二阶段02讲、介绍四种Singleton方式的优缺点在多线程情况下.wmv │       高并发编程第二阶段03讲、介绍三种高效优雅的Singleton实现方式.wmv │       高并发编程第二阶段04讲、多线程的休息室WaitSet详细介绍与知识点总结.mp4 │       高并发编程第二阶段05讲、一个解释volatile关键字作用最好的例子.mp4 │       高并发编程第二阶段06讲、Java内存模型以及CPU缓存不一致问题的引入.mp4 │       高并发编程第二阶段07讲、CPU以及CPU缓存的结构,解决高速缓存一致性问题的两种方案介绍.mp4 │       高并发编程第二阶段08讲、并发编程的三个重要概念,原子性,可见性,有序性.mp4 │       高并发编程第二阶段09讲、指令重排序,happens-before规则精讲.mp4 │       高并发编程第二阶段10讲、volatile关键字深入详解.mp4 │       高并发编程第二阶段11讲、volatile关键字总结.mp4 │       高并发编程第二阶段12讲、观察者设计模式介绍.mp4 │       高并发编程第二阶段13讲、使用观察者设计模式观察线程的生命周期.mp4 │       高并发编程第二阶段14讲、单线程执行设计模式,有一个门,始终只能一个人通过-上.mp4 │       高并发编程第二阶段15讲、单线程执行设计模式,有一个门,始终只能一个人通过-下.mp4 │       高并发编程第二阶段16讲、多线程读写锁分离设计模式讲解-上.mp4 │       高并发编程第二阶段17讲、多线程读写锁分离设计模式讲解-中.mp4 │       高并发编程第二阶段18讲、多线程读写锁分离设计模式讲解-下.mp4 │       高并发编程第二阶段19讲、多线程不可变对象设计模式Immutable-上.mp4 │       高并发编程第二阶段20讲、多线程不可变对象设计模式Immutable-下.mp4 │       高并发编程第二阶段21讲、多线程Future设计模式详细介绍-上.mp4 │       高并发编程第二阶段22讲、多线程Future设计模式详细介绍-下.mp4 │       高并发编程第二阶段23讲、第二阶段课程答疑学员问题.mp4 │       高并发编程第二阶段24讲、Guarded Suspension设计模式-上.mp4 │       高并发编程第二阶段25讲、Guarded Suspension设计模式-下.mp4 │       高并发编程第二阶段26讲、ThreadLocal使用详解,深入原理介绍.mp4 │       高并发编程第二阶段27讲、多线程运行上下文设计模式介绍.mp4 │       高并发编程第二阶段28讲、使用ThreadLocal重新实现一个上下文设计模式.mp4 │       高并发编程第二阶段29讲、多线程Balking设计模式-上.mp4 │       高并发编程第二阶段30讲、多线程Balking设计模式-下.mp4 │       高并发编程第二阶段31讲、多线程Producer and Consumer设计模式.mp4 │       高并发编程第二阶段32讲、多线程Count Down设计模式.mp4 │       高并发编程第二阶段33讲、多线程Thread-Per-Message设计模式.mp4 │       高并发编程第二阶段34讲、多线程Two Phase Termination设计模式-上.mp4 │       高并发编程第二阶段35讲、多线程Two Phase Termination设计模式-下.mp4 │       高并发编程第二阶段36讲、多线程Worker-Thread设计模式-上.mp4 │       高并发编程第二阶段37讲、多线程Worker-Thread设计模式-上.mp4 │       高并发编程第二阶段38讲、多线程Active Objects设计模式(接受异步消息的主动对象)-上.mp4 │       高并发编程第二阶段39讲、多线程Active Objects设计模式(接受异步消息的主动对象)-中.mp4 │       高并发编程第二阶段40讲、多线程Active Objects设计模式(接受异步消息的主动对象)-下.mp4 │       高并发编程第二阶段41讲、多线程设计模式内容回顾与总结.mp4 │       高并发编程第二阶段42讲、ClassLoader课程大纲介绍.mp4 │       高并发编程第二阶段43讲、类加载的过程以及类主动使用的六种情况详细介绍.mp4 │       高并发编程第二阶段44讲、被动引用和类加载过程的练习巩固训练题.mp4 │       高并发编程第二阶段45讲、ClassLoader加载阶段发生的故事.mp4 │       高并发编程第二阶段46讲、ClassLoader链接阶段(验证,准备,解析)过程详细介绍.mp4 │       高并发编程第二阶段47讲、ClassLoader初始化阶段详细介绍clinit.mp4 │       高并发编程第二阶段48讲、JVM内置三大类加载器的详细介绍.mp4 │       高并发编程第二阶段49讲、自定义类加载器ClassLoader顺便问候了一下世界.mp4 │       高并发编程第二阶段50讲、ClassLoader父委托机制详细介绍.mp4 │       高并发编程第二阶段51讲、加密解密类加载实战演示.mp4 │       高并发编程第二阶段52讲、加密解密类加载实战演示-续.mp4 │       高并发编程第二阶段53讲、ClassLoader打破双父亲委托机制,重写loadClass实战练习.mp4 │       高并发编程第二阶段54讲、ClassLoader命名空间,运行时包,类卸载详细介绍.mp4 │       高并发编程第二阶段55讲、线程上下文类加载器以及数据库驱动案例分析.mp4 │       └─第三阶段        Java并发编程.png        Java高并发第三阶段(JUC).png        高并发编程第三阶段01讲 AtomicInteger多线程下测试讲解.mkv        高并发编程第三阶段02讲 AtomicInteger API详解,以及CAS算法详细介绍.mkv        高并发编程第三阶段03讲 利用CAS构造一个TryLock自定义显式锁.mp4        高并发编程第三阶段04讲 利用CAS构造一个TryLock自定义显式锁-增强并发情况下.mp4        高并发编程第三阶段05讲 AtomicBoolean源码分析.mp4        高并发编程第三阶段06讲 AtomicLong源码分析.mp4        高并发编程第三阶段07讲 AtomicReference详解,CAS算法带来的ABA问题详解.mp4        高并发编程第三阶段08讲 AtomicStampReference详解,解决CAS带来的ABA问题.mp4        高并发编程第三阶段09讲 AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray讲解.mp4        高并发编程第三阶段10讲 AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater讲解.mp4        高并发编程第三阶段11讲 AtomicXXXFieldUpdater源码分析及使用场景分析.mp4        高并发编程第三阶段12讲 sun.misc.Unsafe介绍以及几种Counter方案性能对比.mp4        高并发编程第三阶段13讲 一个JNI程序的编写,通过Java去调用C,C++程序.mp4        高并发编程第三阶段14讲 Unsafe中的方法使用,一半是天使,一半是魔鬼的Unsafe.mp4        高并发编程第三阶段15讲 Unsafe背后的汇编指令,牛逼男人背后的女人_.mp4        高并发编程第三阶段16讲 CountDownLatch经典案例讲解-上_.mp4        高并发编程第三阶段17讲 CountDownLatch经典案例讲解及API精讲-中_.mp4        高并发编程第三阶段18讲 CountDownLatch经典案例讲解如何给离散平行任务增加逻辑层次关系-下_.mp4        高并发编程第三阶段19讲 CyclicBarrier工具的使用场景介绍_.mp4        高并发编程第三阶段20讲 CyclicBarrier vs CountDownLatch_.mp4        高并发编程第三阶段21讲 Exchanger工具的使用以及常见问题分析-上_.mp4        高并发编程第三阶段22讲 Exchanger工具的使用以及常见问题分析-下_.mp4        高并发编程第三阶段23讲 Semaphore工具的介绍以及借助于Semaphore构造一个Lock_.mp4        高并发编程第三阶段24讲 Semaphore工具API详细介绍-上_.mp4        高并发编程第三阶段25讲 Semaphore工具API详细介绍-下_.mp4        高并发编程第三阶段26讲 Lock&ReentrantLock详细讲解_.mp4        高并发编程第三阶段27讲 ReadWriteLock&ReentrantReadWriteLock详细讲解_.mp4        高并发编程第三阶段28讲 Condition初步使用,提出几个疑问_.mp4        高并发编程第三阶段29讲 关于Condition疑问的几个小实验,对比Wait&Notify_.mp4        高并发编程第三阶段30讲 使用Condition实现一个多线程下的Producer-Consumer_.mp4        高并发编程第三阶段31讲 JDK8-StampedLock详细介绍-上_.mp4        高并发编程第三阶段32讲 JDK8-StampedLock详细介绍-下.mp4        高并发编程第三阶段33讲 ForkJoin框架之RecursiveTask_.mp4        高并发编程第三阶段34讲 ForkJoin框架之RecursiveAction_.mp4        高并发编程第三阶段35讲 Phaser工具的实战案例使用第一部分_.mp4        高并发编程第三阶段36讲 Phaser工具的实战案例使用第二部分_.mp4        高并发编程第三阶段37讲 Phaser工具的实战案例使用第三部分_.mp4        高并发编程第三阶段38讲 Executor&ExecutorService讲解_.mp4        高并发编程第三阶段39讲 ThreadPoolExecutor七大构造参数详细讲解_.mp4        高并发编程第三阶段40讲 ThreadPoolExecutor关闭(很重要)精讲_.mp4        高并发编程第三阶段41讲 newCache&newFixed&single ExecutorService详解_.mp4        高并发编程第三阶段42讲 newWorkStealingPool ExecutorService详解_.mp4        高并发编程第三阶段43讲 Scheduler的前奏Timer&Linux Crontab & quartz比较_.mp4        高并发编程第三阶段44讲 ExecutorService API详细讲解-上_.mp4        高并发编程第三阶段45讲 ExecutorService 四大内置拒绝策略深入探究_.mp4        高并发编程第三阶段46讲 ExecutorService API详细讲解-中_.mp4        高并发编程第三阶段47讲 ExecutorService API详细讲解-下_.mp4        高并发编程第三阶段48讲 Future&Callable详细讲解-上_.mp4        高并发编程第三阶段49讲 Future&Callable详细讲解-下_.mp4        高并发编程第三阶段50讲 CompletionService详细介绍_.mp4        高并发编程第三阶段51讲 ScheduledExecutorService详细讲解-上_.mp4        高并发编程第三阶段52讲 ScheduledExecutorService详细讲解-下_.mp4        高并发编程第三阶段53讲 知识回顾与串联_.mp4        高并发编程第三阶段54讲 课程问题答疑,ExecutorService中的陷阱_.mp4        高并发编程第三阶段55讲 CompletableFuture的使用精讲(体验)-1_.mp4        高并发编程第三阶段56讲 CompletableFuture的使用精讲(构建)-2_.mp4        高并发编程第三阶段57讲 CompletableFuture的使用精讲(熟练)-3_.mp4        高并发编程第三阶段58讲 CompletableFuture的使用精讲(深入)-4_.mp4        高并发编程第三阶段59讲 CompletableFuture的使用精讲(掌握)-5_.mp4        高并发编程第三阶段60讲 LinkedList和有序LinkedList的实现_.mp4        高并发编程第三阶段61讲 跳表数据结构的Java实现-1_.mp4        高并发编程第三阶段62讲 跳表数据结构的Java实现-2_.mp4        高并发编程第三阶段63讲 跳表数据结构的Java实现(解决Bug)-3_.mp4        高并发编程第三阶段64讲 ArrayBlockingList详细讲解_.mp4        高并发编程第三阶段65讲 PriorityBlockingQueue详细讲解_.mp4        高并发编程第三阶段66讲 LinkedBlockingQueue详细讲解_.mp4        高并发编程第三阶段67讲 SynchronousQueue详细讲解_.mp4        高并发编程第三阶段68讲 DelayQueue详细讲解_.mp4        高并发编程第三阶段69讲 LinkedBlockingDeque详细讲解_.mp4        高并发编程第三阶段70讲 LinkedTransferQueue详细讲解_.mp4        高并发编程第三阶段71讲 七大BlockingQueue的特点总结,可以不用详细看_.mp4        高并发编程第三阶段72讲 ConcurrentHashMap性能测试以及JDK1.7原理讲解_.mp4        高并发编程第三阶段73讲 ConcurrentHashMap性能测试以及JDK1.8原理讲解_.mp4        高并发编程第三阶段74讲 ConcurrentSkipListMap详细讲解_.mp4        高并发编程第三阶段75讲 ConcurrentSkipListMap vs ConcurrentHashMap_.mp4        高并发编程第三阶段76讲 ConcurrentLinkedQueue&ConcurrentLinkedDeque_.mp4        高并发编程第三阶段77讲 CopyOnWriteArrayList&CopyOnWriteArraySet源码分析_.mp4        高并发编程第三阶段78讲 ConcurrentLinkedList vs CopyOnWriteArrayList vs SynchronizedList性能对比_.mp4        高并发编程第三阶段79讲 实现一个高并发的无锁队列(Lock-Free).mp4        高并发编程第三阶段80讲 总结与回顾,闲聊与感谢.mp4

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶落闲庭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值