java多线程详细讲解 线程的创建、线程的状态、synchronized锁、Volatile关键字、和cas锁(自旋锁 乐观锁 无锁)

news2025/5/23 3:58:56

java多线程详细讲解 线程的创建、线程的状态、synchronized锁、Volatile关键字、和cas锁(自旋锁 乐观锁 无锁)

    • 一、线程的概念
    • 二、创建线程的三种方式
    • 三、线程方法Sleep、Yield、Join
    • 四、线程的执行状态
    • 五、synchronized关键字
      • 1.为什么要上锁?
      • 2.锁定的内容是什么?
      • 3.synchronized加锁的方式
      • 4.同步方法和非同步方法是否可以同时调用?
      • 5.面试题:模拟银行账户,对业务写方法加锁,对业务读方法不加锁,这样行不行?
      • 6.synchronized是否是可重入锁?
      • 7.程序中出现异常,锁是否会被释放?
      • 8.synchronized的底层实现
    • 六、Volatile关键字
      • 1.保证线程可见性
        • 1.1. 用volatile保持可见性
      • 2.禁止指令重排序(CPU)
        • 2.1. 线程的as-if-serial
        • 2.2. CPU的乱序执行
        • 2.3. 使用内存屏障阻止乱序执行
        • 2.4. JVM中的内存屏障
        • 2.5. volatile的底层实现
        • 2.6. 面试题:DCL单例要不要加volatile?为什么?
    • 七、CAS(Compare And Swap /Set / Exchange)(自旋锁 乐观锁) (无锁)
      • 1.AtomicInteger类
      • 2.Unsafe类
        • 1)Unsafe类的C++源码追踪
        • 2) C++中cmpxchg方法解释

一、线程的概念

每个线程可以理解为单独的任务线,可以同时执行。如烧开水和扫地两件事可以同时进行。

二、创建线程的三种方式

继承Thread类、实现Runnable接口、或者使用lambda表达式


    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Thread");
        }
    }

    static class MyRunnable implements Runnable {

        @Override
        public void run() {
            System.out.println("Runnable");
        }
    }

    public static void main(String[] args) {
        new MyThread().start();
        new Thread(new MyRunnable()).start();
        new Thread(() -> System.out.println("lambda")).start();
    }

三、线程方法Sleep、Yield、Join

  • Sleep是线程睡眠,cpu可以执行其他线程。睡眠时间结束继续执行。
  • Yield 暂时让出执行权,当前线程和其他线程一起抢夺cpu执行权。
  • Join 线程2中执行线程1.join的含义是,等线程1执行完后,继续执行线程2剩余代码。
public static void main(String[] args) {
//        testSleep();
//        testYield();
        testJoin();
    }

    static void testSleep() {
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("A" + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    static void testYield() {
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("A" + i);
                if (i % 10 == 0) {
                    Thread.yield();
                }

            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("  B  " + i);
                if (i % 10 == 0) {
                    Thread.yield();
                }

            }
        }).start();
    }

    static void testJoin() {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("A" + i);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });

        Thread t2 = new Thread(() -> {
            System.out.println("  b start  ");
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 100; i++) {
                System.out.println("b" + i);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            System.out.println("  b end  ");
        });
        t1.start();
        t2.start();
    }

四、线程的执行状态

  • 1.线程在被创建(new)还没有调用start方式时,状态为NEW。

  • 2.线程被调用start方法后被线程调度器执行(Runnbale)。其中有分为两种状态:就绪状态(Ready)、运行状态(Running)(就绪状态中的线程被调度器选中执行后进入运行状态)。

  • 3.线程执行结束进入线程结束状态(Teminated)【注意线程结束后不可以再次调用start方法】

  • 4.除正常执行结束线程外,还有可能出现线程等待的情况。

    • 如在执行同步代码块(如synchronized)没有获得锁时进入blocked,获得锁后进入线程调度器执行(Runnbale);
    • 在运行时如果调用了o.wait()、t.join、LockSupport.park()方法则进入waiting状态,在调用o.notify() 、o.notifyAll()、LockSupport.unPark()进入线程调度器执行(Runnbale);
    • 在运行时如果Thread.sleep(time)、o.wait(time)、t.join(time)、LockSupport.parkNanos()、LockSupport.parkUntil()进入TimedWaiting,时间结束后进入线程调度器执行(Runnbale);
      在这里插入图片描述

