JUC并发编程之线程锁(一)

news2024/9/23 1:26:31

目录

1.ReentrantLock(互斥锁)

 2.ReentRantReaderWriterLock(互斥读写锁)

3.StampedLock(无障碍锁)

4.Condition(自定义锁)

5.LockSupport


问题引出:

        由于传统的线程控制需要用到同步机制Synchronized与 Object类中的wait();notify();函数进行控制,但是这样控制并不容易,所以在JUC中提供了全新的框架,

框架核心接口为:1.Lock();2.ReaderWriteLock();

1.ReentrantLock(互斥锁)

ReentrantLock是一种互斥锁,意思是一旦有一个线程获取到锁,那么其他线程就无法运行将会进行等待阻塞。其中又分为公平锁和非公平锁。区别在于获取锁的机制是否公平。并且该锁通过一个FIFO队列管理所有的等待线程。

 以下是ReentrantLock类的常用方法:

方法名描述
ReentrantLock()创建一个新的ReentrantLock实例。
ReentrantLock(boolean fair)创建一个新的ReentrantLock实例,根据参数fair的值决定是否按公平顺序获取锁。
lock()获取锁,如果锁不可用,则当前线程将被阻塞,直到锁可用。
unlock()释放锁,使得其他等待锁的线程可以尝试获取锁。
tryLock()尝试获取锁,如果锁可用则获取锁并返回true,否则立即返回false。
isFair()判断锁是否是公平锁,如果是公平锁则返回true,否则返回false。
new Condition()创建一个与该锁关联的Condition对象,用于线程间等待和通知,在调用Condition的await方法时,当前线程会释放锁。

案例:有五个售票员同时售卖8张票

package Example2110;

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

class  Ticeket{
    private int ticket = 8;
//    创建互斥锁并设置为公平锁,所有线程运行都是等概率的
    ReentrantLock reentrantLock = new ReentrantLock(true);
    public  void sale(){
        while (true){
            //        开启锁只有一个线程能通过
            reentrantLock.lock();
            try {
//                模拟网络延迟
                TimeUnit.SECONDS.sleep(1);
                if (ticket>0){
                    System.out.println(Thread.currentThread().getName()+"售卖第"+ticket--+"张票");
                }else {
                    System.out.println("卖完咯");
                    break;
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
//                释放锁
                reentrantLock.unlock();
//                线程让行,让其他线程也有机会运行
                Thread.yield();
            }
        }
    }
}
public class javaDemo {
    public static void main(String[] args) {
        Ticeket ticket = new Ticeket();
//        创建多个售卖对象
        for (int i=1;i<=5;i++){
            new Thread(()->{
                ticket.sale();
            },"售票员"+i).start();
        }
    }
}

 

问题引出:

        独占锁的最大弊端就在于其阻断了其他线程只允许一个线程工作。在很多情况下就会造成性能问题。所以引入了ReentrantLock(读写互斥锁)

  面试题:ReentrantLock 是如何实现可重入性的?

(1)什么是可重入性

一个线程持有锁时,当其他线程尝试获取该锁时,会被阻塞;而这个线程尝试获取自己持有锁时,如果成功说明该锁是可重入的,反之则不可重入。

(2)synchronized是如何实现可重入性

synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令。每个锁对象内部维护一个计数器,该计数器初始值为0,表示任何线程都可以获取该锁并执行相应的方法。根据虚拟机规范要求,在执行monitorenter指令时,首先要尝试获取对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有了对象的锁,把锁的计数器+1,相应的在执行monitorexit指令后锁计数器-1,当计数器为0时,锁就被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。

(3)ReentrantLock如何实现可重入性

ReentrantLock使用内部类Sync来管理锁,所以真正的获取锁是由Sync的实现类控制的。Sync有两个实现,分别为NonfairSync(非公公平锁)和FairSync(公平锁)。Sync通过继承AQS实现,在AQS中维护了一个private volatile int state来计算重入次数,避免频繁的持有释放操作带来的线程问题。

(4)代码分析

当一个线程在获取锁过程中,先判断state的值是否为0,如果是表示没有线程持有锁,就可以尝试获取锁。
当state的值不为0时,表示锁已经被一个线程占用了,这时会做一个判断current==getExclusiveOwnerThread(),这个方法返回的是当前持有锁的线程,这个判断是看当前持有锁的线程是不是自己,如果是自己,那么将state的值+1,表示重入返回即可。


