JUC面试(六)——Java锁

news2024/11/17 2:31:21

Java锁

公平锁和非公平锁

概念

公平锁

是指多个线程按照申请锁的顺序来获取锁,类似于排队买饭,先来后到,先来先服务,就是公平的,也就是队列

非公平锁

是指多个线程获取锁的顺序,并不是按照申请锁的顺序,有可能申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转,或者饥饿的线程(也就是某个线程一直得不到锁)

如何创建

并发包中ReentrantLock的创建可以指定析构函数的boolean类型来得到公平锁或者非公平锁,默认是非公平锁

/**
* 创建一个可重入锁,true 表示公平锁,false 表示非公平锁。无参默认非公平锁
*/
Lock lock = new ReentrantLock(true);

两者区别

公平锁:就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列中的第一个,就占用锁,否者就会加入到等待队列中,以后安装FIFO的规则从队列中取到自己

非公平锁: 非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。

题外话

Java ReenttrantLock通过构造函数指定该锁是否公平,默认是非公平锁,因为非公平锁的优点在于吞吐量比公平锁大,对于synchronized而言,也是一种非公平锁

可重入锁(递归锁)

ReentrantLock

概念

可重入锁就是递归锁

指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取到该锁的代码,在同一线程在外层方法获取锁的时候,在进入内层方法会自动获取锁

也就是说:线程可以进入任何一个它已经拥有的锁所同步的代码块

ReentrantLock / Synchronized 就是一个典型的可重入锁

代码

可重入锁就是,在一个method1方法中加入一把锁,方法2也加锁了,method1调用method2,那么他们拥有的是同一把锁

public synchronized void method1() {
	method2();
}

public synchronized void method2() {

}

也就是说我们只需要进入method1后,那么它也能直接进入method2方法,因为他们所拥有的锁,是同一把。method1可以拿到method2所有的代码块。

作用

可重入锁的最大作用就是避免死锁

可重入锁验证

证明Synchronized

/**
 * 可重入锁(也叫递归锁)
 * 指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取到该锁的代码,在同一线程在外层方法获取锁的时候,在进入内层方法会自动获取锁
 *
 * 也就是说:`线程可以进入任何一个它已经拥有的锁所同步的代码块`
 * @author: wzq
 * @create: 2020-03-15-12:12
 */

/**
 * 资源类
 */
class Phone {

    /**
     * 发送短信
     * @throws Exception
     */
    public synchronized void sendSMS() throws Exception{
        System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");

        // 在同步方法中,调用另外一个同步方法
        // 锁住同步的代码块,同一把锁
        sendEmail();
    }

    /**
     * 发邮件
     * @throws Exception
     */
    public synchronized void sendEmail() throws Exception{
        System.out.println(Thread.currentThread().getName() + "\t invoked sendEmail()");
    }
}
public class ReenterLockDemo {


    public static void main(String[] args) {
        Phone phone = new Phone();

        // 两个线程操作资源列
        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "t1").start();

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "t2").start();
    }
}

在这里,我们编写了一个资源类phone,拥有两个加了synchronized的同步方法,分别是sendSMS 和 sendEmail,我们在sendSMS方法中,调用sendEmail。最后在主线程同时开启了两个线程进行测试,最后得到的结果为:

在这里插入图片描述

这就说明当 t1 线程进入sendSMS的时候,拥有了一把锁,同时t2线程无法进入,直到t1线程拿着锁,执行了sendEmail 方法后,才释放锁,这样t2才能够进入

t1	 invoked sendSMS()      t1线程在外层方法获取锁的时候
t1	 invoked sendEmail()    t1在进入内层方法会自动获取锁

t2	 invoked sendSMS()      t2线程在外层方法获取锁的时候
t2	 invoked sendEmail()    t2在进入内层方法会自动获取锁

method2不加synchronized结果也一样。

父子类继承关系

关键字synchronized不能被继承

1、子类继承父类时,如果没有重写父类中的同步方法,子类同一对象,在不同线程并发调用该方法时,具有同步效果。

2、子类继承父类,并且重写父类中的同步方法,但没有添加关键字synchronized,子类同一对象,在不同线程并发调用该方法时,不再具有同步效果。

当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的

验证如下:

public class Widget {
    public synchronized void doSomething() {
        System.out.println("Widget中的this: " + this);
    }
}
 
public class LoggingWidget extends Widget {
    @Override
    public synchronized void doSomething() {
        super.doSomething();
        System.out.println("LoggingWidget中的super: " + super.toString());
        System.out.println("LoggingWidget中的this: " + this);
    }
 
    public static void main(String[] args) {
        LoggingWidget loggingWidget = new LoggingWidget();
        loggingWidget.doSomething();
    }
}

结果:

Widget中的this: com.wzq.basics.lock.LoggingWidget@1540e19d
LoggingWidget中的super: com.wzq.basics.lock.LoggingWidget@1540e19d
LoggingWidget中的this: com.wzq.basics.lock.LoggingWidget@1540e19d

不仅子类可重入父类同步方法,而且子类的super和this是同一引用,都是子类对象,锁住的是子类对象。也就是super本身仍然是子类的引用,只不过它可以调用到父类的方法或变量。

证明ReentrantLock

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

/**
 * 资源类
 */
class Phone implements Runnable{

    Lock lock = new ReentrantLock();

    /**
     * set进去的时候,就加锁,调用set方法的时候,能否访问另外一个加锁的set方法
     */
    public void getLock() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t get Lock");
            setLock();
        } finally {
            lock.unlock();
        }
    }

    public void setLock() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t set Lock");
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void run() {
        getLock();
    }
}
public class ReenterLockDemo {


    public static void main(String[] args) {
        Phone phone = new Phone();

        /**
         * 因为Phone实现了Runnable接口
         */
        Thread t3 = new Thread(phone, "t3");
        Thread t4 = new Thread(phone, "t4");
        t3.start();
        t4.start();
    }
}

现在我们使用ReentrantLock进行验证,首先资源类实现了Runnable接口,重写Run方法,里面调用get方法,get方法在进入的时候,就加了锁

然后在方法里面,又调用另外一个加了锁的setLock方法

最后输出结果我们能发现,结果和加synchronized方法是一致的,都是在外层的方法获取锁之后,线程能够直接进入里层,一旦get方法进去后,其它线程无法访问set方法。

在这里插入图片描述

当我们在getLock方法加两把锁会是什么情况呢? (阿里面试)

    public void getLock() {
        lock.lock();
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t get Lock");
            setLock();
        } finally {
            lock.unlock();
            lock.unlock();
        }
    }

最后得到的结果也是一样的,因为里面不管有几把锁,其它他们都是同一把锁,也就是说用同一个钥匙都能够打开

当我们在getLock方法加两把锁,但是只解一把锁会出现什么情况呢?

public void getLock() {
    lock.lock();
    lock.lock();
    try {
        System.out.println(Thread.currentThread().getName() + "\t get Lock");
        setLock();
    } finally {
        lock.unlock();
    }
}

得到结果

t3	 get Lock
t3	 set Lock

也就是说程序直接卡死,线程不能出来,也就说明我们申请几把锁,最后需要解除几把锁,不然死锁了,其它线程也访问不了。

当我们只加一把锁,但是用两把锁来解锁的时候,又会出现什么情况呢?

    public void getLock() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t get Lock");
            setLock();
        } finally {
            lock.unlock();
            lock.unlock();
        }
    }

这个时候,运行程序会直接报错

t3	 get Lock
t3	 set Lock
t4	 get Lock
t4	 set Lock
Exception in thread "t3" Exception in thread "t4" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
	at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
	at com.moxi.interview.study.thread.Phone.getLock(ReenterLockDemo.java:52)
	at com.moxi.interview.study.thread.Phone.run(ReenterLockDemo.java:67)
	at java.lang.Thread.run(Thread.java:745)
java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
	at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
	at com.moxi.interview.study.thread.Phone.getLock(ReenterLockDemo.java:52)
	at com.moxi.interview.study.thread.Phone.run(ReenterLockDemo.java:67)
	at java.lang.Thread.run(Thread.java:745)

自旋锁

自旋锁:spinlock,是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

原来提到的比较并交换,底层使用的就是自旋,自旋就是多次尝试,多次访问,不会阻塞的状态就是自旋。

在这里插入图片描述

优缺点

优点:循环比较获取直到成功为止,没有类似于wait的阻塞