五、synchronized关键字

1.为什么要上锁?

多线程访问操作相同资源时,需要加锁。如i++;由两个线程同时调用i++;正常结果是2,但是结果可能会出现1,两个线程同时读取i=0,加1后把i=1;写回。导致结果出现i=1;

2.锁定的内容是什么?

锁定的内容是对象。底层实现是对象的内存结构mark word中最后两个字节的状态00、01、10、11。
注意: 不要使用java中的常亮对象String、Long、Integer等作为锁对象。

private int count=10;
    private Object o=new Object();
    public void m(){
        synchronized(o){//任何代码要执行下面代码,必须先拿到o的锁
            count--;
            System.out.println(count);
        }
    }

3.synchronized加锁的方式

1.可以加在方法中 (代码同上)
2.也可以加在方法上。在在方法方法上锁的对象既是this对象

 public synchronized void m2(){//等同于在方法中的代码执行synchronized(this)
            count--;
            System.out.println(count);   
    }

4.同步方法和非同步方法是否可以同时调用?

//同步方法指的是加锁的方法,非同步方法则反之。
可以同时调用


public class T {

    public synchronized void m() {
        System.out.println("m1 start");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m1 end");
    }

    public void m2() {
        System.out.println("m2");
    }

    public static void main(String[] args) {
        T t=new T();
        new Thread(t::m).start();
        new Thread(t::m2).start();
    }
}

运行结果为:
m1 start
m2
m1 end

5.面试题:模拟银行账户,对业务写方法加锁,对业务读方法不加锁,这样行不行?

容易产生脏读问题(dirtyRead),如果业务逻辑中允许脏读可以读取时不加锁,如果业务不允许脏读,则需要加锁。


class Account {
        String name;
        double balance;

        public synchronized void set(String name, double balance) {
            this.name = name;
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.balance = balance;
        }

        public /*synchronized*/ double getBalance(String name) {
            return this.balance;
        }

        public static void main(String[] args) {
            Account a = new Account();
            new Thread(() -> a.set("zhangsan", 100.0)).start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(a.getBalance("zhangsan"));
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(a.getBalance("zhangsan"));

        }

    }

上面代码中get方法不加锁运行结果:
0.0
100.0

get方法加synchronized锁运行结果为:
100.0
100.0

6.synchronized是否是可重入锁?

synchronized是可重入锁。

比如父类方法中加了synchronized锁,子类方法也有synchronized锁,子类方法调用super方法及调用父类方法,如果不能重入则会出现死锁。

验证代码:

public class T6 {
    public synchronized void m() {
        System.out.println("m1 start");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        m2();
        System.out.println("m1 end");
    }

    public synchronized void m2() {
        System.out.println("m2");
    }

    public static void main(String[] args) {
        T6 t=new T6();
        new Thread(t::m).start();
    }
}

7.程序中出现异常,锁是否会被释放?

在程序中出现异常,默认情况锁会被释放。
所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。

比如,在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,
在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到产生时的数据,
因此要非常小心的处理同步业务逻辑中的异常。

8.synchronized的底层实现

JDK早期的 重量级 -OS
后来改进为锁升级

锁升级概念: 没错,我就是厕所所长!(一)
没错,我就是厕所所长!(二)

markword 记录这个线程ID(偏向锁)
如果有线程争用:升级为自旋锁;
自旋10次以后,升级为重量级锁 - OS

//锁不能降级

使用场景: 线程比较少且执行时间短用自旋锁;线程数量比较多或执行时间长用系统锁;

六、Volatile关键字

1.保证线程可见性

  • MESI(MESI协议是基于Invalidate的高速缓存一致性协议,并且是支持回写高速缓存的最常用协议之一) 缓存一致性协议