 2.ReentRantReaderWriterLock(互斥读写锁)

         这个锁的机制和ReentrantLock区别并不大,但是将其权力拆分出来,分别为共享锁读,和互斥锁写锁。意思就是多线程想要修改数据就只允许其中一个线程修改其他等待,但是读取数据大家都可以随意读取。

ReentrantReaderWriter类的常用操作方法:

方法名描述
readLock()获取读锁,如果写锁被占用,则当前线程会被阻塞,直到写锁释放。
writeLock()获取写锁,如果读锁或写锁被占用,则当前线程会被阻塞,直到所有的读锁和写锁都释放。

注意:实例化该对象用到开头提到框架中的ReadWriteLock接口进行向上转型得到对象。

如:ReaderWriteLock readWriterLock = new RenntrantReaderWriterLock();

以下案例通过ReentrantReadWriteLock实现一个银行10个ATM机,五个进行存款,五个进行读取账户信息

package Example2111;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class  Bank{
    private String name = "黄田";
    private double account = 0;
    private ReadWriteLock readWriteLock= new ReentrantReadWriteLock();
//    存钱
    public void write(double money){
//        设置写锁并开启
        this.readWriteLock.writeLock().lock();
        try {
            account = account+money;
//            模拟网络延迟
            TimeUnit.SECONDS.sleep(1);
            System.out.println("您正在使用"+Thread.currentThread().getName()+"存入存款:"+money+",当前账户余额为"+account);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
//            更新完数据就可以解放写锁
            this.readWriteLock.writeLock().unlock();
        }
    }
//    读取存款
    public void Read(){
//        设置读取共享锁
        this.readWriteLock.readLock().lock();
        try {
//            模拟网络延迟
            TimeUnit.SECONDS.sleep(2);
            System.out.println("您正在使用"+Thread.currentThread().getName()+"读取账户信息:"+":当前账户人名称"+name+",账户人当前的余额为"+account);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
//            读取完数据自动解锁
            this.readWriteLock.readLock().unlock();
        }
    }
}

public class javaDemo {
    public static void main(String[] args) {
        Bank bank = new Bank();
        double moneys[]=new double[]{10,500,300,400};
//        五个ATM存款
        for (int i =1;i<=5;i++){
            new Thread(()->{
                for (int j=0;j<moneys.length;j++){
                    bank.write(moneys[j]);
                }
            },"银行分行ATM"+i+"号取款机").start();
        }
//        五个ATM读取存款
        for (int i=5;i<=10;i++){
            new Thread(()->{
                bank.Read();
            },"银行分行ATM"+i).start();
        }
    }
}

面试题:synchronized 和 ReentrantLock 区别是什么? 

synchronized 是和 if、else、for、while 一样的关键字,ReentrantLock 是类,这是二者的本质区别。既然 ReentrantLock 是类,那么它就提供了比synchronized 更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量

synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。

相同点:两者都是可重入锁

两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

主要区别如下:

    ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
    ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
    ReentrantLock 只适用于代码块锁,而 synchronized 可以修饰类、方法、变量等。
    二者的锁机制其实也是不一样的。ReentrantLock 底层调用的是 Unsafe 的park 方法加锁,synchronized 操作的应该是对象头中 mark word

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

    普通同步方法,锁是当前实例对象
    静态同步方法,锁是当前类的class对象
    同步方法块,锁是括号里面的对象


3.StampedLock(无障碍锁)

