多线程/JUC笔记 (1

1. 线程有几个状态

有2种说法
第一种:
5个

  • 新生
  • 就绪
  • 运行
  • 阻塞
  • 死亡

第二种:
看源码6个

  • NEW 新生
  • RUNNABLE 运行
  • BLOCKED 阻塞
  • WAITING 等待,死死的等
  • TIMED_WAITING 超时等待
  • TERMINATED 终止

2. wait和sleep区别

类不同
wait是Object类
sleep是Thread类

企业中一般用TimeUnit.xxx方法来休眠

释放锁
wait会释放锁
sleep不会释放锁,抱着锁睡觉了

使用范围不同
wait只能在同步代码块中用
sleep可以在任意地方睡

捕获异常
wait不需要捕获异常
sleep需要捕获异常
 

3. Lock锁(重点)

用synchronized和用lock锁的区别

synchronizedLock
synchronized是内置java关键字lock是个java类
synchronized无法判断获取锁的状态lock可以判断是否获取了锁
synchronized会自动释放锁lock要手动释放锁,如果不释放锁,会出现死锁
synchronized 线程1(获得锁,阻塞),线程2(等待,傻傻的等)Lock有方法tryLock()不一定会一直等待下去
synchronized 可重入锁,非公平,不可中断lock 可重入锁,非公平(可以自己设置),可以判断锁
synchronized适合少量的代码同步问题lock适合锁大量的同步代码

synchronized

锁的对象是方法的调用者

 
生产者消费者问题

//用lock锁写的
Lock lock = new ReentrantLock();
    public void sale(){ //不用synchronized  用lock锁
        lock.lock();    //加锁
        try {
            //核心业务代码  放try catch final中
            if (number>0) {
                number--;
                System.out.println("线程:"+Thread.currentThread().getName()+"卖出第"+n++
                +"张票,剩余票数:"+number);
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
//用synchronized写的
public synchronized void sale(){
        if (number>0) {
            number--;
            System.out.println("线程:"+Thread.currentThread().getName()+"卖出第"+n++
            +"张票,剩余票数:"+number);
        }
    }

用if循环带来的虚假唤醒问题

如果是if解除阻塞状态时,会继续执行接下来的代码。而使用while,在解除阻塞时,需要重新判断,直到满足条件才会执行接下来的代码

public synchronized void decrement() throws InterruptedException {
        if (number == 0) {		//!!!这里应该用while!!!
            //等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + " => " + number);
        //唤醒其他人
        this.notifyAll();
    }

lock代替synchronize
在这里插入图片描述

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    
public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0) {
                //等待
                condition.await();		//代替wait()
            }
            number--;
            System.out.println(Thread.currentThread().getName() + " => " + number);
            //唤醒其他人
            condition.signalAll();		//代替notifyAll()
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

Condition 可以精准通知哪一个进程唤醒
创建多个condition分别配给每一个方法
要求 A -> B -> C -> A

public void printA(){
        lock.lock();

        try {
            while (num!=1){
                //等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+":AAAA");
            //唤醒指定人: B
            num = 2;
            condition2.signal();
        } catch (Exception exception) {
            exception.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();

        try {
            while (num!=2){
                //等待
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+":BBBB");
            num = 3;
            condition3.signal();
        } catch (Exception exception) {
            exception.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();

        try {
            while (num!=3){
                //等待
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+":CCCC");
            num = 1;
            condition1.signal();
        } catch (Exception exception) {
            exception.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

4. 8锁问题

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

  1. synchronized锁的对象是方法的调用者
  2. Phone phone = new Phone3(); 两个方法都加了static
    两个方法都加了static后,锁的是Class,这个Class全局唯一,所以并不是因为同一个对象调用,而是因为静态static是类对象,唯一, ==> 所以先发短信
    在这里插入图片描述
  3. 上面的基础上,Phone phone = new Phone3() 变成了下面, 变成了两个对象调用
    就算是两个对象分别调用,还是发短信先,因为静态,只有唯一一个Phone的Class对象,锁的是Class对象,两个对象只有一个Class类模板
    在这里插入图片描述
  4. Phone phone = new Phone4() ,只有一个对象;但是打电话不再静态
    虽然先静后普,先父后子
    但是 发短信要等4秒,所以打电话先。他们一个锁的是类对象,一个锁的是调用者,不是同一个,所以同时执行
    在这里插入图片描述
  5. 上面的基础上,Phone phone = new Phone4() 变成了下面, 变成了两个对象调用
    结果:还是打电话
    在这里插入图片描述

结论

同步静态方法的锁对象是Class,故不受创建对象的数量影响!!!
普通方法 不受锁的影响
new this 是具体的一个手机
static Class 唯一的一个模板

5. 不安全集合: CopyOnWriteArrayList

ArrayList 在并发下是不安全的
java.util.ConcurrentModificationException 并发修改异常!

public static void main(String[] args) {
       List<String> list = new ArrayList<>();	//ArrayList不安全  改!
       for (int i = 0; i < 10; i++) {
           new Thread(() -> {
               list.add(UUID.randomUUID().toString().substring(0,5));
               System.out.println(list);
           },String.valueOf(i)).start();
       }
   }

解决方案

  1. List list = new Vector<>(); 用Vector就不会报错了
  2. List list = Collections.synchronizedList(new ArrayList<>());
  3. List list = new CopyOnWriteArrayList<>(); 正确方案
    在这里插入图片描述

CopyOnWriteArrayList比Vector好在哪?

Vector用了synchronized锁;效率变慢,CopyOnWriteArrayList用的lock锁

6. 不安全集合: CopyOnWriteArraySet

基本同上
HashSet 在并发下也是不安全的
java.util.ConcurrentModificationException 并发修改异常!
解决方案

  1. Set set = Collections.synchronizedSet(new HashSet<>());
  2. CopyOnWriteArraySet set = new CopyOnWriteArraySet<>(); 正确方案

HashSet的底层是什么

HashMap
在这里插入图片描述

Map不安全

Map<String, String> map = new HashMap<>();
// 初始化容量 、 加载因子

map是这样用的吗? :不是,工作中不这样用
默认等价于什么? : Map<String, String> map = new HashMap<>(16 , 0.75);

解决方案

  1. 同上可以用Collections.synchronizedMap()…
  2. 同上用Map<String, String> map = new ConcurrentHashMap<>();
    ps: 类似于CopyOnWriteArraySet

7. Callable(简单)

在这里插入图片描述

  1. 可以有返回值
  2. 可以抛出异常

为什么Thread.start() 可以启动Callable?

  • Thread中只跟Runable扯上关系, 但是有一个类:FutureTask
  • FutureTask 有一个带Callable类型参数的构造器,所以future task可以作为runnable接口的实现类传到thread的构造器中, 所以Callable --> FutureTask --> Runable --> Thread
  • 适配类 FutureTask 即时Runable的实现类, 又有个构造器可以处理Callable
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());
        new Thread(futureTask,"A").start();
        Integer integer = futureTask.get();     //get可能会产生阻塞
        			//所以一般把他放在最后  或者  使用异步通信
        System.out.println(integer);
    }
}

class MyThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("调用了call()");
        //耗时的操作  -->  导致阻塞
        return 1024;
    }
}

输出
在这里插入图片描述

有个问题: 果然启动两个线程,那么打印结果会输出两边吗?

new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();

不会 :( 因为缓存?)
网友解答:
这个并不是缓存,是由于JVM第二次再调用FutrueTask对象所持有的线程
此时FutrueTask的state此时已非NEW状态(各个状态,这边不做详细解释)
则此时会直接结束对应线程,就会导致任务也不执行
只是在第一次调用时返回结果保存了,可能这就是老师所说的缓存

8. 常用辅助类

8.1 CountDownLatch (减法 计数器)

总数是(6) 必须要执行任务的时候再使用
等待计数器归零,然后才向下执行

public static void main(String[] args) throws InterruptedException {
        //总数是6   必须要执行任务的时候再使用
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"Go Out!");
                countDownLatch.countDown(); //数量-1
            },String.valueOf(i+1)).start();
        }
        countDownLatch.await(); //等待计数器归零,然后才向下执行

        System.out.println("走完啦 关门辣");
    }

