0
点赞
收藏
分享

微信扫一扫

JUC基础知识(个人总结)

念川LNSC 2022-03-11 阅读 29
javajuc

    声明: 1. 本文为我的个人复习总结, 并那种从零基础开始普及知识 内容详细全面, 言辞官方的文章
              2. 由于是个人总结, 所以用最精简的话语来写文章
              3. 若有错误不当之处, 请指出

一. 前置基础:

  • IO 操作不占用 cpu, 只是我们一般拷贝文件使用的是【阻塞 IO】, 这时相当于线程虽然不用 cpu, 但需要一

    直等待 IO 结束, 没能充分利用线程。

    所以才有后面的【非阻塞 IO】和【异步 IO】优化

  • synchronized同步 是互斥的

  • 锁的本质是 大家对一个共享资源进行互斥访问

  • 栈是线程私有, 堆是多线程共享的;

    但不意为着线程栈里的局部变量就被别的线程访问到, 需要先进行逃逸分析;

    比如将局部变量传递给方法内部的开辟的线程, 那样此变量就逃逸出此方法了, 便会有线程安全问题

  • 进程 是对运行时程序的封装,操作系统进行资源调度资源分配的基本单位

    线程 是cpu进行调度的基本单位

    管程 是监视器, 即synchronized锁

    举例: 一个正在运行的Java程序是一个JVM进程, 其中既有用户线程, 又有GC垃圾回收线程(守护线程); 可见一个进程里可以有多个线程

  • 无论是主线程还是自定义线程,只有有存活JVM就不会结束;

    而当仅有守护线程时,JVM便会结束

  • 锁粒度不能太大, 但又不能太小; 既要考虑并发能力, 又要考虑安全性

查看进程线程的方法

栈帧:

每个栈由多个栈帧(Frame)组成, 对应着每次方法调用时所占用的内存

每个线程只能有一个活动栈帧, 对应着当前正在执行的那个方法

栈帧内包括: 局部变量, 返回地址, 操作数栈, args参数, 锁记录

多线程是为了充分利用cpu资源, 不会因为单个的阻塞而阻塞整个业务流程。

多线程串行 相比于 单线程 的优势:

​ 多线程串行, 即便有个线程阻塞了, 也不会影响别的线程;

​ 而且当未对临界区进行修改发生竞态条件时, 是不需要加锁的, 这时优势就又更明显了

同步/异步, 是从 多个线程间的 业务逻辑关系或回调通知机制 的角度来看的

串行/并行, 是从 多个线程间的 实际执行过程 的角度来看的

异步业务: 商品详情功能业务 & 评论功能业务
同步业务: 登录功能业务 & 购物车功能业务

一般而言, 由于业务是异步的关系, 导致了并行执行; 由于业务是同步的关系, 导致了串行执行

同步:

  1. 两个事物保持某种相对关系
  2. syncronized互斥
  3. 消息通知机制上 没有回调
  4. 业务同步, 有先后执行顺序

join( ): 插队, 即等此线程结束

join(3000L) 最多让插队3s, 超时main就不等t1了,t1走t1的, main继续往下走

// t1花费1s, t2花费2s, 总花费并不是3s, 而是2s; t1和t2可以看作并行运行
public static main(String[] args){
    Thread t1 = new Thread(( ) -> {
        sleep(1);
        r1 = 10;
    });
    Thread t2 = new Thread(( ) -> {
        sleep(2);
        r2 = 20;
    });
    t1.start( );
    t2.start( );
    t1.join( );	//是main线程调用,故插队到main前
    t2.join( );
}		

yield( ): 礼让一下

LockSupport.park( ) LockSupport.unpark( );

可以先调用unpark, 那么下一次park就会无效; 注意先调用的unpark只能累加一次, 不能叠加

操作系统层面的线程状态:

在这里插入图片描述

在这里插入图片描述

JVM层面的线程状态:

在这里插入图片描述

Java层面的 RUNNABLE 状态涵盖了操作系统层面的【可运行状态】、【运行状态】和【阻塞状态】(BIO导致的线程阻塞, 在Java里无法区分, 仍然认为是可运行)

Java层面的阻塞状态有: BLOCKED(没抢到锁), WAITING(wait), TIMED_WAITING(带时间的wait)

线程通信的几种方式:

  • 共享内存(全局变量)
  • synchronized锁
  • wait( )/notify( )
  • join( )
  • 并发工具: CountDownLatch, CyclicBarrier
  • 管道
  • 信号量

二. synchronized底层:

synchronized底层是Monitor, 翻译为监视器, 重量级锁或管程

Monitor底层结构:

  1. Owner 抢到锁的线程
  2. EntryList 没抢到锁而阻塞的线程
  3. WaitSet wait的线程, 相当于一间休息室; 所以wait必须放在synchronized中执行

synchronized的字节码:

在这里插入图片描述

第4行的astore是存放锁的引用, 因为后面需要找到锁进行释放

monitorenter后有两个monitorexit, 第一个monitorexit是正常执行时的释放锁, 第二个monitorexit是发生异常时进行释放锁

锁的相关信息(线程id, 是否为偏向锁, 锁重入的次数)放在对象头的MarkWord里存储

JDK6中引入了偏向锁&轻量级锁, 有了锁升级的概念

1. 偏向锁:

轻量级锁在没有竞争时(就自己一个线程), 每次重入仍然需要执行CAS操作

而偏向锁: 只有第一次使用CAS时将线程ID设置到对象的Mark Word头, 之后发现这个线程ID是自己的 就表示没有竞争, 不用重新CAS