缺点:当不断自旋的线程越来越多的时候,会因为执行while循环不断的消耗CPU资源

手写自旋锁

通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒,B随后进来发现当前有线程持有锁,不是null,所以只能通过自旋等待,直到A释放锁后B随后抢到

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 手写一个自旋锁
 *
 * 循环比较获取直到成功为止,没有类似于wait的阻塞
 *
 * 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒,B随后进来发现当前有线程持有锁,不是null,所以只能通过自旋等待,直到A释放锁后B随后抢到
 * @author: wzq
 * @create: 2020-03-15-15:46
 */
public class SpinLockDemo {

    // 现在的泛型装的是Thread,原子引用线程
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock() {
        // 获取当前进来的线程
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t come in ");

        // 开始自旋,期望值是null,更新值是当前线程,如果是null,则更新为当前线程,否者自旋
        while(!atomicReference.compareAndSet(null, thread)) {

        }
    }

    /**
     * 解锁
     */
    public void myUnLock() {

        // 获取当前进来的线程
        Thread thread = Thread.currentThread();

        // 自己用完了后,把atomicReference变成null
        atomicReference.compareAndSet(thread, null);

        System.out.println(Thread.currentThread().getName() + "\t invoked myUnlock()");
    }

    public static void main(String[] args) {

        SpinLockDemo spinLockDemo = new SpinLockDemo();

        // 启动t1线程,开始操作
        new Thread(() -> {

            // 开始占有锁
            spinLockDemo.myLock();


            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 开始释放锁
            spinLockDemo.myUnLock();

        }, "t1").start();


        // 让main线程暂停1秒,使得t1线程,先执行
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 1秒后,启动t2线程,开始占用这个锁
        new Thread(() -> {

            // 开始占有锁
            spinLockDemo.myLock();
            // 开始释放锁
            spinLockDemo.myUnLock();

        }, "t2").start();

    }
}

在这里插入图片描述

最后输出结果

t1	 come in 
.....一秒后.....
t2	 come in 
.....五秒后.....
t1	 invoked myUnlock()
t2	 invoked myUnlock()

首先输出的是 t1 come in

然后1秒后,t2线程启动,发现锁被t1占有,所有不断的执行 compareAndSet方法,来进行比较,直到t1释放锁后,也就是5秒后,t2成功获取到锁,然后释放

独占锁(写锁)& 共享锁(读锁)&互斥锁

概念

独占锁:指该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁

共享锁:指该锁可以被多个线程锁持有

对ReentrantReadWriteLock其读锁是共享,其写锁是独占

写的时候只能一个人写,但是读的时候,可以多个人同时读

为什么会有写锁和读锁

原来我们使用ReentrantLock创建锁的时候,是独占锁,也就是说一次只能一个线程访问,但是有一个读写分离场景,读的时候想同时进行,因此原来独占锁的并发性就没这么好了,因为读锁并不会造成数据不一致的问题,因此可以多个人共享读

多个线程 同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行,但是如果一个线程想去写共享资源,就不应该再有其它线程可以对该资源进行读或写

读-读:能共存

读-写:不能共存

写-写:不能共存

代码实现

实现一个读写缓存的操作,假设开始没有加锁的时候,会出现什么情况

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;


/**
 * 读写锁
 * 多个线程 同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行
 * 但是,如果一个线程想去写共享资源,就不应该再有其它线程可以对该资源进行读或写
 *
 * @author: wzq
 * @create: 2020-03-15-16:59
 */


/**
 * 资源类
 */
class MyCache {

    private volatile Map<String, Object> map = new HashMap<>();
    // private Lock lock = null;

    /**
     * 定义写操作
     * 满足:原子 + 独占
     * @param key
     * @param value
     */
    public void put(String key, Object value) {
        System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
        try {
            // 模拟网络拥堵,延迟0.3秒
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "\t 写入完成");
    }

    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "\t 正在读取:");
        try {
            // 模拟网络拥堵,延迟0.3秒
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Object value = map.get(key);
        System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value);
    }

}

public class ReadWriteLockDemo {