1.1. 用volatile保持可见性

  • 多线程提高效率,本地缓存数据,造成数据修改不可见,
    想要保证可见,要不触发同步指令(如循环中的System.out.println),要么加上volatile,被修饰的内存,只要有修改,马上同步到每个线程

可以先看一下下面的代码,m方法while处会死循环,除非修改了running=false;
再看一main方法,它开启了一个新线程执行了方法m,之后睡眠1秒后执行running=false;,执行结果只有‘m start’没有‘m end’。

原因是cpu在执行新线程时 把资源(如running)拷贝了一份到CPU内执行,这样比从内存中取快了100倍左右。但是存在着一些问题,比如其他线程中修改了running,但是另一个线程不知道。

所以当线程2修改了变量r,要同步到内存,并且通知其他线程去内存重新获取变量。

解决方案:及boolean running = true;前加volatile关键字,实现可见性。
在这里插入图片描述

public class HelloVolatile {
//    volatile boolean running = true;
     boolean running = true;

    void m() {
        System.out.println("m start");
        while (running) {
//            System.out.println("hello");
        }
        System.out.println("m end");
    }

    public static void main(String[] args) {
        try {
            T01_HelloVolatile t = new T01_HelloVolatile();
            new Thread(t::m, "t1").start();
            Thread.sleep(1000);
            t.running = false;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.禁止指令重排序(CPU)

2.1. 线程的as-if-serial

  • 单线程,两句语句,未必是按照顺序执行
  • 单线程的重排序,必须保障最终一致性
  • as-if-serial:看上去像是序列化(单线程)

java代码:

public class Disorder {
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;


    public static void main(String[] args) throws Exception {
        int i = 0;
        for (; ; ) {
            CountDownLatch countDownLatch = new CountDownLatch(2);

            i++;
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            Thread one = new Thread(new Runnable() {
                @Override
                public void run() {
                    //由于one先启动,下面这句话让它等一等线程two,低着乐意根据自己电脑的实际性能适当调整等待时间。

                    a = 1;
                    x = b;
                    countDownLatch.countDown();
                }
            });
            Thread other = new Thread(new Runnable() {
                @Override
                public void run() {

                    b = 1;
                    y = a;
                    countDownLatch.countDown();
                }
            });
            one.start();
            other.start();
            countDownLatch.await();

            String result = "第" + i + "次(" + x + "," + y + ")";
            if (x == 0 && y == 0) {
                System.out.println(result);
                break;
            } else {
//                System.out.println(result);
            }
        }
    }
}

先说执行结果:在一段时间后 打印出来x=0 ,y=0。

通过以上代码(Disorder类),可以验证代码会发生重排序。
如果代码不会重排序则线程1中 ‘a = 1; ’一定在‘x = b;’之前,线程2‘ b = 1;’一定在‘ y = a;’之前;
则两个线程运行会出现以下几种情况

// a = 1;
// x = b;
// b = 1;
// y = a;
结果x值为0,y值为1

// a = 1;
// b = 1;
// x = b;
// y = a;
结果x值为1,y值为1

// b = 1;
// y = a;
// a = 1;
// x = b;
结果x值为1,y值为0

但是绝对不会出现( x = b;在a = 1;之前 或 y = a;在b = 1;之前),如果出现了则证明代码回重排序(x值为0,y值为0)。当然需要运行一段时间才会出来结果,可能是3-5分钟后才会出现一次。

// x = b;
// b = 1;
// y = a;
// a = 1;

结果x值为0,y值为0

2.2. CPU的乱序执行

为什么会乱序执行?
主要是为了提高效率。

比如:

{
File a=资源加载;
int b=1}

第一行代码和第二行代码先后执行不影响最终结果。在执行第一行代码时间过长,在等待资源加载,完全可以先执行第二行代码,充分利用cpu资源。

2.3. 使用内存屏障阻止乱序执行

内存屏障是特殊指令,前面的必须执行完,后面的才能执行
intel:Ifence sfence mfence

2.4. JVM中的内存屏障

所有实现JVM的虚拟机,必须实现四个屏障
LoadLoadBarrier LoadStore StoreLoad StoreStore

单词解释:
Load 读
Store 写

2.5. volatile的底层实现

volatile修饰的内存,不可以重新排序,对volatile修饰的变量的读写访问,都不可以换顺序

2.6. 面试题:DCL单例要不要加volatile?为什么?

java单例模式的线程安全 JAVA多线程编程中的双重检查锁定(DCL单例(Double Check Lock))

七、CAS(Compare And Swap /Set / Exchange)(自旋锁 乐观锁) (无锁)

先展示用法,再解释原理。

首先需求是用100个线程同时执行 for循环10000次i++;操作。期望结果是1000000

实现思路1:


public class IPlusPlus {
    private static long n = 0L;

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[100];
        CountDownLatch latch = new CountDownLatch(threads.length);
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
//                    synchronized (IPlusPlus.class) {
                        n++;
//                    }
                }
                latch.countDown();
            });
        }

        for (Thread thread : threads) {
            thread.start();
        }

        latch.await();
        System.out.println(n);
    }
}