偏向锁可以重偏向到其他线程

撤销偏向锁:

​ 即升级为轻量级锁, 需要STW

如果撤销偏向到达某个阈值, 那么整个类的所有对象都会变为不可偏向的

2. 轻量级锁:

多个线程错开访问, 没有竞争时 可以使用轻量级锁来优化

3. 重量级锁:

多线程间有竞争

synchronized锁的状态只能升级不能降级

其他锁的概念:

自旋锁: 抢锁失败后先别着急阻塞, 而是多循环尝试几次; 因为线程切换 阻塞状态和可运行状态 是需要代价的

​ 是使用CAS来实现的

锁膨胀: 即锁升级, 偏向锁->轻量级锁->重量级锁

锁粗化: 连续好几个synchronized同一个锁对象, 且中间未夹杂其他代码, 则被粗化成一个synchronized代码块

锁消除: 逃逸分析后发现局部变量没有逃逸出此方法, 便会对此局部变量加的锁消除掉

死锁: 两个线程互相把此对方的锁, 且都不愿意先释放自己的锁, 造成一种互相等待的僵局

活锁: 两个线程执行相反的操作, 比如A线程对初始值为10的cnt进行++操作, B线程对cnt进行–操作;

​ 两个线程一直在运行, cnt的值却很久都达到不了0, 结束不了运行

公平锁: 所有线程雨露均沾, 防止有些线程太强了干完了所有活, 导致其他线程饥饿着没活干;

​ 但这个会降低并发, 所以不建议使用;

​ new ReentrantLock(true); 是公平锁, 默认为false

可重入锁: 在锁代码块内部重复获得相同的锁, 又称递归锁

三. 多线程安全问题:

多线程安全问题: 多个线程对共享的一块内存进行写操作, 即临界区发生了竞态条件

一段代码块内如果存在对共享资源的多线程读写操作, 称这段代码块为临界区

多个线程在临界区内执行, 由于代码的执行序列不同而导致结果无法预测, 称之为发生了竞态条件

虚假唤醒: if 里线程被wait打断后再醒来时可能数据已经变了, 而却不重复判断

1. 原子性:

  • i++的原子性问题: 最初cnt=0, A线程++得到1,还没的及写入却被阻塞了,B又对0–成-1写入, 这时A继续写为1; 导致了0 ++ --=1

  • 多个操作间的原子性问题: 线程切换导致if判断不准确。比如10000元去买东西, if(10000>8000), if(10000>4000), if(10000>2000), 因为线程被打断来不及扣减, 最终10000-8000-4000-2000<0 负债了

解决方案:

  1. 阻塞式的解决方案:synchronized, AQS

  2. 非阻塞式的解决方案:原子类(即CAS+volatile)

2. 可见性:

JIT编译将热点代码缓存到工作内存里, 那样工作内存里存储的就不是主内存里的最新数据

解决方案:

  1. volatile, 保证可见性和有序性
  2. 有synchronized即可禁止JIT编译将此部分代码缓存到工作内存, 无论锁谁都行
  3. 全局禁用JIT(不推荐)

3. 有序性:

cpu调整指令顺序

比如一会加载a, 一会加载b, 循环1000次; cpu发现单线程下不影响结果的前提下, 肯定优先先加载1000次a, 再去加载1000次b

int a = 0;    
boolean flag = false;    
public void writer( ){    
    a = 1;                 
    flag = true;   
}    
	
public void reader( ){    
    if(flag){       
	int i = a + a; 
    }    
}

解决方案:

​ 加锁, synchronized或AQS

综上:

  • volatile能解决 可见性 & 有序性 问题

  • 乐观锁CAS 能解决 原子性 & 可见性 & 有序性 问题

  • 悲观锁synchronized或AQS 能解决原子性 & 可见性 & 有序性问题

  • 用private和final修饰方法,可以避免一些问题, 因为如果不然的话, 在子类中, 创建了新线程让新的线程共享list局部变量, 就坑爹了

  • as-if-serial规则保证 单线程下的指令重排, 不会影响执行结果

    happens-before规则保证 多线程下的指令重排, 不会影响执行结果

happens-before规则:

规定了哪些写操作对其它线程的读操作可见, 它是可见性有序性的一套规则总结

一个操作happens-before另一个操作,表示第一个的操作结果对第二个操作可见

volatile底层原理:

是更轻量级的锁

可见性: 被volatile修饰的变量执行写操作时: 立即将工作内存中对此变量的数据刷写到主内存中, 并使得其他线程工作内存中对此变量的缓存失效

有序性: volatile修饰的共享变量在被读写时会 加入内存屏障,阻止其他读写操作越过屏障

​ volatile 读写加入的屏障只能防止同一线程内的指令重排

JMM内存模型对volatile的排序规则为:

  • 当第一个操作是volatile读时,无论第二个操作是什么都不能进行重排序

  • 当第二个操作是volatile写时,无论第一个操作是什么都不能进行重排序

  • 当第一个操作是volatile写,第二个操作为volatile读时,不能进行重排序

JMM(Java内存模型)

JMM 定义了一套保障数据的可见性有序性原子性的规则;

架构:

  1. 工作内存 线程私有
  2. 主内存 线程公有的

内存屏障:

