JUC并发学习

JUC 并发学习

1、什么是JUC

源码+官方文档 面试高频问

image-20201130204208571

java.util 工具包、包、分类

业务:普通的线程代码 Thread

Runnable 没有返回值

2、线程与进程

线程、进程,没有不能使用一句话说出来的技术,不扎实!

进程:一个程序,QQ.exe 程序的集合

一个进程往往可以包含多个线程,至少包含一个!

Java默认有几个线程? 2个 main、GC(垃圾回收)

线程:开了一个进程Typora,写字,自动保存(线程负责的)

对于Java而言:Thread、Runnable、Callable

Java真的可以开启线程吗? 开不了

public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

// 有native修饰的方法是本地方法,底层C++,java无法直接操控硬件,因为java是运行在虚拟机上的
private native void start0();

并行,并发

并发编程:并发、并行

并发(多线程操作同一个资源)

  • CPU一核,模拟出来多条线程,天下武功,为快不破,快速交替

并行(多个人一起行走)

  • CUP多核,多个线程可以同时执行;线程池
public static void main(String[] args) {
    // 获取cpu的核数
    // CPU 密集型, IO 密集型
    System.out.println(Runtime.getRuntime().availableProcessors());
}

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

所有的公司都很看重!

线程有几个状态

public enum State {
    // 新生
    NEW,
	// 运行
    RUNNABLE,
	// 堵塞   
    BLOCKED,
	// 等待,死死的等
    WAITING,
	// 超时等待
    TIMED_WAITING,
	// 终止
    TERMINATED;
}

wait/sleep 区别

1、来自不同的类

wait=>Object

sleep=>Thread

2、关于锁的释放

wait 会释放锁,sleep 不会释放锁!

3、使用的范围是不同的

wait:wait必须在同步代码块中

sleep 可以在任何地方睡

4、是否需要捕获异常

wait不需要捕获异常

3、Lock锁(重点)

传统Synchronized

package com.company.project.test;

/**
 * @Author: gcl
 * @create: 2020/11/30 20:50
 */
// 基本的买票例子

/**
 * 真正的多线程开发,公司中的开发,降低耦合性
 * 线程就是一个单独的资源类,没有任何附属的操作哦
 * 1、属性、方法
 */
public class ThreadTest extends Thread {
    // 并发,多线程操作同一个资源类,把资源类丢进线程
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale();}, "A").start();
        new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale();}, "B").start();
        new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale();}, "C").start();
    }
}

// 资源类 OOP
class Ticket {

    private int number = 30;

    public synchronized void sale() {
        if (number>0)
            System.out.println(Thread.currentThread().getName() + "剩余" + number-- + "票。");
    }

}

执行结果

// 可以看到从头到尾都是B线程在跑
...
B剩余9票。
B剩余8票。
B剩余7票。
B剩余6票。
B剩余5票。
B剩余4票。
B剩余3票。
B剩余2票。
B剩余1票。
Disconnected from the target VM, address: '127.0.0.1:14265', transport: 'socket'

Lock接口

image-20201130220715550

image-20201130221312367

公平锁:十分公平,可以先来后到

非公平锁:十分不公平,可以插队(默认)

为什么Lock与Synchronized默认都是非公平锁?

​ 假如一个线程3h完成,一个线程3s完成,3h的线程在3s的前面,这时候如果是公平锁的话,3s的要等待3h之后才能执行!

public ReentrantLock() {
    sync = new NonfairSync();	// 默认非公平锁
}

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {	// 有参构造可以转变成公平锁
    sync = fair ? new FairSync() : new NonfairSync();
}
package com.company.project.test;

/**
 * @Author: gcl
 * @create: 2020/11/30 20:50
 */
// 基本的买票例子

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

/**
 * 真正的多线程开发,公司中的开发,降低耦合性
 * 线程就是一个单独的资源类,没有任何附属的操作哦
 * 1、属性、方法
 */
public class ThreadTest extends Thread {
    // 并发,多线程操作同一个资源类,把资源类丢进线程
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale();}, "A").start();
        new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale();}, "B").start();
        new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale();}, "C").start();
    }
}

/**
 * lock三部曲
 * 1、new ReentrantLock();
 * 2、lock.lock();   // 加锁
 * 3、finally=>  lock.unlock();  // 解锁
 */
class Ticket {
    // 属性、方法
    private int number = 30;
    Lock lock = new ReentrantLock();