执行结果是远远达不到1000000的,这是因为n++操作没有加锁。加上synchronized锁后结果即为1000000。如果不用synchronized锁是否有其他方案呢?

以下便是使用Atomic 自旋锁:

实现思路2:


public class TAtomicInteger {
   static AtomicInteger count = new AtomicInteger(0);

    /* synchronized */ void m() {
        for (int i = 0; i < 10000; i++) {
            //CAS Compare And Swap /Set / Exchange (自旋锁,乐观锁) (无锁)
            count.incrementAndGet();
        }
    }
//两种锁的效率
    //不同场景:
    //临界区执行时间比较长,等的人很多--》重量级
    //时间短,等的人少--》自旋锁
    public static void main(String[] args) {
        TAtomicInteger t = new TAtomicInteger();
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            threadList.add(new Thread(t::m, "thread-" + i));
        }
        threadList.forEach(Thread::start);
        //等待线程执行完
        threadList.forEach((o) -> {
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        //线程执行完再打印结果
        System.out.println(count);
    }
}

以上代码没有使用synchronized锁,结果也可以打印出期望值1000000。这是如何实现的呢?
我们来看一下这个incrementAndGet();方法的源码:

1.AtomicInteger类

第一层AtomicInteger类(只截取了关键代码信息)

public class AtomicInteger extends Number implements java.io.Serializable {
   /*
     * This class intended to be implemented using VarHandles, but there
     * are unresolved cyclic startup dependencies.
     * 这个类打算使用VarHandles来实现,但是是未解析的循环启动依赖项。
     */
    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");

    private volatile int value;

    /**
     * Creates a new AtomicInteger with the given initial value.
     * 使用给定的初始值创建一个新的AtomicInteger。
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    /**
     * Creates a new AtomicInteger with initial value {@code 0}.
     * 使用初始值创建新的AtomicInteger
     */
    public AtomicInteger() {
    }

//…………………………

/**
     * Atomically increments the current value,
     * with memory effects as specified by {@link VarHandle#getAndAdd}.
     * 原子地增加当前值,具有由{@link VarHandle#getAndAdd}指定的内存效果。
     * <p>Equivalent to {@code addAndGet(1)}.
     *
     * @return the updated value 更新后的值
     */
    public final int incrementAndGet() {
        return U.getAndAddInt(this, VALUE, 1) + 1;
    }
    //…………………………
}

在这里插入图片描述
通过追踪源码可以看到incrementAndGet调用了Unsafe类的getAndAddInt();方法,下面我们继续追踪。

2.Unsafe类

通过以下代码可以看出Unsafe类是单例的,且核心方法是用native修饰的(C++实现)


/**
 * A collection of methods for performing low-level, unsafe operations.
 * Although the class and all methods are public, use of this class is
 * limited because only trusted code can obtain instances of it.
 * 用于执行低级别,unsafe操作的方法的集合。
 * 尽管该类和所有方法都是公共的,但该类的使用受限,因为只有受信任的代码才能获得它的实例。
 * 
 * @author John R. Rose
 * @see #getUnsafe
 */

public final class Unsafe {

    private static native void registerNatives();
    static {
        registerNatives();
    }

    private Unsafe() {}