Load是读, Store是写

  1. LoadLoad

    Load1, LoadLoad, Load2; 即先Load1, 再Load2

  2. StoreStore

    Store1, StoreStore, Store2; 即先Store1, 再Store2

  3. LoadStore

    Load1, LoadLoad, Store2; 即先Load1, 再Store2

  4. StoreLoad

    Store1, StoreStore, Load2; 即先Store1, 再Load2

转账案例:

public class ExerciseTransfer {
    public static void main(String[] args) throws InterruptedException {
        Account a = new Account(1000);
        Account b = new Account(1000);
        Thread t1 = new Thread(( ) -> {
            for (int i = 0; i < 1000; i++) {
                a.transfer(b, randomAmount( ));
            }
        }, "t1");
        Thread t2 = new Thread(( ) -> {
            for (int i = 0; i < 1000; i++) {
                b.transfer(a, randomAmount( ));
            }
        }, "t2");
        t1.start( );
        t2.start( );
        t1.join( );
        t2.join( );
        log.debug("total:{}", (a.getMoney( ) + b.getMoney( )));
    }

    static Random random = new Random( );

    public static int randomAmount( ) {
        return random.nextInt(100) + 1;
    }
}

class Account {
    private int money;

    public Account(int money) {
        this.money = money;
    }

    public int getMoney( ) {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    public void transfer(Account target, int amount) {
        // 注意, 不能锁this, 因为上文一个线程 a.transfer(b, randomAmount( ));, 另一个线程 b.transfer(a, randomAmount( ));
        // 当this是a时, 线程二的b根本不理会this(a)这把锁
        synchronized (Account.class) {
            if (this.money > amount) {
                this.setMoney(this.getMoney( ) - amount);
                target.setMoney(target.getMoney( ) + amount);
            }
        }
    }
}

四. CAS与原子类:

CAS:

CAS(compare and set/swap)是乐观锁, 本质并不是锁, 而是while循环进行版本号判断

悲观锁 VS 乐观锁:

悲观锁靠阻塞实现, 乐观锁使用循环代替阻塞

悲观锁: 适用于锁竞争激烈的场景(读少写多)

​ 优点: 线程抢不到锁 阻塞时会让出cpu

​ 缺点: 线程切换状态, 有一定的开销

乐观锁: 适用于锁竞争不激烈(读多写少), 且cpu核数较多的场景

​ 优点: 线程不用切换状态

​ 缺点: 线程就一直执行while占用cpu

  • CAS 是基于乐观锁的思想: 不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗
  • Synchronized 是基于悲观锁的思想: 防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会

原子类:

底层使用CAS+volatile实现无锁并发

JUC包中的4种原子类:

  • 基本类型

    更新基本类型

    • AtomicInteger:整形原子类
    • AtomicLong:长整型原子类
    • AtomicBoolean:布尔型原子类
  • 数组类型

    更新数组里的某个元素

    • AtomicIntegerArray:整形数组原子类
    • AtomicLongArray:长整形数组原子类
    • AtomicReferenceArray:引用类型数组原子类
  • 引用类型

    • AtomicReference
    • AtomicStampedReference:
    • AtomicMarkableReference:
  • 更新类的字段

    需要配合volatile使用, volatile修饰该字段

    • AtomicIntegerFieldUpdater: 整型字段的更新器
    • AtomicLongFieldUpdater: 长整型字段的更新器。
    • AtomicReferenceFieldUpdater:引用类型的更新器

ABA问题:

值A变成了B后又变回了A, 这两个A认不认为它是同一个版本呢?

​ AtomicReference和AtomicMarkableReference认为是(即存在ABA问题), AtomicStampedReference认为不是

​ 解决: 使用版本号

LongAdder:

LongAdder的累加效率要比AtomicLong高:

LongAdder 分为 base 和 cells 两部分:

  1. 没有并发竞争的时候或者是 cells 数组正在初始化的时候,会使用 CAS 来累加到base
  2. 有并发竞争时,会初始化 cells 数组,数组有多少个 cell,就允许有多少线程并行修改; 最后将数组中每个 cell 累加,再加上 base 就是最终的值

相当于降低了锁的粒度, 只锁cells累加单元, 而不是锁住所有

UnSafe:

这个类很底层, 不建议我们直接使用

像CAS和NIO的直接内存, 都是通过底层调用UnSafe来实现的

五. 常见线程安全类:

String

Integer

StringBuffer

Random

Vector

Hashtable

java.util.concurrent 包下的类

这里的安全指的是 多个线程操作同一个实例时不会带来线程安全问题

String和Integer都是不可变类, 所谓的修改不过是new一个新的对象返回

对于Hashtable, 单个API是原子性的,但是组合起来不能保证其原子性,

如下图: 原本是想着table为null才put值, 结果因为API组合起来不具备原子性, 从而put了两次

在这里插入图片描述

六. ReentrantLock:

特点:

  1. 支持多个Condition条件变量, 即具有多个WaitSet休息室, 可以只叫醒某一个休息室的线程

    await signal signalall

  2. 可以使用tryLock

  3. 得手动释放锁

  4. 可重入

  5. 可以设置超时时间

  6. 可以设置为公平锁

  7. 可中断

ReentrantReadWriteLock:

读锁是共享锁, 写锁是排他锁, 写锁可以降级为读锁

七. Callable, Future:

Callable:

// 有返回值; 且抛出了异常,子类不必非得try catch了
class MyThread implements Callable<Integer> {
    @Override
    public Integer call( ) throws Exception {
        return 200;
    }
}

Future:

  • 在主线程中需要执行比较耗时的操作时但又不想阻塞主线程时,可以把这些作业交给Future在后台完成,