在这里插入图片描述

8.2 CyclicBarrier (加法 计数器)

等待 会直接计数 如果没有到达7 就会阻塞,

public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙!");
        });
        for (int i = 0; i < 7; i++) {
            final int temp = i+1;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"召唤龙珠"+temp);
                try {
                    //等待  会直接计数   如果没有到达7  就会阻塞,
                    // 等到了7之后才会唤醒7个线程  所以召唤神龙后会打印7次"h "
                    cyclicBarrier.await();
                    System.out.print("h ");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

在这里插入图片描述

关于打印次数的问题: 两个都是只能开启多线程(8次),不能开启少(5次)
多了还是可以关门/召唤神龙,但是少了就不行,程序会停着未结束

8.3 Semaphore (信号量)

抢车位 限流 限制指定的线程数量
只有有车离开了,其他车就会赶紧进来

public static void main(String[] args) {
        //抢车位  限流  限制指定的线程数量
        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();    //获取
                    System.out.println(Thread.currentThread().getName()+"获得了车位");
                    System.out.println("--------"+Thread.currentThread().getName()+"停了一会---------");
                    TimeUnit.SECONDS.sleep(4);  //模拟停了一会
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println(Thread.currentThread().getName()+"离开了车位");
                    semaphore.release();    //释放
                }
            },String.valueOf(i+1)).start();
        }
    }