    private static final Unsafe theUnsafe = new Unsafe();

    /**
     * Provides the caller with the capability of performing unsafe
     * operations.
     * 为调用方提供执行unsafe操作的能力。
     *
     */
    public static Unsafe getUnsafe() {
        return theUnsafe;
    }
//……………………
 /** Volatile version of {@link #getInt(Object, long)}  */
    @HotSpotIntrinsicCandidate
    public native int     getIntVolatile(Object o, long offset);
    //……………………
    
	// The following contain CAS-based Java implementations used on
	//以下包含在上使用的基于CAS的Java实现
    // platforms not supporting native instructions
    //不支持本机指令的平台

    /**
     * Atomically adds the given value to the current value of a field
     * or array element within the given object {@code o}
     * at the given {@code offset}.
     * 将给定值原子化地添加到字段的当前值
	 * 或给定对象内的数组元素{@code o}
	 * 在给定的{@code偏移量}处。
	 *
     * @param o object/array to update the field/element in 对象/数组以更新中的字段/元素
     * @param offset field/element offset 偏移字段/元素偏移
     * @param delta the value to add 增量要添加的值
     * @return the previous value 以前的值
     * @since 1.8
     */
    @HotSpotIntrinsicCandidate
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
        return v;
    }


 @HotSpotIntrinsicCandidate
    public final boolean weakCompareAndSetInt(Object o, long offset,
                                              int expected,
                                              int x) {
        return compareAndSetInt(o, offset, expected, x);
    }

	/**
     * Atomically updates Java variable to {@code x} if it is currently
     * holding {@code expected}.
     * 如果Java变量当前持有{@code expected},则原子化地将其更新为{@code x}。
     *
     * <p>This operation has memory semantics of a {@code volatile} read
     * and write.  Corresponds to C11 atomic_compare_exchange_strong.
     * 此操作具有{@code volatile}读写的内存语义。对应于C11 atomic_compare_exchange_strong。
     *
     * @return {@code true} if successful 如果成功
     */
    @HotSpotIntrinsicCandidate
    public final native boolean compareAndSetInt(Object o, long offset,
                                                 int expected,
                                                 int x);

//……………………

}

在这里插入图片描述

上图可以说是一段比较核心cas(compareAndSet 比较并且设置)逻辑,从代码中我们可以看出使用了do{循环执行的语句} while(是否继续循环)语句,首先执行获取当前值v

我自己用java写的代码逻辑 助于理解,不是真的底层实现!

public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
            //如果返回false 则再次获取v值。
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
        return v;
    }
/* 此方法不是源码 */
 public final  boolean compareAndSetInt(Object o, long offset, int v, int x){
        //当然 以下代码只是逻辑 在C++中一句汇编语言带代替 实现原子性。
		if (v == value) {//v=1, x=2,
            value = x;
            return true;//如value也为1 则设置成功 返回true;
        }
        return false;//如value不为1  返回false;                                  
    }

然后进入循环判断 是否比较并设置成功了(compareAndSetInt();)
所以核心方式是compareAndSetInt ,但是这个方法是native修饰的,也就是C++语言实现的这个方法:

1)Unsafe类的C++源码追踪

首先我们定位到Unsafe.cpp类的Unsafe_CompareAndSwapInt方法:
Unsafe.cpp
在这里插入图片描述
最终定位到atomtic_liunx_x86.inline.hpp文件的cmpxchg方法。
jdk8u:atomtic_liunx_x86.inline.hpp 93行:
在这里插入图片描述
LOCK_IF_MP方法逻辑:及多cpu前加lock;
在这里插入图片描述

2) C++中cmpxchg方法解释

  • asm : 汇编码
  • LOCK_IF_MP: MP是Multi-processors(多cpu), 多个cpu情况下要加lock锁执行令,作用只允许一个cpu操作,可以保证此命令的操作原子性。
  • cmpxchgl: compare and exchange(比较并交换)

所以最终执行令是 lock加cmpxchg

lock comxchg 指令

如果此篇文章对您有帮助的话点个赞吧!谢谢!

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

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

相关文章

SDL初识(1)