    当主线程将来需要时,就可以通过Future的get方法获得结果

  • 将Future视为保存结果的对象, 它暂时不保存结果, 但将来会保存 (即Callable返回的时候), 为了获得结果,得需要Callable

  • 类的关系: class FutureTask<V> implements RunnableFuture<V>
    interface RunnableFuture<V> extends Runnable, Future<V>

相关API:

CompletableFuture:

是Future的实现类, 用作编排多个线程间的关系, 有异步回调功能

八. Fork/Join:

Fork:把一个复杂任务进行拆分, 递归拆分

Join:把拆分任务的结果进行合并

案例: 计算 1+2+3…+1000, 拆分成子任务, 每个子任务做100个数字的累加

class TaskExample extends RecursiveTask<Long> {
    private int start;
    private int end;
    private long sum;

    public TaskExample(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute( ) {
        System.out.println("任务" + start + "=========" + end + "累加开始");
        //大于 100 个数相加切分,小于直接加
        if (end - start <= 100) {
            for (int i = start; i <= end; i++) {
                //累加
                sum += i;
            }
        } else {
            //切分为 2 块
            int middle = start + 100;
            //递归调用,切分为 2 个小任务
            TaskExample taskExample1 = new TaskExample(start, middle);
            TaskExample taskExample2 = new TaskExample(middle + 1, end);
            //执行:异步
            taskExample1.fork( );
            taskExample2.fork( );
            //同步阻塞获取执行结果
            sum = taskExample1.join( ) + taskExample2.join( );
        }
        //加完返回
        return sum;
    }
}

public class ForkJoinPoolDemo {

    public static void main(String[] args) {
        //定义任务
        TaskExample taskExample = new TaskExample(1, 1000);
        //定义执行对象
        ForkJoinPool forkJoinPool = new ForkJoinPool( );
        //加入任务执行
        ForkJoinTask<Long> result = forkJoinPool.submit(taskExample);
        //输出结果
        try {
            System.out.println(result.get( ));
        } catch (Exception e) {
            e.printStackTrace( );
        } finally {
            forkJoinPool.shutdown( );
        }
    }
}

九. 线程池:

为什么使用线程池:

  1. 复用Thread对象, 防止频繁地创建和销毁, 从而降低资源消耗 并提高响应速度

  2. 提高线程的可管理性, 控制其上限

七大参数:

  1. corePoolSize 核心线程数目
  2. maximumPoolSize 最大线程数目
  3. keepAliveTime 救急线程的生存时间
  4. unit 时间单位 救急线程的生存时间单位
  5. workQueue 等待队列
  6. threadFactory 线程工厂, 可以定制线程对象的创建如 设置线程名字、是否是守护线程等
  7. handler 拒绝策略
    1. 抛异常 AbortPolicy
    2. 返回给调用者去执行 CallerRunsPolicy
    3. 丢弃此任务 DiscardPolicy
    4. 丢弃最早排队的任务 DiscardOldestPolicy

救急线程优先处理新来的任务, 而不是呆在等待队列里的任务

创建线程池的几种方法:

  1. 使用Executors工厂方法创建

    这里的无限大是说为int最大值的意思

  2. 自己通过new ThreadPoolExecutor 方法传参创建

线程的销毁:

救急线程在空闲一定时间后自动销毁

核心线程可调用shutdown等方法销毁

  1. shutdown

    • 不接收新任务

    • 已提交任务会执行完

    • 不会阻塞调用此方法的线程

    • 线程池状态变为 SHUTDOWN

  2. shutdownNow

    • 不接收新任务

    • 已提交但还在队列里没开始执行的任务会被取消

    • 用interrupt打断正在执行的任务

    • 线程池状态变为 STOP

十. AQS常用的锁:

AQS: AbstractQueuedSynchronizer 抽象队列同步器

ReentrantLock, FutureTask, CountDownLatch, CyclicBarrier, Semaphore 都是基于AQS实现的

AQS可以独占, 也可以共享

AQS内部有一些CAS代码

AQS的原理:

AQS维护了一个共享资源, 然后使用双向队列来保证线程排队获取资源

用 volatile state 字段 来表示同步状态, 通过CAS进行setState

队列里只有头节点才是锁的持有者,尾指针指向队列的最后一个等待线程节点

在这里插入图片描述

AQS的资源共享方式有哪些?

通过修改state字段来实现

  • Exclusive:独占, 只有一个线程可以执行, 例如ReentrantLock
  • Share:共享, 多个线程可同时执行, 如ReentrantReadWriteLock

在这里插入图片描述

在这里插入图片描述

AQS使用了模板方法设计模式

自定义AQS:

  1. 继承AbstractQueuedSynchronizer

  2. 重写方法:

    1. isHeldExclusively( ):判断该线程是否正在独占资源

    2. tryAcquire(int):独占方式, 尝试获取资源

    3. tryRelease(int):独占方式, 尝试释放资源

    4. tryAcquireShared(int):共享方式, 尝试获取资源; 负数表示失败, 0表示无可用资源, 正数有可用资源

    5. tryReleaseShared(int):共享方式, 尝试释放资源

CountDownLatch(递减计数器):

当计数器的值变为0时,await方法阻塞的线程会被唤醒继续执行

案例: 6个同学陆续离开教室后值班同学才可以关门

public class CountDownLatchDemo {

