多线程学习笔记(二)

线程协作模式

同时开始


public class FireFlag {

    private volatile boolean fire = false;

    public synchronized void waitForFire() throws InterruptedException {
        while (!fire) {
            wait();
        }
    }

    public synchronized void fire() {
        this.fire = true;
        notifyAll();
    }

    public static void main(String[] args) throws InterruptedException {
        int num=10;
        FireFlag fireFlag = new FireFlag();
        Thread[] recers =new Thread[num];
        for (int i = 0; i < num; i++) {
            recers[i] = new Racer(fireFlag);
            recers[i].start();
        }
        Thread.sleep(1_000);
        fireFlag.fire();
    }

    static class Racer extends Thread {
       FireFlag fireFlag;

        public Racer(FireFlag fireFlag) {
            this.fireFlag = fireFlag;
        }

        @Override
        public void run() {
            try {
                this.fireFlag.waitForFire();
                System.out.println("start run "+Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}



start run Thread-9
start run Thread-8
start run Thread-7
start run Thread-6
start run Thread-5
start run Thread-3
start run Thread-4
start run Thread-2
start run Thread-1
start run Thread-0

同时结束

同步协作工具类

public class MyLatch {
    private int count;

    public MyLatch(int count) {
        this.count = count;
    }

    public synchronized void await() throws InterruptedException {
        while (count > 0) {
            wait();
        }

    }

    public synchronized void countDown() {
        count--;
        if (count <= 0) {
            notifyAll();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int workerNum=100;
        MyLatch latch = new MyLatch(workerNum);
        Worker[] workerNums = new Worker[workerNum];
        for (int i = 0; i < workerNum; i++) {
            workerNums[i] = new Worker(latch);
            workerNums[i].start();
        }
        latch.await();
    }
    static class Worker extends Thread{
        MyLatch myLatch;

        public Worker(MyLatch myLatch) {
            this.myLatch = myLatch;
        }

        @Override
        public void run() {
            try {
                Thread.sleep((int)(Math.random()*1000));
                this.myLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Java专门的类:CountDownLatch

集合点


public class AssemblePoint {
    private int n;

    public AssemblePoint(int n) {
        this.n = n;
    }

    public synchronized void await() throws InterruptedException {
        if (n > 0) {
            n--;
            if (n == 0) {
                notifyAll();
            } else {
                while (n != 0) {
                    wait();
                }
            }
        }
    }


    static class AssemblePointDemo {
        static class Tourist extends Thread {
            AssemblePoint ap;

            public Tourist(AssemblePoint ap) {
                this.ap = ap;
            }

            @Override
            public void run() {
                try {
                    // 先模拟各自独立运行
                    Thread.sleep((int)(Math.random()*1_000));
                    //集合
                    ap.await();
                    System.out.println(Thread.currentThread().getName()+"已到达");
                    // 集合之后的其他操作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }


        public static void main(String[] args) {
            int num=10;
            Tourist[] threads = new Tourist[num];
            AssemblePoint ap = new AssemblePoint(num);
            for (int i = 0; i < num; i++) {
                threads[i] = new Tourist(ap);
                threads[i].start();
            }
        }
    }
}

Thread-5已到达
Thread-1已到达
Thread-3已到达
Thread-6已到达
Thread-9已到达
Thread-8已到达
Thread-0已到达
Thread-2已到达
Thread-4已到达
Thread-7已到达

Java中有一个类似的专门的同步工具类-CyclicBarrier.

线程中断

中断场景:
1-生产者消费者协作场景中,消费者就是一个无限循环,有问题时需要优雅关闭
2-用户下载任务时候,用户会随时取消
3-限时的第三方服务,时间到查不到,取消该任务
4-抢票,

打断相关方法

    public boolean isInterrupted() {
        return isInterrupted(false);
    }

// 2-
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
// 3-还有副作用.会清空标志位
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }


注意:在使用synchronized关键字获取锁的过程中不响应中断请求,这是synchronized的局限性。应该使用显式锁

原子变量

  • AtomicBoolean,在程序中表示一个标志位
  • AtomicInteger
  • AtomicLong,常用来在程序中生成唯一序列号
  • AtomicReference 原子引用类型,以原子方式更新复杂类型。

介绍:AtomicInteger
关键方法:

    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

例子:

// add 1并且返回最新值
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

在这里插入图片描述
总结:
1-synchronized是:悲观的,阻塞式的,存在上下文切换
2-CAS(CompareAndSet):乐观的,非阻塞的

实际上JUC下所有的阻塞式工具,容器,算法都是基于CAS的

演示例子

import java.util.concurrent.atomic.AtomicInteger;

/**仅仅用于演示,实际开发中使用 ReentrantLock
 * 使用AtomicInteger实现锁
 * */
public class MyLock {
    private AtomicInteger status = new AtomicInteger(0);

    public void lock(){
        while (!status.compareAndSet(0,1)){
            Thread.yield();
        }
    }
    public void unlock(){
        // 0-未锁定
        status.compareAndSet(1,0);
    }
}

ABA问题

问题描述:当前值为A,一个线程将A修改为B,在修改为A,当前线程的CAS操作无法分辨当前值是否发生变化。
解决方式:AtomicStampedReference。在修改值的同时给一个时间戳,只有值和时间戳都相同才进行修改。

显式锁

在这里插入图片描述
相比synchronized显式锁,支持非阻塞方式获取锁,可响应中断,可限时。

tryLock
使用tryLock,可以避免死锁。在持有一个锁的时候获取另一个锁获取不到的时候,释放自己的锁,然后重新获取所有锁。
案例


/**
 * 演示tryLock()
 */
public class Account {
    private Lock lock = new ReentrantLock();
    private volatile double money;

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

    public double getMoney() {
        return money;
    }

    void lock() {
        lock.lock();
    }

    void unlock() {
        lock.unlock();
    }

    boolean tryLock() {
        return lock.tryLock();
    }

    public void reduce(double money) {
        lock.lock();
        try {
            this.money -= money;
        } finally {
            lock.unlock();
        }
    }

    public void add(double money) {
        lock.lock();
        try {
            this.money += money;
        } finally {
            lock.unlock();
        }
    }

    static class AccountMgr {
        // 不应该这样
        public static void transfer(Account from, Account to, double money) throws Exception {
            from.lock;
            try {
                to.lock;
                try {
                    if (from.getMoney() >= money) {
                        from.reduce(money);
                        to.add(money);
                    } else {
                        throw new Exception(from + "没有足够的钱");
                    }
                } finally {
                    to.unlock();
                }
            } finally {
                from.unlock();
            }
        }

        // 应该这样
        public static boolean tryTransfer(Account from, Account to, double money) throws Exception {
            if (from.tryLock()) {
                try {
                    if (to.tryLock()) {
                        try {
                            if (from.getMoney() >= money) {
                                from.reduce(money);
                                to.add(money);
                            } else {
                                throw new Exception(from + "没有足够的钱");
                            }
                            return true;
                        } finally {
                            to.unlock();
                        }

                    }
                } finally {
                    from.unlock();
                }
            }

        }
    }

}

如果两个锁都能获得,且转账成功,则返回true,否则返回false。不管怎么样,结束都会释放所有的锁。

实现原理
ReentrantLock的用法比较简单,在底层是:CAS+LockSupport类的一些方法。
LockSupport类主要方法是 parkXXX
方法作用:让出CPU调度,状态变为Waiting,让别的线程搞。而Thread.yield(),只是告诉OS可以让其他线程运行,但自己依然是可运行状态。

总结:park和CAS方法一样,都是调用Unsafe类的对应方法,最终调用的就是OS的API。从java程序员角度,看是不需要关系的。

AQS

AQS:AbstractQueuedSynchronizer。并发工具。
ReentrantLock:默认获取的是非公平锁,底层通过CAS+LockSupport的peak方法
获得锁的大概流程如下图。
在这里插入图片描述

总结:能获得锁就立即返回。否则加入等待队列,被唤醒后检查自己是否是一个等待的线程,如果是且能获得锁,则返回,否则继续等待。这个过程弱国发生了中断,lock会记录中断标志位,但不会提前返回或者抛出异常。

ReentrantLock与synchronized区别

ReentrantLock支持非阻塞方式获取锁,可以相应中断,可以限时,更加灵活。synchronized更加简单,写的代码少,不容易出错。什么都是相对的。
synchronized代表一种声明式编程思维,程序员更多的表达一种同步声明,由java系统负责实现,程序员不知道其细节。显式锁代表一种命令式编程思维,程序员去实现所有细节。
随着新版jvm,ReentrantLock与synchronized的性能是接近的。
简单总结:能用synchronized的时候,用synchronized。

显式锁的显式条件

首先看 java.util.concurrent.locks.Lock#newCondition 这个方法
方法类型是:java.util.concurrent.locks.Condition
学习这个和Object的 wait以及notify对比学习

显式条件—await、signal。等待条件更加灵活
Object-wait,notify(对复杂条件有局限性)

切记:显式条件与显式锁配合,wait、synchronized与synchronized配合。千万不要混用,否则将抛出:IllegalMonitorStateException异常

在看生产者消费者模式

我们可以使用 synchronized+阻塞队列演示此模式。当时提到wait以及notify的一个局限性,它只能有一个条件等待队列,分析等待条件也复杂。在此模式中,有两个条件,一个与队列满有关,一个与队列空有关。
使用显式锁,可以创建多个条件等待队列。


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

public class MyBlockingQueue2<E> {
    private Queue<E> queue = null;
    private int limit;
    private Lock lock = new ReentrantLock();

    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    public MyBlockingQueue2(int limit) {
        this.limit = limit;
        queue = new ArrayDeque<>(limit);
    }

    public E take() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            while (queue.isEmpty()) {
                notEmpty.await();
            }
            E e = queue.poll();
            notFull.signal();
            return e;
        } finally {
            lock.unlock();
        }
    }


    public void put(E e) throws InterruptedException {
        lock.lockInterruptibly();
        try {
            while (queue.size() == limit) {
                notFull.await();
            }
            queue.add(e);
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }
}

重申:不要将Codition中的等待和唤醒方法,与Object中混用。
优点:使用两个等待队列,更加清晰。和使用synchronized相比。

实现原理
看内部类
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject
await方法代码

 public final void await() throws InterruptedException {
        // 如果等待前中断标志位已经被设置,直接抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
        // 为当前线程创建节点,加入条件等待队列
        AbstractQueuedSynchronizer.Node node = addConditionWaiter();
        // 2-释放持有的锁
        int savedState = fullyRelease(node);
        int interruptMode = 0;
        // 3- 放弃cpu,进行等待,知道被中断或者 isOnSyncQueue为true
        // isOnSyncQueue为true 说明:表示该节点被其他线程从条件等待队列移到了外部锁的等待队列,等待条件满足
        while (!isOnSyncQueue(node)) {
            LockSupport.park(this);
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
        }
        // 4-重新获取锁
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        if (node.nextWaiter != null) // clean up if cancelled
            unlinkCancelledWaiters();
        // 5-处理中断,抛出异常或者设置中断标志位
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
    }

singal唤醒方法

    public final void signal() {
        // 验证当前线程持有锁
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        AbstractQueuedSynchronizer.Node first = firstWaiter;
        //调用doSignal唤醒等待队列中的第一个线程
        if (first != null)
            doSignal(first);
    }
// doSignal方法
// 1-将节点从条件等待队列移到锁等待队列
// 2-调用 LockSupport.unpark将线程唤醒
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值