简介 SDL(Simple DirectMedia Layer) 是一个跨平台开发库&#xff0c;旨在通过 OpenGL 和 Direct3D 提供对音频、键盘、鼠标、操纵杆和图形硬件的低级访问。 SDL 支持 Windows、Mac OS X、Linux、iOS 和 Android。可以在源代码中找到对其他平台的支持。SDL 是用 C 语言编写的…

JavaScript【六】JavaScript中的字符串(String)

文章目录 &#x1f31f;前言&#x1f31f;字符串(String)&#x1f31f;单引号和双引号的区别&#x1f31f;属性&#x1f31f; length :字符串的长度 &#x1f31f; 方法&#x1f31f; str.charAt(index);&#x1f31f; str.charCodeAt(index);&#x1f31f; String.fromCharCod…

死磕“增长”:火山引擎的实用主义

作者 | 曾响铃 文 | 响铃说 在刘慈欣的科幻小说《三体》中&#xff0c;地外文明为了封锁地球科技&#xff0c;在天文台向地球科学家展现了「宇宙闪烁」这一奇观&#xff0c;试图颠覆人类的认知&#xff0c;从而影响科技进步&#xff0c;促使地球科技发展陷入停滞。 如今&…

给你们讲个笑话——低代码会取代程序员

今天是正经男&#xff0c;我们严肃讨论一下一直以来争吵不休的取代问题。 低代码开发平台&#xff0c;低代码技术会取代开发人员么&#xff1f; 一、背景 低代码开发平台的普及&#xff0c;让很多公司对快速生成应用抱有很大期望。甚至有人认为&#xff0c;低代码开发平台未来…

MTLAB绘图

这里写目录标题 一、图例1、散点图 二、绘图1、总体图形参数2、坐标、图框、网格图框去上右边框小刻度网格坐标范围和刻度控制旋转 坐标、刻度 3、图例图例位置和方向 Location和Orientation图例加标题 、分多列 4、文本 字、字体、字号5、线型 符号6、颜色栏 colorbar7、颜色8…

【技能分享】CAD转SHP最好的方法

1、利用 ArcToolsbox 工具先将 DWG 文件转为 MDB 通过 CASS 软件生成的 DWG 文件&#xff0c;字段中包含有很多属性内容&#xff0c;所以我们先将 DWG 格式 的文件转换为 MDB 格式&#xff0c;再通过 MDB 转换为 SHP 格式数据进行整理。具体步骤如下&#xff1a; 通过 ArcTool…

2023Mathorcup高校数学建模挑战赛ABCD选题建议

提示&#xff1a;本科同学尽量选择C、D题进行作答&#xff0c;获奖率相对会高。C君认为的难度&#xff1a;AD<C<B&#xff0c;开放度&#xff1a;B<C<A<D 。 A题 量子计算机在信用评分卡组合优化中的应用 这道题目是传统的运筹学题目。需要建立客户信用等级的…

阿里ARouter 路由框架解析

一、简介 众所周知&#xff0c;在日常开发中&#xff0c;随着项目业务越来越复杂&#xff0c;项目中的代码量也越来越多&#xff0c;如果维护、扩展、解耦等成了一个非常头疼问题&#xff0c;随之孕育而生的诸如插件化、组件化、模块化等热门技术。 而其中组件化中一项的难点&…

Spring Cloud 之五:Feign使用Hystrix

系列目录&#xff08;持续更新。。。&#xff09; Spring Cloud之一&#xff1a;注册与发现-Eureka工程的创建 Spring Cloud之二&#xff1a;服务提供者注册到Eureka Server Spring Cloud之三&#xff1a;Eureka Server添加认证 Spring Cloud之四&#xff1a;使用Feign实现…

camunda如何监控流程执行

在 Camunda 中&#xff0c;可以使用 Camunda 提供的用户界面和 API 来监控流程的执行情况。以下是几种常用的监控流程执行的方式&#xff1a; 1、使用 Camunda Cockpit&#xff1a;Camunda Cockpit 是 Camunda 官方提供的流程监控和管理工具&#xff0c;可以在浏览器中访问 Co…

【百面成神】消息中间件基础7问,你能撑到第几问