    public static void main(String[] args) {

        MyCache myCache = new MyCache();
        // 线程操作资源类,5个线程写
        for (int i = 0; i < 5; i++) {
            // lambda表达式内部必须是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.put(tempInt + "", tempInt +  "");
            }, String.valueOf(i)).start();
        }
        // 线程操作资源类, 5个线程读
        for (int i = 0; i < 5; i++) {
            // lambda表达式内部必须是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.get(tempInt + "");
            }, String.valueOf(i)).start();
        }
    }
}

最后运行结果:

0	 正在写入:0
4	 正在写入:4
3	 正在写入:3
1	 正在写入:1
2	 正在写入:2
0	 正在读取:
1	 正在读取:
2	 正在读取:
3	 正在读取:
4	 正在读取:
2	 写入完成
4	 写入完成
4	 读取完成:null
0	 写入完成
3	 读取完成:null
0	 读取完成:null
1	 写入完成
3	 写入完成
1	 读取完成:null
2	 读取完成:null

我们可以看到,在写入的时候,写操作都被其它线程打断了,这就造成了,还没写完,其它线程又开始写,这样就造成数据不一致。

解决方法

ReadWriteLock读写锁接口,实现类ReentrantReadWriteLock。

上面的代码是没有加锁的,这样就会造成线程在进行写入操作的时候,被其它线程频繁打断,从而不具备原子性,这个时候,我们就需要用到读写锁来解决了

/**
* 创建一个读写锁
* 它是一个读写融为一体的锁,在使用的时候,需要转换
*/
private ReadWriteLock rwLock = new ReentrantReadWriteLock();

当我们在进行写操作的时候,就需要转换成写锁

// 创建一个写锁
rwLock.writeLock().lock();

// 写锁 释放
rwLock.writeLock().unlock();

当们在进行读操作的时候,在转换成读锁

// 创建一个读锁
rwLock.readLock().lock();

// 读锁 释放
rwLock.readLock().unlock();

这里的读锁和写锁的区别在于,写锁一次只能一个线程进入,执行写操作,而读锁是多个线程能够同时进入,进行读取的操作

完整代码:

/**
 * 读写锁
 * 多个线程 同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行
 * 但是,如果一个线程想去写共享资源,就不应该再有其它线程可以对该资源进行读或写
 *
 * @author: wzq
 * @create: 2020-03-15-16:59
 */

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 资源类
 */
class MyCache {

    /**
     * 缓存中的东西,必须保持可见性,因此使用volatile修饰
     */
    private volatile Map<String, Object> map = new HashMap<>();

    /**
     * 创建一个读写锁
     * 它是一个读写融为一体的锁,在使用的时候,需要转换
     */
    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    /**
     * 定义写操作
     * 满足:原子 + 独占
     * @param key
     * @param value
     */
    public void put(String key, Object value) {

        // 创建一个写锁
        rwLock.writeLock().lock();

        try {

            System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);

            try {
                // 模拟网络拥堵,延迟0.3秒
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            map.put(key, value);

            System.out.println(Thread.currentThread().getName() + "\t 写入完成");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 写锁 释放
            rwLock.writeLock().unlock();
        }
    }

    /**
     * 获取
     * @param key
     */
    public void get(String key) {

        // 读锁
        rwLock.readLock().lock();
        try {

            System.out.println(Thread.currentThread().getName() + "\t 正在读取:");

            try {
                // 模拟网络拥堵,延迟0.3秒
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Object value = map.get(key);

            System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 读锁释放
            rwLock.readLock().unlock();
        }
    }

    /**
     * 清空缓存
     */
    public void clean() {
        map.clear();
    }


}
public class ReadWriteLockDemo {

    public static void main(String[] args) {

        MyCache myCache = new MyCache();

        // 线程操作资源类,5个线程写
        for (int i = 1; i <= 5; i++) {
            // lambda表达式内部必须是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.put(tempInt + "", tempInt +  "");
            }, String.valueOf(i)).start();
        }

        // 线程操作资源类, 5个线程读
        for (int i = 1; i <= 5; i++) {
            // lambda表达式内部必须是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.get(tempInt + "");
            }, String.valueOf(i)).start();
        }
    }
}

运行结果:

1	 正在写入:1
1	 写入完成
2	 正在写入:2
2	 写入完成
3	 正在写入:3
3	 写入完成
4	 正在写入:4
4	 写入完成
5	 正在写入:5
5	 写入完成
2	 正在读取:
3	 正在读取:
1	 正在读取:
4	 正在读取:
5	 正在读取:
2	 读取完成:2
1	 读取完成:1
4	 读取完成:4
3	 读取完成:3
5	 读取完成:5