    public static void main(String[] args) throws Exception {
        //定义一个数值为 6 的计数器
        CountDownLatch countDownLatch = new CountDownLatch(6);
        //创建 6 个同学(线程)
        for (int i = 1; i <= 6; i++) {
            new Thread(( ) -> {
                try {
                    if (Thread.currentThread( ).getName( ).equals("同学 6")) {
                        Thread.sleep(2000);
                    }
                    System.out.println(Thread.currentThread( ).getName( ) + "离开了");
                    //计数器减一,不会阻塞
                    countDownLatch.countDown( );
                } catch (Exception e) {
                    e.printStackTrace( );
                }
            }, "同学" + i).start( );
        }
        countDownLatch.await( ); // 当计数器为0时,才被唤醒
        System.out.println("全部离开了,现在的计数器为" + countDownLatch.getCount( ));
    }
}

CyclicBarrier(循环栅栏):

每执行cyclicBarrier.await( )一次, 障碍数就会加一, 如果达到了目标障碍数, 才会执行

构造器: public CyclicBarrier(int parties, Runnable runable)

案例: 经历81难才能取得真经

public class CyclicBarrierDemo {

    private final static int NUMBER = 81;

    public static void main(String[] args) throws Exception{
        //定义循环栅栏, 定义的run方法(lambda表达式), await( )81次才会被执行
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, ( ) -> System.out.println("集齐" + NUMBER + "颗龙珠,现在召唤神龙!!!!!!!!!"));
        //定义 7 个线程分别去收集龙珠
        for (int i = 1; i <= NUMBER; i++) {
                try {
                    System.out.println("经历第" + i+"难");
                    Thread.sleep(500L)
                    cyclicBarrier.await( );
                } catch (Exception e) {
                    e.printStackTrace( );
                }
        }
    }
}

Semaphore(信号灯):

定义最大许可证, 每个信号量最多只同时有一个许可证。

使用 acquire 方法获得许可证,release 方法释放许可证

案例: 6辆汽车(线程)抢3个停车位

public class SemaphoreDemo {

    public static void main(String[] args) throws Exception {
        //定义 3 个停车位
        Semaphore semaphore = new Semaphore(3);
        //模拟 6 辆汽车停车
        for (int i = 1; i <= 6; i++) {
            Thread.sleep(100);
            //停车
            new Thread(( ) -> {
                try {
                    System.out.println(Thread.currentThread( ).getName( ) + "找车位 ing");
                    semaphore.acquire( );
                    System.out.println(Thread.currentThread( ).getName( ) + "汽车停车成功!");
                    Thread.sleep(10000L); //停车中
                } catch (Exception e) {
                    e.printStackTrace( );
                } finally {
                    System.out.println(Thread.currentThread( ).getName( ) + "溜了溜了");
                    semaphore.release( );
                }
            }, "汽车" + i).start( );
        }
    }
}

邮戳锁StampedLock:

是对读写锁的优化, 乐观读, 类似于CAS, 戳即版本号

  1. 乐观读:

    long stamp = lock.tryOptimisticRead( );

    // 验戳

    if(!lock.validate(stamp)){

    // 锁升级

    }

  2. 读锁:

    long stamp = lock.readLock( );

    lock.unlockRead(stamp);

  3. 写锁:

    long stamp = lock.writeLock( );

    lock.unlockWrite(stamp);

十一. 大对比:

wait VS sleep:

wait VS park:

打断 sleep, wait, join, park 的线程:

synchronized VS volatile:

synchronized VS Lock:

Runable VS Thread:

Runable run( ) VS Callable call( ):

execute VS submit:

死锁 VS 活锁:

十二. 代码案例:

线程八锁问题:

  1.  import java.util.concurrent.TimeUnit;
     
     //先byPlane, 后byCar 其实同时锁住了两个方法, 但没有时间暂停, 看不出来
     public class Sync_1 {
     
         public static void main(String[] args) throws InterruptedException {
             Vehicle_A vehicle1 = new Vehicle_A( );
     
             new Thread(( ) -> vehicle1.byPlane( )).start( );
             TimeUnit.MILLISECONDS.sleep(500L);  //只是为了排除线程先后启动的误差, 使认为线程1比线程2先启动
             new Thread(( ) -> vehicle1.byCar( )).start( );
         }
     
     }
     
     class Vehicle_A {
     
         public synchronized void byPlane( ) {
             System.out.println("------byPlane------");
         }
     
         public synchronized void byCar( ) {
             System.out.println("------byCar------");
         }
     
     }
    
  2.  import java.util.concurrent.TimeUnit;
     
     //3s后 先byPlane, 后byCar     说明同时锁住了两个方法
     public class Sync_2 {
     
         public static void main(String[] args) throws InterruptedException {
             Vehicle_B vehicle1 = new Vehicle_B( );
     
             new Thread(( ) -> vehicle1.byPlane( )).start( );
             TimeUnit.MILLISECONDS.sleep(500L);  //只是为了排除线程先后启动的误差
             new Thread(( ) -> vehicle1.byCar( )).start( );
         }
     
     }
     
     class Vehicle_B {
     
         public synchronized void byPlane( ) {
             try {
                 TimeUnit.SECONDS.sleep(3);
             } catch (InterruptedException ignored) {
             }
             System.out.println("------byPlane------");
         }
     
         public synchronized void byCar( ) {
             System.out.println("------byCar------");
         }
     
     }
    
  3.  import java.util.concurrent.TimeUnit;
     
     //先hello, 3s后再byPlane     说明没有同时锁住两个方法
     public class Sync_3 {
     