前 言 &#x1f349; 作者简介&#xff1a;半旧518&#xff0c;长跑型选手&#xff0c;立志坚持写10年博客&#xff0c;专注于java后端 ☕专栏简介&#xff1a;纯手打总结面试题&#xff0c;自用备用 &#x1f330; 文章简介&#xff1a;消息中间件最基础、重要的9道面试题 文章…

Android中的MVVM架构:使用Jetpack组件实现现代化的应用架构

Android中的MVVM架构&#xff1a;使用Jetpack组件实现现代化的应用架构 Jetpack组件是构建现代Android应用的绝佳利器&#xff0c;组件化设计让构建App如此简单。 引言 随着移动应用的日益复杂和功能的不断增加&#xff0c;构建稳健、可扩展和易维护的Android应用变得越来越重…

[考研数据结构] 第3章之队列的基本知识与操作

文章目录 队列的基本概念 队列的顺序存储 顺序队列 存储类型 基本操作 循序队列 存储类型 基本操作 循环队列判空与判满的三种解决方案 方法一&#xff1a;牺牲一个存储单元 方法二&#xff1a;类型增设记录型变量size 方法三&#xff1a;类型增设标志型变量tag 队…

嵌入式【协议篇】CAN协议原理

一、CAN协议介绍 1、简介 CAN是控制器局域网络(Controller Area Network, CAN)的简称,是一种能够实现分布式实时控制的串行通信网络。 其实可以简单把CAN通信理解成开一场电话会议,当一个人讲话时其他人就听(广播),当多个人同时讲话时则根据一定规则来决定谁先讲话谁后讲…

【音视频】 zlm的几个代理接口解释

目录 12、/index/api/addStreamProxy 30、/index/api/addStreamPusherProxy 14、/index/api/addFFmpegSource 24、/index/api/openRtpServer 27、/index/api/startSendRtp 参考 12、/index/api/addStreamProxy 拉流代理 &#xff1a; 194上在播放。 而10.30.2.6上加上这个…

FastDGCNN

Faster Dynamic Graph CNN: Faster Deep Learning on 3D Point Cloud Data | IEEE Journals & Magazine | IEEE Xplore ​​​​​​​题目&#xff1a;Faster Dynamic Graph CNN: Faster Deep Learning on 3D Point Cloud Data&#xff08;更快的动态图形CNN&#xff1a;对…

Android 对View 进行旋转、缩放、平移的属性变换后,获取外矩形顶点

文章目录 前言改变 View 的属性&#xff0c;进行旋转、缩放、平移输出 View 的属性 使用 matrix 映射 view 变换后的外矩形前(左)乘(preXxx)、后(右)乘(postXxx) 对映射结果的影响前(左)乘(preXxx) 的意义后(右)乘(postXxx) 结论 来张图 前言 Android View 通过平移、旋转、…

找PPT模板就上这5个网站~

分享几个可以永久免费下载PPT模板、素材的网站&#xff0c;上万个模板随便下载&#xff0c;赶紧收藏起来~ 1、菜鸟图库 https://www.sucai999.com/search/ppt/0_0_0_1.html?vNTYxMjky 网站素材非常全面&#xff0c;主要以设计类素材为主&#xff0c;办公类素材也很多&#x…

4、RSA终端指令

RSA总结 加密算法,都是数学知识对称加密(传统加密算法)RSA(三个人的名字)非对称加密(现代加密算法) 原根欧拉函数、欧拉定理(费马小定理)模反元素 m^(e * d) mod n ≡ m迪菲赫尔曼密钥交换RSA算法 RSA: 拆解两个(大)质数的乘积很难!所以RSA想对安全.加密: M ^e % N C解密: C…

前端学习:HTML头部、布局

目录 HTML头部 一、HTML 元素 二、head标签和header标签的不同 三、HTML 元素 四、HTML 元素 五、HTML 元素 六、 HTML 七、HTML元素 为搜索引擎定义关键词&#xff1a; 为网页定义描述内容&#xff1a; 每60秒刷新当前页面&#xff1a; 八、HTML 九、HTML头部元素…