        问题引出:虽然ReentrantLock和ReentrantReaderWriter解决了并发访问下数据写入安全和效率的问题,但是如果出现非常多的线程的时候,有可能会造成一些线程一直阻塞。调度减少的情况。为此JUC提供了StampedLock(无障碍锁)该所的特点在于若干个读线程之间互不干预。并且可以保证多个写线程的独占操作

以下是StampedLock类的常用方法:

方法描述
readLock()获取读锁。
tryReadLock()尝试获取读锁,如果获取成功返回一个非负数,否则返回一个负数。
optimisticRead()获取一个乐观读锁。
tryConvertToOptimisticRead(stamp)尝试将锁从写锁转换为乐观读锁,如果转换成功返回一个非负数,否则返回一个负数。
tryConvertToReadLock()尝试将锁从乐观读锁转换为普通读锁,如果转换成功返回一个非负数,否则返回一个负数。
writeLock()获取写锁。
tryWriteLock()尝试获取写锁,如果获取成功返回一个非负数,否则返回一个负数。
unlock()释放锁。
unlockRead()释放读锁。
unlockWrite()释放写锁。
validate()验证锁是否仍然有效。

  在StampedLock中有三种模式,乐观读,读,写用以提高并发处理性能,也用以转换锁的类型

案例代码:

假设我们有一个名为Point的类,表示二维平面上的一个点,其中包含x坐标和y坐标。我们希望实现对这个点的读写操作,并且要确保在写操作时其他线程不能同时读取或写入。

package Example2113;

import org.omg.CORBA.BAD_CONTEXT;

import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.locks.StampedLock;

class Point{
//    坐标
    double x;
    double y;
    private final StampedLock stampedLock = new StampedLock();

    public void set(double x,double y){
//        获取写锁
        long stamp = stampedLock.writeLock();
        try {
            this.x = x;
            this.y = y;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
//            执行完后释放写锁
            stampedLock.unlockWrite(stamp);
        }
    }

    public double[] get(){
//        尝试获取乐观锁
        long stamp = stampedLock.tryOptimisticRead();
        double currentX =x;
        double currentY =y;
//        如果获取乐观锁失败则转为悲观锁
        if (!stampedLock.validate(stamp)){
//            获取
            stamp  = stampedLock.readLock();
            try {
                currentX =x;
                currentY = y;
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                stampedLock.unlockRead(stamp);
            }
        }
        return new double[]{currentX, currentY};
    }
}

public class javaDemo {
    public static void main(String[] args) {
        Point point = new Point();
        Random random = new Random(10);

        for (int i =0;i<5;i++){
            new Thread(()->{
                point.set(random.nextDouble(), random.nextDouble());
                double recieve[] = point.get();
                System.out.println(Arrays.toString(recieve));
            }).start();
        }
//        for (int j = 0;j<20;j++){
//            new Thread(()->{
//                double recieve[] = point.get();
//                System.out.println(Arrays.toString(recieve));
//            }).start();
//        }
    }
}

问:乐观锁和悲观锁是什么?

乐观锁和悲观锁是并发编程中两种不同的锁策略。

  1. 乐观锁:乐观锁假设多个线程之间的并发冲突很少发生,所以它们可以同时读取共享数据而无需阻塞其他线程。在乐观锁中,线程首先尝试获取读锁,即乐观读取操作。如果没有发生写入冲突,那么乐观读锁会立即完成,并返回结果。但如果有其他线程在此期间进行了写入操作,则需要重新获取悲观锁再次尝试。