         public static void main(String[] args) throws InterruptedException {
             Vehicle_C vehicle1 = new Vehicle_C( );
     
             new Thread(( ) -> vehicle1.byPlane( )).start( );
             TimeUnit.MILLISECONDS.sleep(500L);  //只是为了排除线程先后启动的误差
             new Thread(( ) -> vehicle1.hello( )).start( );
         }
     
     }
     
     class Vehicle_C {
     
         public synchronized void byPlane( ) {
             try {
                 TimeUnit.SECONDS.sleep(3);
             } catch (InterruptedException ignored) {
             }
             System.out.println("------byPlane------");
         }
     
         public synchronized void byCar( ) {
             System.out.println("------byCar------");
         }
     
         public void hello( ) {
             System.out.println("------hello------");
         }
     
     }
    
  4.  import java.util.concurrent.TimeUnit;
     
     //先byCar, 3s后再byPlane     说明没有同时锁住两个方法
     public class Sync_4 {
     
         public static void main(String[] args) throws InterruptedException {
             Vehicle_D vehicle1 = new Vehicle_D( );
             Vehicle_D vehicle2 = new Vehicle_D( );
     
             new Thread(( ) -> vehicle1.byPlane( )).start( );
             TimeUnit.MILLISECONDS.sleep(500L);  //只是为了排除线程先后启动的误差
             new Thread(( ) -> vehicle2.byCar( )).start( );
         }
     
     }
     
     class Vehicle_D {
     
         public synchronized void byPlane( ) {
             try {
                 TimeUnit.SECONDS.sleep(3);
             } catch (InterruptedException ignored) {
             }
             System.out.println("------byPlane------");
         }
     
         public synchronized void byCar( ) {
             System.out.println("------byCar------");
         }
     
     }
    
  5.  import java.util.concurrent.TimeUnit;
     
     //3s后 先byPlane, 后byCar     说明同时锁住了两个方法
     public class Sync_5 {
     
         public static void main(String[] args) throws InterruptedException {
             Vehicle_E vehicle1 = new Vehicle_E( );
     
             new Thread(( ) -> vehicle1.byPlane( )).start( );
             TimeUnit.MILLISECONDS.sleep(500L);  //只是为了排除线程先后启动的误差
             new Thread(( ) -> vehicle1.byCar( )).start( );
         }
     
     }
     
     class Vehicle_E {
     
         public synchronized static void byPlane( ) {
             try {
                 TimeUnit.SECONDS.sleep(3);
             } catch (InterruptedException ignored) {
             }
             System.out.println("------byPlane------");
         }
     
         public synchronized static void byCar( ) {
             System.out.println("------byCar------");
         }
     
     }
    
  6.  import java.util.concurrent.TimeUnit;
     
     //3s后 先byPlane, 后byCar     说明同时锁住了两个方法
     public class Sync_6 {
     
         public static void main(String[] args) throws InterruptedException {
             Vehicle_F vehicle1 = new Vehicle_F( );
             Vehicle_F vehicle2 = new Vehicle_F( );
     
             new Thread(( ) -> vehicle1.byPlane( )).start( );
             TimeUnit.MILLISECONDS.sleep(500L);  //只是为了排除线程先后启动的误差
             new Thread(( ) -> vehicle2.byCar( )).start( );
         }
     
     }
     
     class Vehicle_F {
     
         public synchronized static void byPlane( ) {
             try {
                 TimeUnit.SECONDS.sleep(3);
             } catch (InterruptedException ignored) {
             }
             System.out.println("------byPlane------");
         }
     
         public synchronized static void byCar( ) {
             System.out.println("------byCar------");
         }
     
     }
    
  7.  import java.util.concurrent.TimeUnit;
     
     //先byCar, 3s后再byPlane     说明没有同时锁住两个方法
     public class Sync_7 {
     
         public static void main(String[] args) throws InterruptedException {
             Vehicle_G vehicle1 = new Vehicle_G( );
     
             new Thread(( ) -> vehicle1.byPlane( )).start( );
             TimeUnit.MILLISECONDS.sleep(500L);  //只是为了排除线程先后启动的误差
             new Thread(( ) -> vehicle1.byCar( )).start( );
         }
     
     }
     
     class Vehicle_G {
     
         public synchronized static void byPlane( ) {
             try {
                 TimeUnit.SECONDS.sleep(3);
             } catch (InterruptedException ignored) {
             }
             System.out.println("------byPlane------");
         }
     
         public static void byCar( ) {
             System.out.println("------byCar------");
         }
     
     }
    
  8.  import java.util.concurrent.TimeUnit;//先byCar, 3s后再byPlane    说明没有同时锁住两个方法public class Sync_8 {    public static void main(String[] args) throws InterruptedException {        Vehicle_H vehicle1 = new Vehicle_H( );        Vehicle_H vehicle2 = new Vehicle_H( );        new Thread(( ) -> vehicle1.byPlane( )).start( );        TimeUnit.MILLISECONDS.sleep(500L);  //只是为了排除线程先后启动的误差        new Thread(( ) -> vehicle2.byCar( )).start( );    }}class Vehicle_H {    public synchronized static void byPlane( ) {        try {            TimeUnit.SECONDS.sleep(3);        } catch (InterruptedException ignored) {        }        System.out.println("------byPlane------");    }    public synchronized void byCar( ) {        System.out.println("------byCar------");    }}
    

总结:

  1. 锁非静态方法为锁this,锁静态方法为锁对应的类.class

  2. 锁住静态时不影响非静态方法, 但影响其他的静态