    public void sale() {

        lock.lock();    // 加锁

        try {
            if (number>0)
                System.out.println(Thread.currentThread().getName() + "剩余" + number-- + "票。");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 解锁
            lock.unlock();
        }
    }
}

执行效果

...
A剩余11票。
A剩余10票。
A剩余9票。
A剩余8票。
A剩余7票。
A剩余6票。
A剩余5票。
A剩余4票。
A剩余3票。
A剩余2票。
A剩余1票。
Disconnected from the target VM, address: '127.0.0.1:14389', transport: 'socket'

Synchronized 和 Lock 区别

1、Synchronized 内置的Java关键字,Lock是一个Java类

2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取了锁

3、Synchronized 会释放锁,lock必须要手动释放锁!如果不释放,死锁

4、Synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去;

5、Synchronized 可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置);

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

锁是什么,如何判断锁的是谁?

4、生产者、消费者问题

示例代码:

package com.company.project.practice;

/**
 * @Author: gcl
 * @create: 2020/12/13 19:12
 */

public class A {

    public static void main(String[] args) {
        Data data = new Data();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }

}

// 判断等待,业务,通知
class Data {    // 数字   资源类
    private int number = 0;

    // +1
    public synchronized void increment() throws InterruptedException {
        if (number != 0) {
            // 等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        //通知其他线程,我+1完毕了
        this.notifyAll();
    }

    // -1
    public synchronized void decrement() throws InterruptedException {
        if (number == 0) {
            // 等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        //通知其他线程,我-1完毕了
        this.notifyAll();
    }
}

运行结果:

B=>1
A=>0
B=>1
A=>0
B=>1
A=>0
B=>1
A=>0
B=>1
A=>0
B=>1
A=>0
B=>1
A=>0
B=>1
A=>0
B=>1
A=>0
B=>1
A=>0

问题存在:如果是四个线程那还安全吗?

新增两个线程

new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        try {
            data.decrement();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
},"C").start();

new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        try {
            data.increment();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
},"D").start();

image-20201213210940987

看到程序不仅数值异常,并且出现了死锁的情况

原因如下:

image-20201213210754055

所以在Data中将if换成while即可

while (number == 0) {
    // 等待
    this.wait();
}

使用Lock锁实现生产、消费

package com.company.project.practice;

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

/**
 * @Author: gcl
 * @create: 2020/12/13 19:12
 */

public class A {

    public static void main(String[] args) {
        Data data = new Data();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }

}

// 判断等待,业务,通知
class Data {    // 数字   资源类
    private int number = 0;

    Lock lock = new ReentrantLock();			// 1
    Condition condition = lock.newCondition();		// 2

    // condition.await();   等待
    // condition.signalAll();   喚醒全部

    // +1
    public void increment() throws InterruptedException {
        lock.lock();		// 3
        try {
            while (number != 0) {
                // 等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程,我+1完毕了
            condition.signalAll();	// 4
        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();	// 5
        }
    }

    // -1
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0) {
                // 等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程,我+1完毕了
            condition.signalAll();
        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

任何一个新的技术,绝不仅仅只是覆盖了原来的技,优势和补充!

Condition 精准的通知和唤醒线程

解决代码:

package com.company.project.practice;

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

/**
 * @Author: gcl
 * @create: 2020/12/13 19:12
 */

public class C {

    public static void main(String[] args) {
        Data3 data = new Data3();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printA();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printB();
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printC();
            }
        },"C").start();
    }

}

// 判断等待,业务,通知
class Data3 {    // 数字   资源类

    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    private int number = 1; // 1A 2B 3C