  2. 悲观锁:悲观锁假设多个线程之间的并发冲突经常发生,因此每个线程在访问共享数据之前会悲观地认为会发生冲突,所以必须先获得独占锁(写锁)或共享锁(读锁)。只有持有锁的线程完成操作后,其他线程才能访问共享数据。悲观锁会导致其他线程阻塞等待锁的释放,从而降低并发性能。

问:为什么要用Stamp,代码中的long stamp是什么意思

stamp是一个标记,用于记录锁的状态。在读取锁和写入锁上下文之间传递,以确保数据一致性。

面试题 :请谈谈 ReadWriteLock 和 StampedLock

ReadWriteLock包括两种子锁

(1)ReadWriteLock

ReadWriteLock 可以实现多个读锁同时进行,但是读与写和写于写互斥,只能有一个写锁线程在进行。

(2)StampedLock

StampedLock是Jdk在1.8提供的一种读写锁,相比较ReentrantReadWriteLock性能更好,因为ReentrantReadWriteLock在读写之间是互斥的,使用的是一种悲观策略,在读线程特别多的情况下,会造成写线程处于饥饿状态,虽然可以在初始化的时候设置为true指定为公平,但是吞吐量又下去了,而StampedLock是提供了一种乐观策略,更好的实现读写分离,并且吞吐量不会下降。

StampedLock包括三种锁:

(1)写锁writeLock:

writeLock是一个独占锁写锁,当一个线程获得该锁后,其他请求读锁或者写锁的线程阻塞, 获取成功后,会返回一个stamp(凭据)变量来表示该锁的版本,在释放锁时调用unlockWrite方法传递stamp参数。提供了非阻塞式获取锁tryWriteLock。

(2)悲观读锁readLock:

readLock是一个共享读锁,在没有线程获取写锁情况下,多个线程可以获取该锁。如果有写锁获取,那么其他线程请求读锁会被阻塞。悲观读锁会认为其他线程可能要对自己操作的数据进行修改,所以需要先对数据进行加锁,这是在读少写多的情况下考虑的。请求该锁成功后会返回一个stamp值,在释放锁时调用unlockRead方法传递stamp参数。提供了非阻塞式获取锁方法tryWriteLock。

(3)乐观读锁tryOptimisticRead:

tryOptimisticRead相对比悲观读锁,在操作数据前并没有通过CAS设置锁的状态,如果没有线程获取写锁,则返回一个非0的stamp变量,获取该stamp后在操作数据前还需要调用validate方法来判断期间是否有线程获取了写锁,如果是返回值为0则有线程获取写锁,如果不是0则可以使用stamp变量的锁来操作数据。由于tryOptimisticRead并没有修改锁状态,所以不需要释放锁。这是读多写少的情况下考虑的,不涉及CAS操作,所以效率较高,在保证数据一致性上需要复制一份要操作的变量到方法栈中,并且在操作数据时可能其他写线程已经修改了数据,而我们操作的是方法栈里面的数据,也就是一个快照,所以最多返回的不是最新的数据,但是一致性得到了保证。

面试题: 如何让 Java 的线程彼此同步?

  1. synchronized
  2. volatile
  3. ReenreantLock
  4. 使用局部变量实现线程同步


4.Condition(自定义锁)

        在JUC中允许用户进行锁对象的创建,可以通过Condition接口实现。经常用于生产者消费者模型中。

以下是Condition接口的常用方法

方法描述
await()当前线程等待,并释放锁。等价于Object.wait()
await(long time, TimeUnit unit)当前线程等待一段时间,并释放锁。等价于Object.wait()
awaitUninterruptibly()当前线程不可中断地等待,并释放锁。
signal()唤醒一个等待该条件的线程。等价于Object.notify()
signalAll()唤醒所有等待该条件的线程。等价于Object.notifyAll()

 使用案例:

假设有一个生产者-消费者模型,多个生产者线程负责向共享队列中生产数据,多个消费者线程负责从队列中消费数据。

package Example2114;

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


class SharedQueue {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    private static final int MAX_SIZE = 10;
    private Queue<Integer> queue = new LinkedList<>();

    public void produce(int num) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == MAX_SIZE) {
                // 队列满时,等待条件notFull
                notFull.await();
            }
            queue.offer(num);
            System.out.println("Produced: " + num);
            // 唤醒一个等待notEmpty条件的线程
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public int consume() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                // 队列空时,等待条件notEmpty
                notEmpty.await();
            }
            int num = queue.poll();
            System.out.println("Consumed: " + num);
            // 唤醒一个等待notFull条件的线程
            notFull.signal();
            return num;
        } finally {
            lock.unlock();
        }
    }
}