从运行结果我们可以看出,写入操作是一个一个线程进行执行的,并且中间不会被打断,而读操作的时候,是同时5个线程进入,然后并发读取操作

乐观锁&悲观锁

悲观锁指一直认为其他线程会过来抢占资源,采取加锁互斥的方式。Java 中的 SynchronizedReentrantLock 等独占锁(排他锁)也是一种悲观锁思想的实现。

乐观锁指认为在修改数据前不会有其它线程过来修改数据,乐观锁的实现方案一般来说有两种: 版本号机制CAS实现 。juc中的AtomicStampedReference时间戳原子引用就是乐观锁实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/174864.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

steam搬砖项目,每天1-2小时,月入1w+(内附教学资料)

steam搬砖就非常合适&#xff0c;最大的优点&#xff0c;操作性简单&#xff0c;0门槛也不用担心&#xff0c;一台电脑就能搞定。利用空余时间完成就可以 话不多说&#xff0c;先上目录&#xff0c;以便大家阅读~ 一.steam搬砖玩法 1、steam搬砖项目介绍 2、项目原理与产出…

ROS2机器人编程简述humble-第二章-Parameters .3.4

ROS2机器人编程简述humble-第二章-Launchers .3.3机器人程序通常需要配置各类参数&#xff0c;官网和一些书中都有介绍。概述ROS中的参数与各个节点相关。参数用于在启动时&#xff08;以及运行时&#xff09;配置节点&#xff0c;而无需更改代码。参数的生存期与节点的生存期相…

【区块链】深入剖析免费赚钱app的本质

你对免费赚钱软件好奇吗&#xff1f;前言一、揭开“免费赚钱app”神秘面纱1.常见的赚钱app2.app真的在做慈善吗&#xff1f;3.羊毛党的价值4.真正的游戏规则二、区块链1.哈希算法2.互联网挖矿三、深入探讨“区块链”的套路1.免费赚钱app真正的价值2.虚拟货币的本质3.虚拟货币泡…

Java设计模式-观察者模式/观察者模式适合那些场景?怎么使用

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 6.7 观察者模式 6.7.1 定义 又称发布-订阅模式&#xff0c;定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象主体对象在状态变化…

[前端笔记——CSS] 9.CSS处理文件的标准流程+DOM

[前端笔记——CSS] 9.CSS处理文件的标准流程DOM1.CSS处理文件的标准流程2.关于DOM2.1 举个例子2.2 应用 CSS 到 DOM1.CSS处理文件的标准流程 当浏览器展示一个文件的时候&#xff0c;它必须兼顾文件的内容和文件的样式信息&#xff0c;CSS处理文件的标准流程如下&#xff1a; …

06_FreeRTOS临界区代码保护

目录 临界段代码保护简介 临界段代码保护函数介绍 任务级临界区函数详解 中断级临界区函数详解 临界段代码保护简介 什么是临界段:临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段 适用场合如: 1.外设:需严格按照时序初始化的外设:IIC、SPI等等 2.系统…

国家发明专利:基于改进型黏菌优化算法的业务资源分配方法

国家发明专利&#xff1a;基于改进型黏菌优化算法的业务资源分配方法 摘要 本发明公开了一种基于改进型黏菌优化算法的业务资源分配方法&#xff0c;其步骤包括&#xff1a;1将生产任务分成若干个环节&#xff0c;构建有向环状分配网络&#xff1b;2构建工厂参数矩阵并归一化&…

力扣98.验证二叉搜索树

文章目录力扣98.验证二叉搜索树题目描述算法思路代码实现力扣98.验证二叉搜索树 题目描述 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含 小于 当前节点的数。 节点的右子树只包含 …

领域驱动设计(DDD)的几种典型架构介绍

我们生活中都听说了DDD&#xff0c;也了解了DDD&#xff0c;那么怎么将一个新项目从头开始按照DDD的过程进行划分与架构设计呢&#xff1f; 一、专业术语 各种服务 IAAS&#xff1a;基础设施服务&#xff0c;Infrastructure-as-a-service PAAS&#xff1a;平台服务&#xff0c…