  3. 锁住非静态时不影响静态, 但影响其他的非静态

多个线程交替打印问题:

  1. 三个线程T1、T2、T3交替打印A、B、C, 打印10次, 如ABCABCABCABC…

  2. 三个线程T1、T2、T3交替打印A(5次)、B(10次)、C(15次), 打印10次, 如AAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCC…

  3. 三个线程交替打印1-100, T1打印1, T2打印2, T3打印3, T1打印4…

  4. 两个线程交替打印字母和数字, T1打印A1, T2打印B2, T1打印C3…一直到Z26, 如A1B2C3…

synchronized & wait & notify版解法:

  1.   public class Demo01 {
      
          private static Object lock = new Object( );
          private static int num;
      
          public static void printABC(String show, int targetNum) throws InterruptedException {
              for (int i = 0; i < 10; i++) {
                  synchronized (lock) {
                      while (num % 3 != targetNum) {
                          lock.wait( );
                      }
                      num++;
                      System.out.print(show);
                      lock.notifyAll( );
                  }
              }
          }
      }
      
      class Test01 {
      
          public static void main(String[] args) {
              new Thread(( ) -> {
                  try {
                      Demo01.printABC("A", 0);
                  } catch (InterruptedException e) {
                      e.printStackTrace( );
                  }
              }).start( );
              new Thread(( ) -> {
                  try {
                      Demo01.printABC("B", 1);
                  } catch (InterruptedException e) {
                      e.printStackTrace( );
                  }
              }).start( );
              new Thread(( ) -> {
                  try {
                      Demo01.printABC("C", 2);
                  } catch (InterruptedException e) {
                      e.printStackTrace( );
                  }
              }).start( );
          }
      }
    
  2.  public class Demo02 {
     
     
         private static Object lock = new Object( );
         private static int num;
     
         public static void printABC(String show, int targetNum) throws InterruptedException {
             for (int i = 0; i < 10; i++) {
                 synchronized (lock) {
                     while (num % 3 != targetNum) {
                         lock.wait( );
                     }
                     num++;
                     for (int j = 0; j < 5 * (targetNum + 1); j++) {
                         System.out.print(show);
                     }
                     lock.notifyAll( );
                 }
             }
         }
     }
     
     class Test02 {
     
         public static void main(String[] args) {
             new Thread(( ) -> {
                 try {
                     Demo02.printABC("A", 0);
                 } catch (InterruptedException e) {
                     e.printStackTrace( );
                 }
             }).start( );
             new Thread(( ) -> {
                 try {
                     Demo02.printABC("B", 1);
                 } catch (InterruptedException e) {
                     e.printStackTrace( );
                 }
             }).start( );
             new Thread(( ) -> {
                 try {
                     Demo02.printABC("C", 2);
                 } catch (InterruptedException e) {
                     e.printStackTrace( );
                 }
             }).start( );
         }
     }
    
  3.  public class Demo03 {
     
         private static Object lock = new Object( );
         private static int num;
     
         public static void print_1_100(String threadName, int targetNum) throws InterruptedException {
             // num [0,99] num++后再打印num
             while (true) {
                 synchronized (lock) {
                     while (num % 3 != targetNum) {
                         lock.wait( );
                     }
                     num++;
                     if (num > 100) {
                         // 不叫醒其他线程的话,会导致其他线程都在wait  程序不会结束
                         lock.notifyAll( );
                         break;
                     }
                     System.out.println(threadName + ":" + num);
                     lock.notifyAll( );
                 }
             }
         }
     }
     
     class Test03 {
     
         public static void main(String[] args) {
             new Thread(( ) -> {
                 try {
                     Demo03.print_1_100("T1", 0);
                 } catch (InterruptedException e) {
                     e.printStackTrace( );
                 }
             }).start( );
             new Thread(( ) -> {
                 try {
                     Demo03.print_1_100("T2", 1);
                 } catch (InterruptedException e) {
                     e.printStackTrace( );
                 }
             }).start( );
             new Thread(( ) -> {
                 try {
                     Demo03.print_1_100("T3", 2);
                 } catch (InterruptedException e) {
                     e.printStackTrace( );
                 }
             }).start( );
         }
     }
    
  4.  public class Demo04 {
          public static Object lock = new Object( );
          public static int num;
      
          public static void print(int order) throws InterruptedException {
              while (true) {
                  synchronized (lock) {
                      while (num % 2 != order) {
                          lock.wait( );
                      }
                      num++;
                      if (num > 26) {
                          lock.notifyAll( );
                          break;
                      }
                      System.out.print((char) (num - 1 + 'A'));
                      System.out.print(num);
                      lock.notifyAll( );
                  }
              }
          }
      }
      
      class Test04 {
      
          public static void main(String[] args) {
              new Thread(( ) -> {
                  try {
                      Demo04.print(0);
                  } catch (InterruptedException e) {
                      e.printStackTrace( );
                  }
              }).start( );
              new Thread(( ) -> {
                  try {
                      Demo04.print(1);
                  } catch (InterruptedException e) {
                      e.printStackTrace( );
                  }
              }).start( );
          }
      }
        
    

Lock & Condition版解法:

  1.  public class Demo01 {
     
         private static int num;
     
         public static void printABC(String show, int targetNum, Lock lock, Condition cur, Condition next) throws InterruptedException {
             for (int i = 0; i < 10; i++) {
                 lock.lock( );
                 try {
                     while (num % 3 != targetNum) {
                         cur.await( );
                     }
                     num++;
                     System.out.print(show);
                     next.signal( );
                 } finally {
                     lock.unlock( );
                 }
             }
         }
     }
     