public class javaDemo {
    public static void main(String[] args) {
        SharedQueue sharedQueue = new SharedQueue();

        // 创建生产者线程
        Thread producer1 = new Thread(() -> {
            try {
                for (int i = 0; i < 20; i++) {
                    sharedQueue.produce(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread producer2 = new Thread(() -> {
            try {
                for (int i = 20; i < 40; i++) {
                    sharedQueue.produce(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 创建消费者线程
        Thread consumer1 = new Thread(() -> {
            try {
                for (int i = 0; i < 20; i++) {
                    sharedQueue.consume();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread consumer2 = new Thread(() -> {
            try {
                for (int i = 0; i < 20; i++) {
                    sharedQueue.consume();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 启动线程
        producer1.start();
        producer2.start();
        consumer1.start();
        consumer2.start();
    }
}

案例2:实现缓存队列的读取:

package Example2116;

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//缓冲读取与写入
class DataBuffer{
    private final Lock lock = new ReentrantLock();
    private final Condition notUll = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    private static final int MAX_SIZE = 20;
    Queue<String> queue = new LinkedList<>();

    public void Set(String str){
//        获取互斥锁
        lock.lock();
        try {
//            当缓冲区满了
            while (queue.size()==MAX_SIZE){
                notUll.await();
            }
//            模拟网络延迟
            TimeUnit.SECONDS.sleep(2);
//            放入数据并尝试唤醒等待的消费者线程
            queue.offer(str);
            System.out.println(Thread.currentThread().getName()+"放入数据"+str);
            notEmpty.signal();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public  String Get() throws Exception{
//        使用互斥锁
        lock.lock();
        try {
            while (queue.isEmpty()){
                notEmpty.await();
            }
            String string = queue.poll();
            System.out.println("获取到缓冲区内容"+string);
            notUll.signal();
            return string;
        }finally {
//            解锁
            lock.unlock();
        }
    }
}

public class javaDemo {
    public static void main(String[] args) {
        DataBuffer dataBuffer = new DataBuffer();
        for (int i=0;i<5;i++){
            new Thread(()->{
                for (int j = 0;j<20;j++){
                    dataBuffer.Set("数据"+j+"号数、");
                }
            },"生产者"+i+"号").start();
        }
        for (int i=0;i<50;i++){
            new Thread(()->{
                for (int j=0;j<100;j++){
                    try {
                        System.out.println("消费者取走"+dataBuffer.Get());
                    }catch (Exception e){}
                }
            }).start();
        }
    }
}

 

面试题: Java 如何实现多线程之间的通讯和协作?

Java中线程通信协作的最常见的两种方式:

1、syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()

2、ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()


5.LockSupport

由于JDK1.2时候为了预防死锁废除了一些Thread中的方法,但是一部分人认为这几个方法在操作上实际会更加直观,所以就有了LockSupport来替代这几个方法

LockSupport类的常用方法:

方法签名说明
park()阻塞当前线程,直到调用unpark(Thread thread)或者中断当前线程。
park(Object blocker)阻塞当前线程,并关联一个特定的阻塞对象,用于调试和监控的目的。
parkNanos(long nanos)阻塞当前线程,最多阻塞指定的纳秒数,参数nanos为等待时间。
parkNanos(Object blocker, long nanos)阻塞当前线程,并关联一个特定的阻塞对象,最多阻塞指定的纳秒数。
unpark(Thread thread)解除指定线程的阻塞状态。可以提前唤醒被阻塞的线程,使其继续执行。

案例代码:设置一个长辈线程和一个孩子辈线程,只有当长辈线程吃饭时候子线程才能进行吃饭

package Example2117;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class javaDemo {
    public static void main(String[] args) {
//        子线程
        Thread son = new Thread(()->{
//            阻塞线程
            LockSupport.park();
            System.out.println("子辈开始吃饭");
        });
//        父辈线程
        Thread father = new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println("长辈开始吃饭");
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                LockSupport.unpark(son);
            }
        });
        son.start();
        father.start();
    }
}

 面试题:Java Concurrency API 中的 Lock 接口(Lock interface)是什么?对比同步它有什么优势?

Lock 接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。

它的优势有:

(1)可以使锁更公平

(2)可以使线程在等待锁的时候响应中断

(3)可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间

(4)可以在不同的范围,以不同的顺序获取和释放锁

整体上来说 Lock 是 synchronized 的扩展版,Lock 提供了无条件的、可轮询的(tryLock 方法)、定时的(tryLock 带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition 方法)锁操作。另外 Lock 的实现类基本都支持非公平锁(默认)和公平锁,synchronized 只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。


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

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

相关文章

LeetCode--HOT100题(23)

目录 题目描述&#xff1a;206. 反转链表&#xff08;简单&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;206. 反转链表&#xff08;简单&#xff09; 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 LeetCode做题链接&…

剑指offer56-II.数组中数字出现的次数II

第一种方法非常简单&#xff0c;就是用一个HashMap&#xff0c;key是数组中元素的值&#xff0c;value是出现的次数&#xff0c;所以遍历一遍数组&#xff0c;如果map中没有这个元素就把它put进去value设为1&#xff0c;否则value加1&#xff0c;然后遍历一遍map&#xff0c;如…

tensorflow 1.14 的 demo 02 —— tensorboard 远程访问

tensorflow 1.14.0&#xff0c; 提供远程访问 tensorboard 服务的方法 第一步生成 events 文件&#xff1a; 在上一篇demo的基础上加了一句&#xff0c;如下&#xff0c; tf.summary.FileWriter("./tmp/summary", graphsess1.graph) hello_tensorboard_remote.py …

Celery嵌入工程的使用

文章目录 1.config 1.1 通过app.conf进行配置1.2 通过app.conf.update进行配置1.3 通过配置文件进行配置1.4 通过配置类的方式进行配置2.任务相关 2.1 任务基类(base)2.2 任务名称(name)2.3 任务请求(request)2.4 任务重试(retry) 2.4.1 指定最大重试次数2.4.2 设置重试间隔时间…

基础排障实验

排障实验要求&#xff1a; 确保重启后服务能正常访问。确保在客户机上&#xff0c;应用能够使用http://www.jxjz.com:8081访问。确保DNS能够解析邮件服务器mail.jxjz.com。确保DHCP能够保留地址192.168.100.200/24给MAC为ff-0a-ac-44-33-22的主机。确保client客户机能正常的分…

【mars3d - 报错】使用mars3d加载时的一些报错和不生效问题

在使用过程中遇到过很多报错&#xff0c;不管大的还是小的&#xff0c;在这里总结下&#xff0c;应该会持续更新&#xff1b; 1、设置贴地之后报错 可能是因为 arcType&#xff1a;Cesium.arcType.NONE 与 clampToGround&#xff1a;true 是相互冲突的&#xff0c;两个都设置就…

Ubuntu常用压缩指令总结

一、tar tar是Linux系统中最常用的压缩工具之一&#xff0c;它的一个优点是它可以保留文件的权限和所有权信息。tar可以创建.tar文件&#xff08;通常称为"tarball"&#xff09;&#xff0c;或者与gzip或bzip2等工具结合使用来创建.tar.gz或.tar.bz2文件。gzip工具的…

考研算法第40天:众数 【模拟,简单题】

题目 本题收获 又是一道比较简单的模拟题&#xff0c;就不说解题思路了&#xff0c;说一下中间遇到的问题吧&#xff0c;就是说cin输入它是碰到空格就停止输入的&#xff0c;详细的看下面这篇博客对于cin提取输入流遇到空格的问题_while(cin) 空格_就是那个党伟的博客-CSDN博…

【动态规划刷题 6】 删除并获得点数 粉刷房子

740. 删除并获得点数 给你一个整数数组 nums &#xff0c;你可以对它进行一些操作。 每次操作中&#xff0c;选择任意一个 nums[i] &#xff0c;删除它并获得 nums[i] 的点数。之后&#xff0c;你必须删除 所有 等于 nums[i] - 1 和 nums[i] 1 的元素。 开始你拥有 0 个点数。…

JavaScript实践:用Canvas开发一个可配置的大转盘抽奖功能

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f3c6;本文已…

基于Qlearning强化学习的路径规划算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 Q值更新规则 4.2 基于Q-learning的路径规划算法设计 4.3 Q-learning路径规划流程 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022A 3.部分核心程序 ..…

leetcode870. 优势洗牌(java)

优势洗牌 leetcode870. 优势洗牌题目描述双指针 排序代码 滑动窗口 leetcode870. 优势洗牌 难度 - 中等 leetcode870. 优势洗牌 题目描述 给定两个长度相等的数组 nums1 和 nums2&#xff0c;nums1 相对于 nums2 的优势可以用满足 nums1[i] > nums2[i] 的索引 i 的数目来描…

SecOC基础原理详解1

1、SecOC是什么&#xff1f; SecOC官方说法叫安全通讯模块。通俗一点就是发送CAN原始数据的时候进行加密&#xff0c;解析CAN原始数据的时候进行解密。 车上实施SecOC机制数据的ECU&#xff08;ECU是啥&#xff1f;可以百度一下&#xff09;&#xff0c;即使接收到了攻击性的…

如何将NAS空间变为本地磁盘,并拥有与实体硬盘所有常用功能

1.前言 作为一个垃圾佬&#xff0c;用自己的双路E5板子&#xff0c;加持PCIE拆分&#xff0c;4块PM983A齐上阵&#xff0c;气势可嘉。作为一个家庭NAS&#xff0c;如果仅仅用他当作一个普通的存储区存储数据那未免太过于浪费了&#xff1b;另一边&#xff0c;由于工作机硬盘太…

leetcode 子序列问题

718 最长重复子数组 此处求的是连续的子序列&#xff0c;使用动态规划进行求解。 使用dp[i][j]表示第1个序列前i个数字和第2个序列前j个数字的最大的重复子数组长度。 class Solution(object):def findLength(self, nums1, nums2):""":type nums1: List[int]:t…

Kotlin语法

整理关键语法列表如下&#xff1a; https://developer.android.com/kotlin/interop?hlzh-cn官方指导链接 语法形式 说明 println("count ${countnum}")字符串里取值运算 val count 2 var sum 0 类型自动推导 val 定义只读变量&#xff0c;优先 var定义可变变量…

Visual Studio在Release模式下设置代码的调试

Visual Studio在Release模式下设置代码的调试 Debug 模式下模型的加载速度、打开速度会降低很多&#xff0c;这里不推荐使用 Debug 模式进行调试。 Release 模式下可进行调试&#xff0c;使用 Release 模式调试&#xff0c;会提高模型打开速度、 加载速度、编译速度&#xff0c…

ChatGPT 6 月流量下滑 10%大模型遇到增长停滞,背后原因是什么?

近期数据显示&#xff0c;ChatGPT在过去的6个月中流量下滑了10%。这引发了对大模型发展是否达到瓶颈的疑问。我们对此进行了分析&#xff0c;并得出以下观点。首先&#xff0c;ChatGPT在实用性方面存在一些问题。它生成的文本内容往往过于模板化&#xff0c;句式和结构的同质性…

【计算机视觉 | Kaggle】保姆级教程:入门 Kaggle 的步骤详细介绍

文章目录 一、Overview二、Evaluation三、Timeline四、Code Requirements五、Data5.1 数据的可视化5.2 文件 六、Discussion七、Code 一、Overview 当进入到一场比赛的 Overview 页面后&#xff0c;先读完 Description&#xff0c;了解比赛讲了一件什么事情。 我们以一场比赛…

无涯教程-Perl - getprotobynumber函数

描述 此函数在标量context中将协议NUMBER转换为其对应的名称,在列表context中将其名称和相关信息转换为:($name,$aliases,$protocol_number)。 语法 以下是此函数的简单语法- getprotobynumber NUMBER返回值 此函数针对错误返回undef,否则返回标量context中的协议编号,并在…