【Java寒假打卡】JavaWeb-Servlet基础

【Java寒假打卡】JavaWeb-Servlet基础介绍servlet快速入门servlet的执行过程servlet关系视图Servlet实现方式-继承HTTPSERVLETServlet的生命周期线程安全问题servlet的映射方式案例-多路径映射问题-指定名称的方式Servlet的创建时机默认Servlet介绍 servlet是运行在Java服务器…

免费分享一套 SpringBoot + Vue + ElementUI 的人力资源管理系统,挺漂亮的

大家好&#xff0c;我是锋哥&#xff0c;看到一个不错的SpringBoot Vue ElementUI 的人力资源管理系统&#xff0c;分享下哈。 项目介绍 项目背景 人力资源管理是企业运营中必不可少的一环&#xff0c;它关系到企业的前途与发展。尤其对于中小微企业来说&#xff0c;对企业…

积分分离PID控制算法及仿真-1

在普通 PID 控制中引入积分环节的目的&#xff0c;主要是为了消除静差&#xff0c;提高控制精度。但在过程的启动、结束或大幅度增减设定时&#xff0c;短时间内系统输出有很大的偏差&#xff0c;会造成PID运算的积分积累&#xff0c;致使控制量超过执行机构可能允许的最大动作…

Vue项目基础环境搭建完整步骤

使用vue官方脚手架(vue-cli) vue-cli简介 Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统&#xff0c;是一个官方脚手架&#xff0c;可以帮助我们快速创建vue项目工程目录&#xff0c;目前最新版本4.x。 Vue CLI 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建…

第三天总结 之 商品管理界面的实现 之 模糊查询 与 数据在页面展示

商品管理界面的实现 模糊查询 第一步&#xff1a; 明确 查询时 需要的 条件 即sql语句中 where 后的条件 如 &#xff1a; 根据前端 可以发现 模糊查询可以通过 商品名称 日期 商品类型 来查询 所以在对应的controller层下的GoodsFuzzySelectServlet中首先要获取这三个属性的…

《深入浅出计算机组成原理》学习笔记 Day6

二进制编码1. “逢二进一”2. 字符串的表示参考1. “逢二进一” 把一个二进制数对应到十进制&#xff0c;就是把从右到左的第 N 位&#xff0c;乘上一个2 的 N 次方&#xff0c;然后加起来&#xff0c;就成了一个十进制数。从右到左的位置&#xff0c;是从 0 开始的。 例如&a…

初识C语言:IDE的选择与使用【C语言】

本文是本专栏【C语言】的第一篇文章&#xff0c;也是博主最近半年以来的更新第一篇文章&#xff0c;如果觉得有帮助&#xff0c;可以点赞支持一下&#xff01;另外本专栏最后会做一篇类似于【python爬虫】专栏最后的那篇整合的文章&#xff0c;把C语言的所有基础知识用一篇文章…

深度学习 GNN图神经网络(三)模型思想及文献分类案例实战

如果你有一定神经网络的知识基础&#xff0c;想学习GNN图神经网络&#xff0c;可以按顺序参考系列文章&#xff1a; 深度学习 GNN图神经网络&#xff08;一&#xff09;图的基本知识 深度学习 GNN图神经网络&#xff08;二&#xff09;PyTorch Geometric&#xff08;PyG&#x…

Nginx入门与应用

NginxNginx概述Nginx介绍Nginx下载和安装windowsLinuxNginx目录结构Nginx命令查看版本检查配置文件正确性启动和停止重新加载配置文件Nginx环境变量&#xff08;Linux&#xff09;Nginx配置文件结构Nginx具体应用部署静态资源反向代理负载均衡Nginx概述 Nginx介绍 Nginx是一款…

Linux系统——基础IO

要努力&#xff0c;但不要着急&#xff0c;繁花锦簇&#xff0c;硕果累累&#xff0c;都需要过程&#xff01; 目录 1.文件基础必备概念 2.文件系统调用接口 1.open && close 2.write 3.read 3.文件描述符fd 3.1什么是文件描述符 3.2文件描述符意义 3.3文件描述符的分配…

【C++】map和set的模拟实现

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《吃透西嘎嘎》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;红黑树的…