【JUC】并发编程学习笔记(二)

8 篇文章 0 订阅

五、多线程锁

5.1、synchronized实现同步的基础

Java中的每一个对象都可以作为锁。
具体表现为以下3种形式。

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的class对象。
  • 对于同步方法块,锁是synchonized括号里配置的对象

5.2、公平锁与非公平锁

//创建非公平锁
//可能一个线程吃掉所有任务 其他线程被“饿死” 但效率高
private final ReentrantLock lock = new ReentrantLock(false);
//创建公平锁
//每个线程都领到任务 但效率降低
private final ReentrantLock lock = new ReentrantLock(true);

5.3、可重入锁

同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提是锁对象得是同一个对象),不会因为之前已经获取过锁还没有释放而发生阻塞

class RLock {
    public synchronized void add(){
        add();
    }
}
public class Demo06 {
    public static void main(String[] args) {
        new RLock().add();
    }
}

此时不会堵塞 只会栈溢出java.lang.StackOverflowError

5.4、死锁

5.4.1、概念

两个或者两个以上进程在执行过程中,因为争夺资源而造成一种互相等待的现象。如果没有外力干涉,他们无法再执行下去。
在这里插入图片描述

产生死锁原因:

  1. 系统资源不足
  2. 进程运行推进顺序不合适
  3. 资源分配不当

5.4.2、案例

import java.util.concurrent.TimeUnit;

public class DeadLock {

    static Object o1=new Object();
    static Object o2=new Object();

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (o1){
                System.out.println(Thread.currentThread().getName()+"持有o1,试图获取o2");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2){
                    System.out.println(Thread.currentThread().getName()+"获取o2");
                }
            }
        },"aa").start();

        new Thread(()->{
            synchronized (o2){
                System.out.println(Thread.currentThread().getName()+"持有o2,试图获取o1");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1){
                    System.out.println(Thread.currentThread().getName()+"获取o1");
                }
            }
        },"bb").start();
    }
}

5.4.3、查看死锁

  1. jps
    先输入jps -l找到进程号
  2. jstack
    jvm自带堆栈跟踪工具
    输入jstack 进程号在最后提示Found xxx deadlock.发现xx个死锁

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

六、Callable接口

6.1、概述

通过使用Runnable 创建线程。但是,Runnable 缺少的一项功能是,当线程终止时(即run ( )完成时),我们无法使线程返回结果。为了支持此功能,Java中提供了Callable接口。

RunnableCallable
是否有返回值
是否抛出异常
实现方法run()call()

6.2、FutureTask概述和原理

FutureTask类 实现了RunnableFuture接口,而RunnableFuture 继承了Runnable和Future接口,所以本质上 FutureTask是Runnable的一种实现。

6.3、案例

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

//比较Callable Runable
class MyThread1 implements Runnable{
    @Override
    public void run() {

    }
}
class MyThread2 implements Callable{
    @Override
    public Integer call() throws Exception {
        return 200;
    }
}
public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建Runable线程
        new Thread(new MyThread1(),"aa").start();

        //创建Callable线程
        //不能直接替换runnable的方式,因为Thread类的构造方法根本没有Callable.
        //FutureTask构造可以传递callable

        //创建FutureTask线程
        FutureTask<Integer> futureTask1=new FutureTask<>(new MyThread2());

        //lambda简化FutureTask线程
        FutureTask<Integer> futureTask2=new FutureTask<>(()->{
            System.out.println(Thread.currentThread().getName()+" in Callable");
            return 1024;
        });

        new Thread(futureTask2,"bb").start();
        while (!futureTask2.isDone()){
            System.out.println("wait");
        }
        System.out.println(futureTask2.get());
        System.out.println(futureTask1.get());
        System.out.println(Thread.currentThread().getName()+" over");
    }
}

七、辅助类

7.1、减少计数CountDownLatch

CountDownLatch类可以设置一个计数器,然后通过countDown方法来进行减1的操作,使用await方法等待计数器不大于0,然后继续执行await方法之后的语句。

  • CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞
  • 其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)
  • 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行

案例:6个同学陆续离开教室后才可以关门
正常写法

public class CountDownLatchDemo {
    public static void main(String[] args) {
        //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()+"锁门了");
    }
}

会出现问题:
在这里插入图片描述
更改之后:

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {

        //创建CountDownLatch
        CountDownLatch countDownLatch=new CountDownLatch(6);

        //6个同学陆续离开教室之后
        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread( ).getName()+"号同学离开了教室");
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }
        //等待
        countDownLatch.await();
        System.out.println(Thread.currentThread( ).getName()+"锁门了");
    }
}

在这里插入图片描述

7.2、循环棚栏CyclicBarrier

允许一组线程全部等待彼此达到共同屏障点的同步辅助。循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。

例子:集齐7颗龙珠就可以召唤神龙

import java.util.concurrent.BrokenBarrierException;
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("已经集齐7颗龙珠,可以召唤神龙了");
        });
        for (int i = 1; i <=7; i++) {
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread( ) .getName()+"星龙被收集到了");
                    //等待
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

在这里插入图片描述

7.3、信号灯Semaphore

一个单向隧道能同时容纳10个小汽车或5个卡车通过(1个卡车等效与2个小汽车), 而隧道入口记录着当前已经在隧道内的汽车等效比重. 比如1个小汽车和1个卡车, 则隧道入口显示3. 若隧道入口显示10表示已经满了. 当汽车驶出隧道之后, 隧道入口显示的数字则会相应的减小. 于这个示例相符合场景非常适合用信号量.

案例:抢车位,6部汽车3个停车位

import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

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(5));
                    System.out.println(Thread.currentThread( ) .getName()+"离开了车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值