    public void printA() {
        lock.lock();
        try {
            // 业务,判断-> 执行 -> 通知
            while (number != 1) {
                condition1.await();
            }

            number++;
            System.out.println(Thread.currentThread().getName() + "=>AAAAAA");
            condition2.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printB() {
        lock.lock();
        try {
            // 业务,判断-> 执行 -> 通知
            while (number != 2) {
                condition2.await();
            }

            number++;
            System.out.println(Thread.currentThread().getName() + "=>BBBBBB");
            condition3.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }


    public void printC() {
        lock.lock();
        try {
            // 业务,判断-> 执行 -> 通知
            while (number != 3) {
                condition3.await();
            }

            number = 1;
            System.out.println(Thread.currentThread().getName() + "=>CCCCCCCC");
            condition1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

}

image-20201213225945790

5、线程八锁问题

如何判断锁的是谁!永远的知道是什么是锁,锁到底锁的是谁!

对象,Class

synchronized锁的对象是调用

package com.company.project;

/*
 * 题目:判断打印的 "one" or "two" ?
 * 
 * 1. 两个普通同步方法,两个线程,标准打印, 打印? //one  two
 * 2. 新增 Thread.sleep() 给 getOne() ,打印? //one  two
 * 3. 新增普通方法 getThree() , 打印? //three  one   two
 * 4. 两个普通同步方法,两个 Number 对象,打印?  //two  one
 * 5. 修改 getOne() 为静态同步方法,打印?  //two   one
 * 6. 修改两个方法均为静态同步方法,一个 Number 对象?  //one   two
 * 7. 一个静态同步方法,一个非静态同步方法,两个 Number 对象?  //two  one
 * 8. 两个静态同步方法,两个 Number 对象?   //one  two
 * 
 * 线程八锁的关键:
 * ①非静态方法的锁默认为  this,  静态方法的锁为 对应的 Class 实例
 * ②某一个时刻内,只能有一个线程持有锁,无论几个方法。
 */
public class TestThread8Monitor {
    
    public static void main(String[] args) {
        Number number = new Number();
        Number number2 = new Number();
        
        new Thread(() -> {
            Number.getOne();
        }).start();
        
        new Thread(() -> {
            number2.getTwo();
        }).start();
        
        /*new Thread(new Runnable() {
            @Override
            public void run() {
                number.getThree();
            }
        }).start();*/
        
    }
}

class Number{
    
    public static synchronized void getOne(){//Number.class
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        
        System.out.println("one");
    }
    
    public synchronized void getTwo(){//this
        System.out.println("two");
    }
    
    public void getThree(){
        System.out.println("three");
    }
    
}

6、集合类不安全

List

List不安全

package com.company.project.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author gaocl4
 * @date 2020/12/15 12:00
 */
// Exception in thread "0" java.util.ConcurrentModificationException
public class ListTest {

    public static void main(String[] args) {
        // 并发下 ArrayList 不安全的吗,Synchronized

        /**
         * 解决方法:
         * 1、List<String> list = new Vector<>();    //可以进入add方法中看到
         * 这里的add是由synchronized修饰的
         * public synchronized boolean add(E e) {
         *         modCount++;
         *         ensureCapacityHelper(elementCount + 1);
         *         elementData[elementCount++] = e;
         *         return true;
         *     }
         *
         * 2、List<String> list = Collections.synchronizedList(new ArrayList<>());
         *
         * 3、List<String> list = new CopyOnWriteArrayList<>();
         *  public boolean add(E e) {
         *         final ReentrantLock lock = this.lock;
         *         lock.lock();
         *         try {
         *             Object[] elements = getArray();
         *             int len = elements.length;
         *             Object[] newElements = Arrays.copyOf(elements, len + 1);
         *             newElements[len] = e;
         *             setArray(newElements);
         *             return true;
         *         } finally {
         *             lock.unlock();
         *         }
         *     }
         *
         */

        List<String> list = new ArrayList<>();

        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list.toString());
            }, String.valueOf(i)).start();
        }
    }

}

下面是普通的ArrayList()

image-20201215134617342

可以看到会报 并发修改异常

Exception in thread "22" java.util.ConcurrentModificationException

Set

Set集合不安全

package com.company.project.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.stream.Collectors;

/**
 * @author gaocl4
 * @date 2020/12/15 12:00
 */
// Exception in thread "0" java.util.ConcurrentModificationException
public class SetTest {

    public static void main(String[] args) {

        // Set<String> list = new HashSet<>();
        // Set<String> list = Collections.synchronizedSet(new HashSet<>());
        Set<String> list = new CopyOnWriteArraySet<>();

        for (int i = 0; i < 50; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list.toString());
            }, String.valueOf(i)).start();
        }
    }

}

hashSet 底层是什么?

进入底层查看

public HashSet() {
    map = new HashMap<>();
}

可以看到,底层是new了一个hashMap

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

add方法是将Map的key存进集合中

Map

Map 集合不安全

HashMap:

image-20201215142745171

查看Java Api

image-20201215143229207

public class MapTest {

    public static void main(String[] args) {

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

        for (int i = 0; i < 50; i++) {
            new Thread(() -> {
                list.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(list.toString());
            }, String.valueOf(i)).start();
        }
    }
}