     class Test01 {
         private static ReentrantLock lock = new ReentrantLock( );
         private static Condition condition1 = lock.newCondition( );
         private static Condition condition2 = lock.newCondition( );
         private static Condition condition3 = lock.newCondition( );
     
         public static void main(String[] args) {
             new Thread(( ) -> {
                 try {
                     Demo01.printABC("A", 0, lock, condition1, condition2);
                 } catch (InterruptedException e) {
                     e.printStackTrace( );
                 }
             }).start( );
             new Thread(( ) -> {
                 try {
                     Demo01.printABC("B", 1, lock, condition2, condition3);
                 } catch (InterruptedException e) {
                     e.printStackTrace( );
                 }
             }).start( );
             new Thread(( ) -> {
                 try {
                     Demo01.printABC("C", 2, lock, condition3, condition1);
                 } catch (InterruptedException e) {
                     e.printStackTrace( );
                 }
             }).start( );
         }
     }
    
  2.  public class Demo02 {
     
         private static int num;
     
         public static void printABC(String show, int targetNum, Lock lock, Condition cur, Condition next) throws InterruptedException {
             for (int i = 0; i < 10; i++) {
                 lock.lock( );
                 try {
                     while (num % 3 != targetNum) {
                         cur.await( );
                     }
                     num++;
                     for (int j = 0; j < 5 * (targetNum + 1); j++) {
                         System.out.print(show);
                     }
                     next.signal( );
                 } finally {
                     lock.unlock( );
                 }
             }
         }
     }
     
     class Test02 {
         private static ReentrantLock lock = new ReentrantLock( );
         private static Condition condition1 = lock.newCondition( );
         private static Condition condition2 = lock.newCondition( );
         private static Condition condition3 = lock.newCondition( );
     
         public static void main(String[] args) {
             new Thread(( ) -> {
                 try {
                     Demo02.printABC("A", 0, lock, condition1, condition2);
                 } catch (InterruptedException e) {
                     e.printStackTrace( );
                 }
             }).start( );
             new Thread(( ) -> {
                 try {
                     Demo02.printABC("B", 1, lock, condition2, condition3);
                 } catch (InterruptedException e) {
                     e.printStackTrace( );
                 }
             }).start( );
             new Thread(( ) -> {
                 try {
                     Demo02.printABC("C", 2, lock, condition3, condition1);
                 } catch (InterruptedException e) {
                     e.printStackTrace( );
                 }
             }).start( );
         }
     }
    
  3.  public class Demo03 {
     
         private static int num;
     
         public static void print_1_100(String threadName, int targetNum, Lock lock, Condition cur, Condition next) throws InterruptedException {
             // num [0,99] num++后再打印num
             while (true) {
                 lock.lock( );
                 try {
                     while (num % 3 != targetNum) {
                         cur.await( );
                     }
                     num++;
                     if (num > 100) {
                         // 不叫醒其他线程的话,会导致其他线程都在wait  程序不会结束
                         next.signal( );
                         break;
                     }
                     System.out.println(threadName + ":" + num);
                     next.signal( );
                 } finally {
                     lock.unlock( );
                 }
             }
         }
     }
     
     class Test03 {
         private static ReentrantLock lock = new ReentrantLock( );
         private static Condition condition1 = lock.newCondition( );
         private static Condition condition2 = lock.newCondition( );
         private static Condition condition3 = lock.newCondition( );
     
         public static void main(String[] args) {
             new Thread(( ) -> {
                 try {
                     Demo03.print_1_100("A", 0, lock, condition1, condition2);
                 } catch (InterruptedException e) {
                     e.printStackTrace( );
                 }
             }).start( );
             new Thread(( ) -> {
                 try {
                     Demo03.print_1_100("B", 1, lock, condition2, condition3);
                 } catch (InterruptedException e) {
                     e.printStackTrace( );
                 }
             }).start( );
             new Thread(( ) -> {
                 try {
                     Demo03.print_1_100("C", 2, lock, condition3, condition1);
                 } catch (InterruptedException e) {
                     e.printStackTrace( );
                 }
             }).start( );
         }
     }
    
  4.  public class Demo04 {
          public static int num;
      
          public static void print(int order, Lock lock, Condition cur, Condition next) throws InterruptedException {
              while (true) {
                  lock.lock( );
                  try {
                      while (num % 2 != order) {
                          cur.await( );
                      }
                      num++;
                      if (num > 26) {
                          next.signal( );
                          break;
                      }
                      System.out.print((char) (num - 1 + 'A'));
                      System.out.print(num);
                      next.signal( );
                  } finally {
                      lock.unlock( );
                  }
              }
          }
      }
      
      class Test04 {
          private static ReentrantLock lock = new ReentrantLock( );
          private static Condition condition1 = lock.newCondition( );
          private static Condition condition2 = lock.newCondition( );
      
          public static void main(String[] args) {
              new Thread(( ) -> {
                  try {
                      Demo04.print(0, lock, condition1, condition2);
                  } catch (InterruptedException e) {
                      e.printStackTrace( );
                  }
              }).start( );
              new Thread(( ) -> {
                  try {
                      Demo04.print(1, lock, condition2, condition1);
                  } catch (InterruptedException e) {
                      e.printStackTrace( );
                  }
              }).start( );
          }
      }  
    
举报

相关推荐

0 条评论