在这里插入图片描述

semaphore.acquire(); //获取
如果已经满了,就会等待,等待被释放位置
semaphore.release(); //释放
会将当前的信号量释放 +1

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

9. 读写锁

ReentrantReadWriteLock()
一次只能被一个线程占有(写锁 / 独占锁)
多个线程可以同时占有(读锁 / 共享锁)

public class testRW {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        //写入
        for (int i = 0; i < 5; i++) {
            final  int temp = i+1;
            new Thread(()->{
                myCache.put(temp+"",temp+"");
            },String.valueOf(temp)).start();
        }
        //读取
        for (int i = 0; i < 5; i++) {
            final  int temp = i+1;
            new Thread(()->{
                myCache.get(temp+"");
            },String.valueOf(temp)).start();
        }
    }
}

//模拟缓存
class MyCache{
    private volatile Map<String , Object> map = new HashMap<>();
    //读写锁   更加细粒度的控制
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //存 写
    public void put(String key,Object value){

        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"写入了:"+key);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"完成写入操作");
        } catch (Exception exception) {
            exception.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }

    }

    //放 读
    public void get(String key){
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"读取了:"+key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName()+"完成读取操作");
        } catch (Exception exception) {
            exception.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

加读写锁前: 不符合预期 ! 1写入了之后,还没完成写入操作就被其他人插队写入。
在这里插入图片描述
加了读写锁后
在这里插入图片描述

10. 阻塞队列

什么情况下会使用阻塞? : 多线程并发处理、线程池

队列满的时候写
队列空的时候读
都会阻塞

在这里插入图片描述
学会使用队列:
添加、移除

ArrayBlockingQueue<>() 四组API

1、 抛出异常
2、 不会抛出异常
3、 阻塞 等待
4、 超时等待

方式 \ 结果抛出异常有返回值,不抛异常阻塞 等待超时等待
添加addofferputoffer( Obj , 秒数x , TimeUnit.Second )
移除removepolltakepoll( , )
多添加结果抛异常返false一直等待等待 —> x秒后返false然后终止
多移除结果抛异常返null最后一个数不出现,一直等待等待 —> x秒后返null然后终止
检测队首元素elementpeek--
public static void test() throws InterruptedException {
        //队列大小
        ArrayBlockingQueue queue = new ArrayBlockingQueue<>(3);

        //1 加:add
//        System.out.println(queue.add("a"));
//        System.out.println(queue.add("b"));
//        System.out.println(queue.add("c"));
        //2 加:offer()
//        System.out.println(queue.offer("a"));
//        System.out.println(queue.offer("b"));
//        System.out.println(queue.offer("c"));
//        System.out.println(queue.offer("d"));
        //3 加:put   没有返回值
//        queue.put("a");
//        queue.put("b");
//        queue.put("c");
//        queue.put("d");
        //4 加:offer()
        System.out.println(queue.offer("a"));
        System.out.println(queue.offer("b"));
        System.out.println(queue.offer("c"));
//        System.out.println(queue.offer("d",3,TimeUnit.SECONDS));

        //取
        for (int i = 0; i < 3; i++) {
            //1 remove
//            System.out.println(queue.remove());
            //2 poll
//            System.out.println(queue.poll());
            //3 take
//            System.out.println(queue.take());
            //4 poll
            System.out.println(queue.poll());
        }
//           System.out.println(queue.remove());
//           System.out.println(queue.poll());
//           System.out.println(queue.take());
           System.out.println(queue.poll(3,TimeUnit.SECONDS));
    }

同步队列 SynchronousQueue

BlockingQueue queue = new SynchronousQueue();

同步队列
和其他BlockingQueue不一样,SynchronousQueue不存储元素
put了一个元素后,不能再put进去,必须先take取出来

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值