7、Callable(简单)

image-20201215150633538

以前的类实现线程

public class CallableTest {

    public static void main(String[] args) {
        new Thread(new Thread()).start();
    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        
    }
}

JUC中是使用Callable接口实现,为什么?

查看API,可以看到Runablle中有一个FutureTask的实现类

image-20201215150026494

在FutrueTask的构造方法中可以看到

image-20201215150153362

// 实例:不指定返回类型
class MyThread implements Callable{

    @Override
    public Object call() throws Exception {
        return null;
    }
}
// 实例:指定返回类型,在实现Callable接口时,可以通过Callable<Type>,在Type中指定返回的类型
class MyThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("call me");
        return 1024;
    }
}

实测:

public class CallableTest {

    public static void main(String[] args) {
        // new Thread(new Thread()).start();
        new Thread(new FutureTask<>(new MyThread())).start();
    }
}

class MyThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("call me");
        return 1024;
    }
}
结果:
call me

亦可写成

public class CallableTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // new Thread(new Thread()).start();

        MyThread thread = new MyThread();
        // 适配类
        FutureTask futureTask = new FutureTask(thread);

        new Thread(futureTask, "testThread").start();

        // 获取返回值, 这个get方法可能会产生阻塞!
        Integer o = (Integer) futureTask.get();
        System.out.println(o);
    }
}

class MyThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("call me");
        // 耗时的操作
        return 1024;
    }
}
结果
call me
1024

细节:

  • 有缓存
  • 结果可能需要等待,会阻塞!

8、常用的辅助类

8.1、CountDownLatch

image-20201215154320047

实例:

package com.company.project.utils;

import java.util.concurrent.CountDownLatch;

/**
 * @author gaocl4
 * @date 2020/12/15 15:44
 */
// 计数器
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 总数是六,必须要执行任务的时候,在使用!
        CountDownLatch countDownLatch = new CountDownLatch(7);

        for (int i = 0; i < 7; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "Go out");
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        
//        countDownLatch.await();     // 等待计数器归零,然后再向下执行

        System.out.println("Close Door");
    }

}
Close Door
0Go out
1Go out
2Go out
3Go out
4Go out
5Go out
6Go out

可以看到,如果注掉countDownLatch.await();线程全部自由执行

如果打开countDownLatch.await();

1Go out
0Go out
3Go out
4Go out
2Go out
5Go out
6Go out
Close Door

原理:

countDownLatch.countDown(); // 数量减1

countDownLatch.await(); // 等待计数器归零,然后再向下执行

每次有线程调用 countDown() 数量-1, 假设计数器变为零,countDownLatch.await() 就会被唤醒,继续执行!

8.2、CyclicBarrier

image-20201215160746462

加法计数器

/**
 * @author gaocl4
 * @date 2020/12/15 16:08
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) {

        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("召喚神竜!");
        });

        for (int i = 1; i <= 7; i++) {
            final int temp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集到了" + temp + "星球!");

                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }
}
Thread-0收集到了1星球!
Thread-1收集到了2星球!
Thread-2收集到了3星球!
Thread-3收集到了4星球!
Thread-4收集到了5星球!
Thread-5收集到了6星球!
Thread-6收集到了7星球!
召喚神竜!

如果是CyclicBarrier在构造方法传的时候是8的话,就得等8个线程才释放

image-20201215161605671

这样程序就会一直等待下一个线程。

8.3、Semaphore

image-20201215163518354

实例代码:

// 停车位场景,限流!
public class SemaphoredDemo {

    public static void main(String[] args) {
        // 线程数量,停车位!限流!

        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                // acquire() 得到
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢到车位!");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + "离开车位!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();    // release() 释放
                }
            }, String.valueOf(i)).start();
        }
    }
}
0抢到车位!
1抢到车位!
2抢到车位!
0离开车位!
3抢到车位!
1离开车位!
4抢到车位!
2离开车位!
5抢到车位!
3离开车位!
5离开车位!
4离开车位!

原理:

semaphore.acquire(); 获得,假设如果已经满了,等待,等到被释放为止!

semaphore.release(); 释放,会将当前的信号量释放+1,然后唤醒等待的线程!

作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!

9、ReadWriteLock

posted @ 2020-12-15 16:49  Fries_Angle  阅读(95)  评论(0)    收藏  举报