当前位置: 首页 > news >正文

并发编程知识总结

并发编程知识总结

最近学习了:冰河《深入理解高并发编程》《并发编程的艺术》
特此简要对学习做了部分总结,方便后续对并发编程知识的完善和巩固;
若想深入了解学习,可阅读上述参考原著;

线程与线程池

进程

进程是系统进行资源分配的基本单位。现代操作系统在运行一个程序时,会为其创建一个进程。

进程也就是应用程序在内存中分配的空间,也就是正在运行的程序,各个进程之间互不干扰

使用进程+CPU时间片轮转方式的操作系统,在宏观上同一时间段处理多个任务,换句话说,进程让操作系统的并发成为了可能。虽然并发在宏观上看,有多个任务在执行,但事实上,对单核CPU而言,任意具体时刻都只有一个任务在占用CPU资源

线程

线程是CPU调度的基本单位。是比进程更小的可单独运行的单位

在一个进程中,可以创建多个线程,这些线程可以拥有各自私有的计数器、栈内存和局部变量,并且能够访问共享的内存变量。

多线程

在同一程序中,可同时运行多个线程来执行不同的任务;这些线程可同时运用CPU多个核心来运行

为什么使用多线程?最主要的是:多线程编程可最大限度的利用多核CPU资源。

上下文切换

上下文切换是指CPU从一个进程(或线程)切换到另一个进程(或线程)。

上下文是指某一时刻CPU寄存器和程序计数器的内容

上下文切换通常是计算密集型的,意味着此操作会消耗大量的CPU时间,故线程也不是越多越好。如何减少系统中上下文切换次数,是提升多线程性能的一个重要课题

线程的实现方式

  1. 继承Thread类

    public class ThreadDemo {public static class TestThread extends Thread {@Overridepublic void run() {System.out.println("extends Thread");}}public static void main(String[] args) {TestThread testThread = new TestThread();testThread.start();}//console:extends Thread
    }
    
  2. 实现Runnable接口

    public class ThreadDemo {public static class TestThreadRun implements Runnable {@Overridepublic void run() {System.out.println("extends Thread2");}}public static void main(String[] args) {Thread thread = new Thread(new TestThreadRun());thread.start();}//console:extends Thread2
    }
    
  3. 实现Callable接口

    public class ThreadDemo {public static class TestThreadCall implements Callable<String> {@Overridepublic String call() {System.out.println("extends Thread2");return "result:do success";}}public static void main(String[] args) {TestThreadCall call = new TestThreadCall();String result = call.call();System.out.println(result);}//console:// extends Thread2//result:do success
    }
    

线程的优先级

在Java中,通过一个整型的成员变量priority来控制线程优先级,范围从1~10,值越大优先级越高,默认为5。

优先级高的线程在分配CPU时间片时,分配概率高于低优先级的线程。

注意:线程优先级不能作为程序正确性的依赖(高优先级的不保证一定早于低优先级的执行)

线程的执行顺序

在同一个方法中,连续创建多个线程后,调用线程的start()方法的顺序,并不能决定线程的执行顺序

如何确保线程的执行顺序?

  1. 可以使用Thread类中的join()方法来确保线程的执行顺序。join()实际上让主线程等待当前子线程执行完成
  2. join()方法内部会调用本地wait()方法,调用线程wait()方法时,会使主线程处于等待状态,等待子线程执行完毕后再执行

线程的生命周期

线程生命周期中几个重要状态:

  1. NEW:线程被创建,但还没有调用start()方法;

  2. RUNNABLE:可运行状态,包含就绪状态、运行中状态;

  3. BLOCKED:阻塞状态,这状态的线程,须要等待其他线程释放锁或者等待进入synchronized;

  4. WAITTING:等待状态,此状态的线程,需要等待其他线程唤醒或中断操作,方可进入下一状态;

    调用如下三个方法,会让线程进入等待状态:

    1. Object.wait():使当前线程处于等待状态,直到另一个线程唤醒它
    2. Thread.join():等待线程执行完毕,底层调用的是Object的wait()方法
    3. LockSupport.park():除非获得调用许可,否则禁用当前线程进行线程调度
  5. TIME_WAITTING:超时等待状态,可在规定时间后,自行返回;

  6. TERMINATED:终止状态,表示线程执行完毕;

线程生命周期流程图(简略):

在这里插入图片描述

注意:使用jps结合jstack命令可线程分析生产环境Java线程异常信息

深入理解Thread类

Thread类定义

public class Thread implements Runnable {...}@FunctionalInterface
public interface Runnable {public abstract void run();
}

Thread类实现了Runnable接口,而Rnnnable接口仅有一个run()方法,并被@FunctionalInterface注解修饰

加载本地资源

/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {registerNatives();
}

此方法主要用来加载一些本地资源,并在静态代码块中调用此本地方法

Thread成员变量

 //线程名称private volatile String name;//优先级private int            priority;private Thread         threadQ;private long           eetop;/* Whether or not to single_step this thread. *///是否单步线程private boolean     single_step;/* Whether or not the thread is a daemon thread. *///是否守护线程private boolean     daemon = false;/* JVM state */private boolean     stillborn = false;/* What will be run. *///实际执行体private Runnable target;/* The group of this thread */private ThreadGroup group;/* The context ClassLoader for this thread */private ClassLoader contextClassLoader;/* The inherited AccessControlContext of this thread *///访问控制上下文private AccessControlContext inheritedAccessControlContext;/* For autonumbering anonymous threads. *///为匿名线程生成名称的编号private static int threadInitNumber;private static synchronized int nextThreadNum() {return threadInitNumber++;}/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. *///与此线程相关的ThreadLocalThreadLocal.ThreadLocalMap threadLocals = null;/** InheritableThreadLocal values pertaining to this thread. This map is* maintained by the InheritableThreadLocal class.*/ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;//当前线程请求的堆栈大小private long stackSize;//线程终止后存在的JVM私有状态private long nativeParkEventPointer;//线程IDprivate long tid;/* For generating thread ID *///用于生成线程IDprivate static long threadSeqNumber;/* Java thread status for tools,* initialized to indicate thread 'not yet started'*///线程状态,初始为0,表示未启动private volatile int threadStatus = 0;/*** The argument supplied to the current call to* java.util.concurrent.locks.LockSupport.park.* Set by (private) java.util.concurrent.locks.LockSupport.setBlocker* Accessed using java.util.concurrent.locks.LockSupport.getBlocker*/volatile Object parkBlocker;/* The object in which this thread is blocked in an interruptible I/O* operation, if any.  The blocker's interrupt method should be invoked* after setting this thread's interrupt status.*///Interruptible中定义了中断方法,用来中断特定线程private volatile Interruptible blocker;//当前线程的内部锁private final Object blockerLock = new Object();//线程最小优先级public final static int MIN_PRIORITY = 1;//默认优先级public final static int NORM_PRIORITY = 5;//最大优先级public final static int MAX_PRIORITY = 10;

从成员变量可以看出,Thread类不是一个任务,而是一个实实在在的线程对象,其内部Runnable类型的target变量,才是实际执行的任务

线程的状态定义

public enum State {/*** Thread state for a thread which has not yet started.*///新建状态;线程被创建,但是还没有调用start()方法NEW,/*** Thread state for a runnable thread.  A thread in the runnable* state is executing in the Java virtual machine but it may* be waiting for other resources from the operating system* such as processor.*///可运行状态;包括运行中状态和就绪状态RUNNABLE,//阻塞状态;此状态的线程需要等待其他线程释放锁,或者等待进入synchronizedBLOCKED,//等待状态;此状态的线程需要其他线程对其进行唤醒或者中断状态,进而进入下一状态WAITING,//超时等待状态;可以在一定的时间自行返回TIMED_WAITING,/*** Thread state for a terminated thread.* The thread has completed execution.*///终止状态;当前线程执行完毕TERMINATED;
}

Thread类的构造方法

public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);
}public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);
}public Thread(ThreadGroup group, Runnable target) {init(group, target, "Thread-" + nextThreadNum(), 0);
}public Thread(String name) {init(null, null, name, 0);
}public Thread(ThreadGroup group, String name) {init(group, null, name, 0);
}public Thread(ThreadGroup group, Runnable target, String name) {init(group, target, name, 0);
}

通过几个常用的Thread类的构造方法,发现Thread类的初始化,主要是通过init()方法来实现

init()方法

/*** Initializes a Thread.** @param g the Thread group* @param target the object whose run() method gets called* @param name the name of the new Thread* @param stackSize the desired stack size for the new thread, or*        zero to indicate that this parameter is to be ignored.* @param acc the AccessControlContext to inherit, or*            AccessController.getContext() if null* @param inheritThreadLocals if {@code true}, inherit initial values for*            inheritable thread-locals from the constructing thread*/
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {if (name == null) {//名称为空throw new NullPointerException("name cannot be null");}this.name = name;Thread parent = currentThread();//安全管理器SecurityManager security = System.getSecurityManager();if (g == null) {/* Determine if it's an applet or not *//* If there is a security manager, ask the security managerwhat to do. */if (security != null) {//获取线程组g = security.getThreadGroup();}/* If the security doesn't have a strong opinion of the matteruse the parent thread group. */if (g == null) {//线程组为空,从父类获取g = parent.getThreadGroup();}}/* checkAccess regardless of whether or not threadgroup isexplicitly passed in. */g.checkAccess();/** Do we have the required permissions?*/if (security != null) {if (isCCLOverridden(getClass())) {security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);}}g.addUnstarted();//当前线程继承父线程相关属性this.group = g;this.daemon = parent.isDaemon();this.priority = parent.getPriority();if (security == null || isCCLOverridden(parent.getClass()))this.contextClassLoader = parent.getContextClassLoader();elsethis.contextClassLoader = parent.contextClassLoader;this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();this.target = target;setPriority(priority);if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize;/* Set thread ID */tid = nextThreadID();
}

Thread类的构造方法,是被创建Thread线程的主线程调用的,此时调用构造方法的主线程就是Thread的父线程;在init()方法中,新创建的Thread线程,会继承父线程的部分属性

run()方法

@Override
public void run() {if (target != null) {target.run();}
}

可以看到,Thread方法的run()方法实现比较简单,实际是由Runnable类型的target来运行run()方法实现的;

须注意的是:直接调用Runnable接口的run方法,不会创建新线程来执行任务;如果需要创建新线程执行任务,则需要调用Thread类的start()方法;

start()方法

public synchronized void start() {/*** This method is not invoked for the main method thread or "system"* group threads created/set up by the VM. Any new functionality added* to this method in the future may have to also be added to the VM.** A zero status value corresponds to state "NEW".*/if (threadStatus != 0)throw new IllegalThreadStateException();/* Notify the group that this thread is about to be started* so that it can be added to the group's list of threads* and the group's unstarted count can be decremented. */group.add(this);//标记线程是否启动boolean started = false;try {//调用本地方法启动start0();//变更线程启动标识started = true;} finally {try {if (!started) {//未启动,则线程组中标记为启动失败group.threadStartFailed(this);}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable thenit will be passed up the call stack */}}
}

从源码可看出,start()方法被synchronized的修饰,说明此方法是同步的,它会在线程实际启动前,检查线程的状态,如果不是0(NEW状态),则直接返回异常;所以一个线程只能启动一次,多次启动是会报异常的

调用start()方法后,新创建的线程就会处于就绪状态(如果没有被CPU调度),当CPU有空闲时,会被CPU调度执行,此时线程为运行状态,JVM会调用线程的run()方法来执行任务

sleep()方法

public static native void sleep(long millis) throws InterruptedException;public static void sleep(long millis, int nanos) throws InterruptedException {if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (nanos < 0 || nanos > 999999) {throw new IllegalArgumentException("nanosecond timeout value out of range");}if (nanos >= 500000 || (nanos != 0 && millis == 0)) {millis++;}sleep(millis);
}

sleep()方法会让线程休眠一段时间,需注意的是,调用sleep()方法使线程休眠后,不会释放锁

join()方法

join()方法的使用场景,通常是启动线程执行任务的线程,调用执行线程的join()方法,等待执行线程执行任务,直到超时或者执行线程终止

interrupt()方法

线程中断:

在某些情况下,我们启动线程之后,发现并不需要运行它,需要中断此线程。目前在JAVA中还没有安全直接的方法来停止线程,但是JAVA引入了线程中断机制来处理需要中断线程的情况。

线程中断机制是一种协作机制。需要注意通过中断操作,并不能直接终止线程运行,而是通知被中断的线程自行处理。

Thread类中提供了几个方法,用来处理线程中断:

  1. Thread.interrupt():中断线程。这里的中断线程不会立即终止线程运行,而是将线程中断标识设置为true.
  2. Thread.currentThread.isInterrupt():测试当前线程是否被中断。线程的中断状态受这个方法的影响,意思是调用一次使线程的中断状态设置为true,连续调用两次会使该线程中断状态重新设置为false.
  3. Thread.isInterrupt():测试当前线程是否被中断。与上面方法不同的是,此方法不会改变线程中断状态。

此方法用来中断当前线程执行,它通过设置线程的中断标志位来中断当前线程。此时,如果为线程设置了中断标志位,

可能会抛出InterruptException异常,同时会清除当前线程的中断状态。这种中断线程的方式比较安全,它能使正在执行的任务可以继续执行完成,而不像stop那样强制线程关闭。

public void interrupt() {if (this != Thread.currentThread())checkAccess();synchronized (blockerLock) {Interruptible b = blocker;if (b != null) {interrupt0();           // Just to set the interrupt flagb.interrupt(this);return;}}interrupt0();
}

过期的suspend()/resume()/stop()

suspend()/resume()/stop()三个方法,可简单理解为对线程的暂停、恢复和终止操作,由于方法已过时,不推荐使用。

Callable和Futrue

Callable接口

Callable接口可以获取线程执行后的返回结果;而继承Thread和实现Runnable接口,无法获取执行结果

@FunctionalInterface
public interface Runnable {/*** When an object implementing interface <code>Runnable</code> is used* to create a thread, starting the thread causes the object's* <code>run</code> method to be called in that separately executing* thread.* <p>* The general contract of the method <code>run</code> is that it may* take any action whatsoever.** @see     java.lang.Thread#run()*/public abstract void run();
}@FunctionalInterface
public interface Callable<V> {/*** Computes a result, or throws an exception if unable to do so.** @return computed result* @throws Exception if unable to compute a result*/V call() throws Exception;
}

可以看出,Callable接口和Runnable接口类似,同样是只有一个方法的函数式接口;不同的是Callable提供的方法有返回值,而且支持泛型。

Callable一般是配合线程次工具ExecutorService类使用,我们会在后续章节深入线程池的使用。

这里只介绍ExecutorService可以调用submit方法来执行一个Callable,并返回一个Future,后续程序可通过Future的get方法获取执行的结果。

public class TestTask implements Callable<String> {@Overridepublic String call() throws Exception {//模拟程序执行需要一秒Thread.sleep(1000);return "do success!";}public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService executorService = Executors.newCachedThreadPool();TestTask testTask = new TestTask();Future<String> submitRes = executorService.submit(testTask);//注意调用get方法会阻塞当前线程,知道得到结果//实际编码中建议使用设有超时时间的重载get方法String reslut = submitRes.get();System.out.println(reslut);}//console:// do success!
}

异步模型

  1. 无返回结果的异步模型

    无返回结果的异步任务,可直接丢进线程或线程池中运行,此时无法直接获取任务执行结果;一种方式是可以通过回调方法来获取运行结果;实现方式类似观察者模式

  2. 有返回结果的异步模型

    在JDK中提供可直接获取返回异步结果的方案:

    1. 使用Future获取结果

      使用Futrue接口往往配合线程池来获取异步结果

    2. 使用FutrueTask获取结果

      FutureTask类即可结合Thread类使用、也可配合线程池使用

Future接口

  1. Future接口

    public interface Future<V> {boolean cancel(boolean mayInterruptIfRunning);boolean isCancelled();boolean isDone();V get() throws InterruptedException, ExecutionException;V get(long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeotException;
    
    1. boolean cancel(boolean)
      1. 取消任务的执行,接收一个boolean类型参数,成功取消返回true,否者返回false.
      2. 任务已完成、已结束或不可取消,返回false,表示取消失败
      3. 若任务未启动,调用此方法返回true,表示取消成功
      4. 若任务已启动,会根据输入boolean参数决定,是否通过中断线程来取消任务运行
    2. boolean isCancelled()
      1. 判断任务在完成之前取消
      2. 若任务完成前取消,返回true,否则返回false
      3. 注意:只有在任务未启动、完成前取消,才会返回true,否则都会返回false
    3. boolean isDone()
      1. 判断任务是否已经完成
      2. 若任务正常结束、抛出异常退出、被取消,都会返回true,表示任务完成
    4. V get()
      1. 任务完成时,直接返回任务的结果数据
      2. 未完成时,等待任务完成并返回结果
    5. V get(long,TimeUnit)
      1. 任务完成时,直接返回任务完成结果
      2. 未完成时,会在超时时间内等过返回结果,超过时间,抛出TimeOutException异常
  2. RunnableFuture接口

    public interface RunnableFuture<V> extends Runnable, Future<V> {/*** Sets this Future to the result of its computation* unless it has been cancelled.*/void run();
    }
    

    RunnabeleFuture接口不但继承了Future接口,同时继承了Runnable接口,因此同时兼具两者的抽象接口

  3. FutureTask类

    public class FutureTask<V> implements RunnableFuture<V> {//详细源码,后续分析
    }
    

    FutureTask类是RunnableFuture接口非常重要的实现类,他实现了RunnableFuture接口、Future接口和Runnnable接口的所有抽象方法

    1. FutureTask类中的变量与常量

      首先定义了一个volatile修饰的变量state,volatile变量通过内存屏障和禁止重排序来实现线程安全;

      然后定义几个任务运行时的状态常量;

      (代码注释中,给出几种可能的状态变更情况)

      	/* Possible state transitions:* NEW -> COMPLETING -> NORMAL* NEW -> COMPLETING -> EXCEPTIONAL* NEW -> CANCELLED* NEW -> INTERRUPTING -> INTERRUPTED*/private volatile int state;private static final int NEW          = 0;private static final int COMPLETING   = 1;private static final int NORMAL       = 2;private static final int EXCEPTIONAL  = 3;private static final int CANCELLED    = 4;private static final int INTERRUPTING = 5;private static final int INTERRUPTED  = 6;
      

      接下来,又定义了几个成员变量;

      	/** The underlying callable; nulled out after running */private Callable<V> callable;/** The result to return or exception to throw from get() */private Object outcome; // non-volatile, protected by state reads/writes/** The thread running the callable; CASed during run() */private volatile Thread runner;/** Treiber stack of waiting threads */private volatile WaitNode waiters;
      
      1. callable: 通过调用run()方法来执行具体任务;
      2. outcaom:通过get()方法获取到的返回结果或异常信息
      3. runner:用来运行callable接口的线程,并通过CAS来保证线程安全
      4. waiters:等待线程的堆栈,派生类中,会通过CAS和此堆栈来实现运行状态的切换
    2. 构造方法

      两种不同传参的构造方法;

      public FutureTask(Callable<V> callable) {if (callable == null)throw new NullPointerException();this.callable = callable;this.state = NEW;       // ensure visibility of callable}/*** Creates a {@code FutureTask} that will, upon running, execute the* given {@code Runnable}, and arrange that {@code get} will return the* given result on successful completion.** @param runnable the runnable task* @param result the result to return on successful completion. If* you don't need a particular result, consider using* constructions of the form:* {@code Future<?> f = new FutureTask<Void>(runnable, null)}* @throws NullPointerException if the runnable is null*/public FutureTask(Runnable runnable, V result) {this.callable = Executors.callable(runnable, result);this.state = NEW;       // ensure visibility of callable}
      
    3. isCancelled()和isDone()

          public boolean isCancelled() {return state >= CANCELLED;}public boolean isDone() {return state != NEW;}
      

      这两个方法中,都是通过判断状态值的大小来判断是否已取消或者完成的;

      这里可以学习的是,今后定义状态值时,尽量遵循一定的变化规律来定义,这样对于状态频繁变更的场景,规律的状态值,进行业务逻辑时可能起到事半功倍的效果

    4. cancel(boolean)方法

      public boolean cancel(boolean mayInterruptIfRunning) {if (!(state == NEW &&UNSAFE.compareAndSwapInt(this, stateOffset, NEW,mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))return false;try {    // in case call to interrupt throws exceptionif (mayInterruptIfRunning) {try {Thread t = runner;if (t != null)t.interrupt();} finally { // final stateUNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);}}} finally {finishCompletion();}return true;}
      

      首先会根据状态判断或者CAS操作结果,来快速判断是否可取消;若状态不为NEW或CAS返回false,则直接返回取消失败;

      然后在try代码块中,先判断是否可中断来取消;若可以,则定义一个引用指向运行的任务,并判断任务是否为空,不为空则调用中断方法,然后修改运行状态为取消;

      最后调用finally代码块中的finishCompletion()方法来结束任务运行;

      /*** Removes and signals all waiting threads, invokes done(), and* nulls out callable.*/private void finishCompletion() {// assert state > COMPLETING;for (WaitNode q; (q = waiters) != null;) {if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {for (;;) {Thread t = q.thread;if (t != null) {q.thread = null;LockSupport.unpark(t);}WaitNode next = q.next;if (next == null)break;q.next = null; // unlink to help gcq = next;}break;}}done();callable = null;        // to reduce footprint}
      

      在finishCompletion()方法中,先定义了一个for循环,循环waiters(线程等待堆栈),循环终止条件为waiters为空;具体循环内部,先判断CAS操作是否成功,若成功,则定义新一自旋循环;在自旋循环中,会唤醒堆栈中的线程,使其运行完成,完成之后break跳出循环,最后调用done()方法并置空callable;

    5. get()方法

      public V get() throws InterruptedException, ExecutionException {int s = state;if (s <= COMPLETING)s = awaitDone(false, 0L);return report(s);}public V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException {if (unit == null)throw new NullPointerException();int s = state;if (s <= COMPLETING &&(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)throw new TimeoutException();return report(s);}
      

      无参的get()方法,在任务未运行完成时,会阻塞等待任务完成;有参的get()方法,会阻塞等待完成,但超过给定时长后,会抛出TimeoutException异常;

      /*** Awaits completion or aborts on interrupt or timeout.** @param timed true if use timed waits* @param nanos time to wait, if timed* @return state upon completion*/private int awaitDone(boolean timed, long nanos)throws InterruptedException {final long deadline = timed ? System.nanoTime() + nanos : 0L;WaitNode q = null;boolean queued = false;for (;;) {if (Thread.interrupted()) {removeWaiter(q);throw new InterruptedException();}int s = state;if (s > COMPLETING) {if (q != null)q.thread = null;return s;}else if (s == COMPLETING) // cannot time out yetThread.yield();else if (q == null)q = new WaitNode();else if (!queued)queued = UNSAFE.compareAndSwapObject(this, waitersOffset,q.next = waiters, q);else if (timed) {nanos = deadline - System.nanoTime();if (nanos <= 0L) {removeWaiter(q);return state;}LockSupport.parkNanos(this, nanos);}elseLockSupport.park(this);}}
      

      awaitDone()方法主要就是等待执行的完成或中断;

      其中最重要的就是for自旋循环,循环中会先判断是否中断,若中断,则调用removeWaiter()移除等待堆栈,抛出中断异常;若未中断,则往下执行判断是否完成逻辑;

    6. set()和setException()

      protected void set(V v) {if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {outcome = v;UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final statefinishCompletion();}}protected void setException(Throwable t) {if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {outcome = t;UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL);finishCompletion();}}
      

      两个方法逻辑几乎相同,只是一个在设定任务状态时设置为NORMAL,一个设置为EXCEPTIONAL;

    7. run()和runAndReset()

      public void run() {if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))return;try {Callable<V> c = callable;if (c != null && state == NEW) {V result;boolean ran;try {result = c.call();ran = true;} catch (Throwable ex) {result = null;ran = false;setException(ex);}if (ran)set(result);}} finally {// runner must be non-null until state is settled to// prevent concurrent calls to run()runner = null;// state must be re-read after nulling runner to prevent// leaked interruptsint s = state;if (s >= INTERRUPTING)handlePossibleCancellationInterrupt(s);}}
      

      可以说使用使用了Future或FutureTask(),就必然会调用run()方法来运行任务;

      run()方法中,会先判断是否为NEW状态或者CAS操作返回false,将直接返回,不再继续执行;

      接下来try代码块中,则是执行callable的call()方法来运行,并接收结果;

    8. removeWaiter()方法

      private void removeWaiter(WaitNode node) {if (node != null) {node.thread = null;retry:for (;;) {          // restart on removeWaiter racefor (WaitNode pred = null, q = waiters, s; q != null; q = s){s = q.next;if (q.thread != null)pred = q;else if (pred != null) {pred.next = s;if (pred.thread == null) // check for racecontinue retry;}else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,q, s))continue retry;}break;}}}
      

      此方法主要是通过自旋循环的方式,移除WaitNode(等待堆栈)中的线程;

深度解析ThreadPoolExecutor

Java的线程次技术是Java最核心的技术之一,在Java高并发领域中,是一个永远绕不开的话题

Thread直接创建线程的不足

  1. 每次new Thread()都新建线程,无复用,性能差;
  2. 线程缺乏统一管理,可能无限制的新建线程,可能占用大量资源导致OOM或者死机;
  3. 缺少更多的管控操作,如更多执行、定期执行、中断等;

使用线程池的好处

  1. 可复用已有的线程,减少新建线程带来的开销,提升性能;
  2. 可有效控制最大并发数,提升资源利用率,同时减少过多线程导致的资源竞争;
  3. 提供定时执行、定期执行、单线程、并发数控制等功能;
  4. 提供支持线程池监控的方法,可实时监控线程池运行情况;

线程池

  1. Executors
    1. newCachedThreadPool:创建一个可缓冲的线程池,若线程池的大小超出了处理需要,可以灵活回收空闲线程,若无线程可回收,则新建线程;
    2. newFixedThreadPool:创建一个定长的线程池,可以控制最大线程并发数,超过定长的线程将在队列中等待;
    3. newScheduledThreadPool:创建一个定长的线程池,可以定时、定期性的执行;
    4. newSingleThreadPool:创建一个单线程化的线程池,使用唯一的线程执行线程任务,可保证所有任务按照指定顺序执行;
    5. newSingleScheduleThreadPool:创建一个单线程化的线程池,可定时、定期性的执行;
    6. newWorkStealingThreadPool:创建一个具有并行级别的work-stealing线程池;

线程池实例的几种状态

  1. Running

    运行状态,能接受新提交的任务,也能处理阻塞队列中的任务

  2. Shutdown

    关闭状态,不在接收新提交任务,但可以处理阻塞队列中已保存的任务;

    Running --> shutdown() --> Shutdown

  3. Stop

    停止状态,不能接收新任务,也不能处理阻塞队列中的任务,会中断正在处理中的任务;

    Running/Shutdown --> shutdownNow() --> Stop

  4. Tidying

    清空状态,所有的任务都已经终止,有效线程数为0(阻塞队列中的线程数为0,线程池中执行的线程数也为0)

  5. Terminated

    结束状态,Tidying --> terminate() --> Terminated

注意:无须对线程池状态做特殊处理,线程池状态是线程池内部根据方法自行定义和处理的

合理配置线程数的建议

  1. CPU密集型任务,需要压榨cpu,线程数可设置为ncpu+1(cpu数量+1)
  2. IO密集型任务,数量可设置为ncpu*2(2倍的cpu数量)

线程池核心类之ThreadPoolExecutor

  1. 构造方法

    /*** Creates a new {@code ThreadPoolExecutor} with the given initial* parameters and default thread factory and rejected execution handler.* It may be more convenient to use one of the {@link Executors} factory* methods instead of this general purpose constructor.** @param corePoolSize the number of threads to keep in the pool, even*        if they are idle, unless {@code allowCoreThreadTimeOut} is set* @param maximumPoolSize the maximum number of threads to allow in the*        pool* @param keepAliveTime when the number of threads is greater than*        the core, this is the maximum time that excess idle threads*        will wait for new tasks before terminating.* @param unit the time unit for the {@code keepAliveTime} argument* @param workQueue the queue to use for holding tasks before they are*        executed.  This queue will hold only the {@code Runnable}*        tasks submitted by the {@code execute} method.* @throws IllegalArgumentException if one of the following holds:<br>*         {@code corePoolSize < 0}<br>*         {@code keepAliveTime < 0}<br>*         {@code maximumPoolSize <= 0}<br>*         {@code maximumPoolSize < corePoolSize}* @throws NullPointerException if {@code workQueue} is null*/public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);}
    

    此为参数最多的构造方法,其他构造都以此重载

    1. corePoolSize:核心线程数量
    2. maximumPoolSize:最大线程数量
    3. workQueue:阻塞队列,存储等待执行的任务

    上述三个参数关系如下:

    • 若运行的线程数小于corePoolSize,直接新建线程运行;即使线程池中的其他线程是空闲的
    • 若运行的线程数大于等于corePoolSize,且小于maximumPoolSize,此时多出的数量,会进入workQueue等待,只有workQueue满时,才会新建线程
    • 若corePoolSize等于maximumPoolSize,那此时线程池大小固定,若有新任务提交,若workQueue未满,则进入workQueue,等待有空闲线程时,从workQueue取出执行
    • 若运行的线程数超过了maximumPoolSize,且workQueue已满,则会通过拒绝策略rejectHandler来处理策略

    基于上述参数,线程次会对任务进行如下处理:

    当提交一个新任务到线程池,线程池会根据当前运行的线程数,来执行不同的处理方式;主要有三种处理方式:直接切换、使用无界队列、使用有界队列

    • 直接切换常使用的队列就是SynchronousQueue
    • 使用无限队列,就是使用基于链表的队列。例如,LinkedBlockingQueue,使用此队列时,线程池中创建的最大线程数就是corePoolSize,maximumPoolSize将不会起作用
    • 使用有界队列,就是使用基于数组的队列。例如,ArrayBlockingQueue,使用这种方式,可将线程池最大线程数限制为maximumPoolSize,可降低资源的消耗;但这种方式使得线程次对线程的调度更困难了,因为线程池数和队列数都是固定的了

    根据上述参数,可以简单得出降低资源消耗的一些措施:

    • 若想降低系统资源消耗、上下文切换开销等,可以设置一个较大的队列容量和较小的线程池容量,这样会降低线程处理任务的吞吐量
    • 若提交的任务经常阻塞,可以重新设置较大的线程次最大线程数
    1. keepAliveTime:线程没有执行任务时,最多等待多久时间终止

      当线程池中的线程数,超过corePoolSize时,若没有新任务提交,超出核心线程数的线程,不会立即销毁,会在等待keepAliveTime时间后销毁

    2. unit:keepAliveTime的时间单位

    3. threadFactory:线程工厂,用来创建线程

      缺省时会默认创建一个线程工厂,默认的工厂新建的线程具有相同的优先级,且是非守护的,同时也设置了线程名称

    4. rejectHandler:拒绝策略

      若workQueue满了,且没有空闲线程,则会执行拒绝策略

      线程池共提供了四种拒绝策略:

      • 直接抛出异常,这也是默认的策略。实现类为AbortPolicy
      • 用调用者所在线程来执行,实现类为CallerRunsPolicy
      • 丢弃队列中最靠前的任务,并执行当前任务,实现类为DiscardOldestPolicy
      • 直接丢弃当前任务,实现类为DiscardPolicy
  2. 启动和停止的方法

    1. execute():提交任务,交给线程池执行
    2. submit():提交任务,可以返回结果,相当于execute+Future
    3. shutDown():关闭线程池,等待线程执行完毕
    4. shutDownNow():立即关闭线程池,不等待线程执行完毕
  3. 适用于监控的方法

    1. getTaskCount():获取线程已执行和未执行的总数
    2. getCompletedTaskCount():获取已执行完成的线程数
    3. getCorePoolSize():获取核心线程数
    4. getActiveCount():或取运行中线程数

深度解析线程池中顶层接口和抽象类

接口和抽象类总览

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mx45ZeS5-1670044760209)(C:\Users\lixuewen\AppData\Roaming\Typora\typora-user-images\image-20221031170640687.png)]

  • Executor接口:线程池的最顶层接口,提供了一个无返回值的提交任务方法
  • ExecutorService:继承自Executor,扩展了很多功能,例如关闭线程池、提交任务并返回结果、唤醒线程池中的任务等
  • AbstractExecutotService:继承自ExecutorService,实现了一些非常实用的方法,供子类调用
  • ScheduledExecutorService:继承自ExecutorService,扩展了定时任务相关的方法

Executor接口

public interface Executor {/*** Executes the given command at some time in the future.  The command* may execute in a new thread, in a pooled thread, or in the calling* thread, at the discretion of the {@code Executor} implementation.** @param command the runnable task* @throws RejectedExecutionException if this task cannot be* accepted for execution* @throws NullPointerException if command is null*/void execute(Runnable command);
}

Executor接口比较简单,提供了一个执行提交任务的方法execute(Runnable command)

ExecutorService接口

ExecutorService接口是非定时任务类线程池的核心接口,通过此接口执行向线程池提交任务(支支持有返回和无返回两种方式)、关闭线程池、唤醒线程任务等。

public interface ExecutorService extends Executor {/*** Initiates an orderly shutdown in which previously submitted* tasks are executed, but no new tasks will be accepted.* Invocation has no additional effect if already shut down.** <p>This method does not wait for previously submitted tasks to* complete execution.  Use {@link #awaitTermination awaitTermination}* to do that.** @throws SecurityException if a security manager exists and*         shutting down this ExecutorService may manipulate*         threads that the caller is not permitted to modify*         because it does not hold {@link*         java.lang.RuntimePermission}{@code ("modifyThread")},*         or the security manager's {@code checkAccess} method*         denies access.*///关闭线程池,不再接受新提交的任务,但之前提交的任务继续执行,直到完成void shutdown();/*** Attempts to stop all actively executing tasks, halts the* processing of waiting tasks, and returns a list of the tasks* that were awaiting execution.** <p>This method does not wait for actively executing tasks to* terminate.  Use {@link #awaitTermination awaitTermination} to* do that.** <p>There are no guarantees beyond best-effort attempts to stop* processing actively executing tasks.  For example, typical* implementations will cancel via {@link Thread#interrupt}, so any* task that fails to respond to interrupts may never terminate.** @return list of tasks that never commenced execution* @throws SecurityException if a security manager exists and*         shutting down this ExecutorService may manipulate*         threads that the caller is not permitted to modify*         because it does not hold {@link*         java.lang.RuntimePermission}{@code ("modifyThread")},*         or the security manager's {@code checkAccess} method*         denies access.*///立即关闭线程池,不再接受新提交任务,会尝试停止线程池中正在执行的任务List<Runnable> shutdownNow();/*** Returns {@code true} if this executor has been shut down.** @return {@code true} if this executor has been shut down*///判断线程池是否已经关闭boolean isShutdown();/*** Returns {@code true} if all tasks have completed following shut down.* Note that {@code isTerminated} is never {@code true} unless* either {@code shutdown} or {@code shutdownNow} was called first.** @return {@code true} if all tasks have completed following shut down*///判断线程中所有任务是否已经结束,只有调用shutdown()或shutdownNow()之后,调用此方法才返回trueboolean isTerminated();/*** Blocks until all tasks have completed execution after a shutdown* request, or the timeout occurs, or the current thread is* interrupted, whichever happens first.** @param timeout the maximum time to wait* @param unit the time unit of the timeout argument* @return {@code true} if this executor terminated and*         {@code false} if the timeout elapsed before termination* @throws InterruptedException if interrupted while waiting*///等待线程中所有任务执行结束,并设置超时时间boolean awaitTermination(long timeout, TimeUnit unit)throws InterruptedException;/*** Submits a value-returning task for execution and returns a* Future representing the pending results of the task. The* Future's {@code get} method will return the task's result upon* successful completion.** <p>* If you would like to immediately block waiting* for a task, you can use constructions of the form* {@code result = exec.submit(aCallable).get();}** <p>Note: The {@link Executors} class includes a set of methods* that can convert some other common closure-like objects,* for example, {@link java.security.PrivilegedAction} to* {@link Callable} form so they can be submitted.** @param task the task to submit* @param <T> the type of the task's result* @return a Future representing pending completion of the task* @throws RejectedExecutionException if the task cannot be*         scheduled for execution* @throws NullPointerException if the task is null*///提交一个Callable类型的任务,并返回一个Future类型的结果<T> Future<T> submit(Callable<T> task);/*** Submits a Runnable task for execution and returns a Future* representing that task. The Future's {@code get} method will* return the given result upon successful completion.** @param task the task to submit* @param result the result to return* @param <T> the type of the result* @return a Future representing pending completion of the task* @throws RejectedExecutionException if the task cannot be*         scheduled for execution* @throws NullPointerException if the task is null*///提交一个Runnable类型任务,并设置泛型接收结果数据,返回一个Futrue类型结果<T> Future<T> submit(Runnable task, T result);/*** Submits a Runnable task for execution and returns a Future* representing that task. The Future's {@code get} method will* return {@code null} upon <em>successful</em> completion.** @param task the task to submit* @return a Future representing pending completion of the task* @throws RejectedExecutionException if the task cannot be*         scheduled for execution* @throws NullPointerException if the task is null*///提交一个Runnable类型任务,并返回一个Future类型结果Future<?> submit(Runnable task);/*** Executes the given tasks, returning a list of Futures holding* their status and results when all complete.* {@link Future#isDone} is {@code true} for each* element of the returned list.* Note that a <em>completed</em> task could have* terminated either normally or by throwing an exception.* The results of this method are undefined if the given* collection is modified while this operation is in progress.** @param tasks the collection of tasks* @param <T> the type of the values returned from the tasks* @return a list of Futures representing the tasks, in the same*         sequential order as produced by the iterator for the*         given task list, each of which has completed* @throws InterruptedException if interrupted while waiting, in*         which case unfinished tasks are cancelled* @throws NullPointerException if tasks or any of its elements are {@code null}* @throws RejectedExecutionException if any task cannot be*         scheduled for execution*///批量提交Callable类型任务,并返回他们的执行结果,Task列表和Future列表一一对应<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)throws InterruptedException;/*** Executes the given tasks, returning a list of Futures holding* their status and results* when all complete or the timeout expires, whichever happens first.* {@link Future#isDone} is {@code true} for each* element of the returned list.* Upon return, tasks that have not completed are cancelled.* Note that a <em>completed</em> task could have* terminated either normally or by throwing an exception.* The results of this method are undefined if the given* collection is modified while this operation is in progress.** @param tasks the collection of tasks* @param timeout the maximum time to wait* @param unit the time unit of the timeout argument* @param <T> the type of the values returned from the tasks* @return a list of Futures representing the tasks, in the same*         sequential order as produced by the iterator for the*         given task list. If the operation did not time out,*         each task will have completed. If it did time out, some*         of these tasks will not have completed.* @throws InterruptedException if interrupted while waiting, in*         which case unfinished tasks are cancelled* @throws NullPointerException if tasks, any of its elements, or*         unit are {@code null}* @throws RejectedExecutionException if any task cannot be scheduled*         for execution*///批量提交Callable类型任务,并获取返回结果,并限定处理所有任务的时间<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException;/*** Executes the given tasks, returning the result* of one that has completed successfully (i.e., without throwing* an exception), if any do. Upon normal or exceptional return,* tasks that have not completed are cancelled.* The results of this method are undefined if the given* collection is modified while this operation is in progress.** @param tasks the collection of tasks* @param <T> the type of the values returned from the tasks* @return the result returned by one of the tasks* @throws InterruptedException if interrupted while waiting* @throws NullPointerException if tasks or any element task*         subject to execution is {@code null}* @throws IllegalArgumentException if tasks is empty* @throws ExecutionException if no task successfully completes* @throws RejectedExecutionException if tasks cannot be scheduled*         for execution*///批量提交任务,并获得一个已经成功执行任务的结果<T> T invokeAny(Collection<? extends Callable<T>> tasks)throws InterruptedException, ExecutionException;/*** Executes the given tasks, returning the result* of one that has completed successfully (i.e., without throwing* an exception), if any do before the given timeout elapses.* Upon normal or exceptional return, tasks that have not* completed are cancelled.* The results of this method are undefined if the given* collection is modified while this operation is in progress.** @param tasks the collection of tasks* @param timeout the maximum time to wait* @param unit the time unit of the timeout argument* @param <T> the type of the values returned from the tasks* @return the result returned by one of the tasks* @throws InterruptedException if interrupted while waiting* @throws NullPointerException if tasks, or unit, or any element*         task subject to execution is {@code null}* @throws TimeoutException if the given timeout elapses before*         any task successfully completes* @throws ExecutionException if no task successfully completes* @throws RejectedExecutionException if tasks cannot be scheduled*         for execution*///批量提交任务,并获得一个已经完成任务的结果,并限定处理任务时间<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}

AbstractExecutorEservice

​ 此类是一个抽象类,继承自ExecutorEservice,并在此基础上,实现了一些实用方法,供子类调用。

  1. newTaskFor()

    /*** Returns a {@code RunnableFuture} for the given runnable and default* value.** @param runnable the runnable task being wrapped* @param value the default value for the returned future* @param <T> the type of the given value* @return a {@code RunnableFuture} which, when run, will run the* underlying runnable and which, as a {@code Future}, will yield* the given value as its result and provide for cancellation of* the underlying task* @since 1.6*/protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {return new FutureTask<T>(runnable, value);}/*** Returns a {@code RunnableFuture} for the given callable task.** @param callable the callable task being wrapped* @param <T> the type of the callable's result* @return a {@code RunnableFuture} which, when run, will call the* underlying callable and which, as a {@code Future}, will yield* the callable's result as its result and provide for* cancellation of the underlying task* @since 1.6*/protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {return new FutureTask<T>(callable);}
    

    FutureTask用来获取运行结果,实际应用中,我们经常使用的是它的子类FutureTask;

    newTaskFor方法的作用,就是将任务封装成FutureTask对象,后续将FutureTask对象提交到线程池

  2. doInvokeAny()

    此方法是批量执行线程池任务,最终返回一个结果数据的核心方法;此方法只要获取到其中一个线程的结果,就会取消线程池中其他运行的线程;

  3. invokeAny()

    此方法内部仍是调用doInvokeAny()方法,提交批量线程,其中有一个完成返回结果,其余的线程都取消运行;

  4. invokeAll()

    invokeAll()方法实现了有超时设置和无超时设置的逻辑;

    无超时设置的方法逻辑是:将提交的批量任务,都封装成RunnableFuture对象,然后调用execute()方法执行任务,并将结果Future添加到Future集合中,之后会对Future集合进行遍历,判断任务是执行完成;若未完成,则会调用get方法阻塞,直到获取结果,此时会忽略异常;最后在finally中,判断所有任务的完成标识,若未完成,则取消执行;

  5. submit()

    此方法较简单,就是将任务封装成RunnableFuture对象,调用execute()执行完成后,返回Future结果

ScheduleExecutorService

​ 此接口继承自ExecutorService,除了继承父类的方法外,还提供了定时处理任务的功能

源码角度分析创建线程池方式

​ Executors.newWorkStealingPool:此方法是Java8中新增的创建线程池的方法,它能够为线程设置并行级别,并拥有更高的并发度和性能;除此之外,其他的创建线程池的方法,都是调用的ThreadPoolExecutor的构造方法;

ThreadPoolExecutor创建线程池

/*** Creates a new {@code ThreadPoolExecutor} with the given initial* parameters.** @param corePoolSize the number of threads to keep in the pool, even*        if they are idle, unless {@code allowCoreThreadTimeOut} is set* @param maximumPoolSize the maximum number of threads to allow in the*        pool* @param keepAliveTime when the number of threads is greater than*        the core, this is the maximum time that excess idle threads*        will wait for new tasks before terminating.* @param unit the time unit for the {@code keepAliveTime} argument* @param workQueue the queue to use for holding tasks before they are*        executed.  This queue will hold only the {@code Runnable}*        tasks submitted by the {@code execute} method.* @param threadFactory the factory to use when the executor*        creates a new thread* @param handler the handler to use when execution is blocked*        because the thread bounds and queue capacities are reached* @throws IllegalArgumentException if one of the following holds:<br>*         {@code corePoolSize < 0}<br>*         {@code keepAliveTime < 0}<br>*         {@code maximumPoolSize <= 0}<br>*         {@code maximumPoolSize < corePoolSize}* @throws NullPointerException if {@code workQueue}*         or {@code threadFactory} or {@code handler} is null*/public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.acc = System.getSecurityManager() == null ?null :AccessController.getContext();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}

通过查看ThreadPoolExecutor类的源码,发现最终都通过调用异常构造方法来构造线程次,其他初始化参数前面已经介绍过;

ForkJoinPool类创建线程池

public static ExecutorService newWorkStealingPool(int parallelism) {return new ForkJoinPool(parallelism,ForkJoinPool.defaultForkJoinWorkerThreadFactory,null, true);}/*** Creates a work-stealing thread pool using all* {@link Runtime#availableProcessors available processors}* as its target parallelism level.* @return the newly created thread pool* @see #newWorkStealingPool(int)* @since 1.8*/public static ExecutorService newWorkStealingPool() {return new ForkJoinPool(Runtime.getRuntime().availableProcessors(),ForkJoinPool.defaultForkJoinWorkerThreadFactory,null, true);}

从上述源码可以看出,Executors.newWorkStealingPool()方法,构造线程池时,实际是调用ForkJoinPool来构造的线程池;

/*** Creates a {@code ForkJoinPool} with the given parameters, without* any security checks or parameter validation.  Invoked directly by* makeCommonPool.*/private ForkJoinPool(int parallelism,ForkJoinWorkerThreadFactory factory,UncaughtExceptionHandler handler,int mode,String workerNamePrefix) {this.workerNamePrefix = workerNamePrefix;this.factory = factory;this.ueh = handler;this.config = (parallelism & SMASK) | mode;long np = (long)(-parallelism); // offset ctl countsthis.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);}

查看源码得知,ForkJoinPool各种构造方法,最终调用的是上述的私有构造方法;

其中各初始化参数如下:

  • parallelism:并发级别
  • factory:创建线程的工厂
  • handler:当线程次中的线程抛出未捕获的异常时,通过此UncaughtExceptionHandler进行处理
  • mode:取值表示FIFO_QUEUE或LIFO_QUEUE
  • workerNamePrefix:执行任务的线程名称前缀

ScheduledThreadPoolExecutor创建线程池

/*** Creates a new ScheduledThreadPoolExecutor with the given* initial parameters.** @param corePoolSize the number of threads to keep in the pool, even*        if they are idle, unless {@code allowCoreThreadTimeOut} is set* @param threadFactory the factory to use when the executor*        creates a new thread* @param handler the handler to use when execution is blocked*        because the thread bounds and queue capacities are reached* @throws IllegalArgumentException if {@code corePoolSize < 0}* @throws NullPointerException if {@code threadFactory} or*         {@code handler} is null*/public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory,RejectedExecutionHandler handler) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue(), threadFactory, handler);}

​ ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,查看其源码可知,其构造方法本质还是调用ThreadPoolExecutor的构造方法,只不过队列传递的为DelayedWorkQueue。

源码解析ThreadPoolExecutor如何正确运行

ThreadPoolExecutor中的重要属性

  • ctl相关的属性

    AomaticInteger类型的常量ctl是贯穿线程池整个生命周期

    	//主要用来保存线程状态和线程数量,前3位保存线程状态,低29位报存线程数量private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));//线程池中线程数量(32-3)private static final int COUNT_BITS = Integer.SIZE - 3;//线程池中的最大线程数量private static final int CAPACITY   = (1 << COUNT_BITS) - 1;// runState is stored in the high-order bits//线程池的运行状态private static final int RUNNING    = -1 << COUNT_BITS;private static final int SHUTDOWN   =  0 << COUNT_BITS;private static final int STOP       =  1 << COUNT_BITS;private static final int TIDYING    =  2 << COUNT_BITS;private static final int TERMINATED =  3 << COUNT_BITS;// Packing and unpacking ctl//获取线程状态private static int runStateOf(int c)     { return c & ~CAPACITY; }//获取线程数量private static int workerCountOf(int c)  { return c & CAPACITY; }private static int ctlOf(int rs, int wc) { return rs | wc; }/** Bit field accessors that don't require unpacking ctl.* These depend on the bit layout and on workerCount being never negative.*/private static boolean runStateLessThan(int c, int s) {return c < s;}private static boolean runStateAtLeast(int c, int s) {return c >= s;}private static boolean isRunning(int c) {return c < SHUTDOWN;}/*** Attempts to CAS-increment the workerCount field of ctl.*/private boolean compareAndIncrementWorkerCount(int expect) {return ctl.compareAndSet(expect, expect + 1);}/*** Attempts to CAS-decrement the workerCount field of ctl.*/private boolean compareAndDecrementWorkerCount(int expect) {return ctl.compareAndSet(expect, expect - 1);}/*** Decrements the workerCount field of ctl. This is called only on* abrupt termination of a thread (see processWorkerExit). Other* decrements are performed within getTask.*/private void decrementWorkerCount() {do {} while (! compareAndDecrementWorkerCount(ctl.get()));}
    
  • 其他重要属性

    	//用于存放任务的阻塞队列private final BlockingQueue<Runnable> workQueue;/*** Lock held on access to workers set and related bookkeeping.* While we could use a concurrent set of some sort, it turns out* to be generally preferable to use a lock. Among the reasons is* that this serializes interruptIdleWorkers, which avoids* unnecessary interrupt storms, especially during shutdown.* Otherwise exiting threads would concurrently interrupt those* that have not yet interrupted. It also simplifies some of the* associated statistics bookkeeping of largestPoolSize etc. We* also hold mainLock on shutdown and shutdownNow, for the sake of* ensuring workers set is stable while separately checking* permission to interrupt and actually interrupting.*///可重入锁private final ReentrantLock mainLock = new ReentrantLock();/*** Set containing all worker threads in pool. Accessed only when* holding mainLock.*///存放线程池中线程的集合,访问这个集合时,必须先获得mainLock锁private final HashSet<Worker> workers = new HashSet<Worker>();/*** Wait condition to support awaitTermination*///在锁内部阻塞等待条件完成private final Condition termination = mainLock.newCondition();//线程工厂,以此来创建新线程private volatile ThreadFactory threadFactory;/*** Handler called when saturated or shutdown in execute.*///拒绝策略private volatile RejectedExecutionHandler handler;/*** The default rejected execution handler*///默认的拒绝策略private static final RejectedExecutionHandler defaultHandler =new AbortPolicy();
    

ThreadPoolExecutor中的重要内部类

  • Worker

    private final class Worker extends AbstractQueuedSynchronizer implements Runnable{/*** This class will never be serialized, but we provide a* serialVersionUID to suppress a javac warning.*/private static final long serialVersionUID = 6138294804551838833L;/** Thread this worker is running in.  Null if factory fails. */final Thread thread;/** Initial task to run.  Possibly null. */Runnable firstTask;/** Per-thread task counter */volatile long completedTasks;/*** Creates with given first task and thread from ThreadFactory.* @param firstTask the first task (null if none)*/Worker(Runnable firstTask) {setState(-1); // inhibit interrupts until runWorkerthis.firstTask = firstTask;this.thread = getThreadFactory().newThread(this);}/** Delegates main run loop to outer runWorker  */public void run() {runWorker(this);}// Lock methods//// The value 0 represents the unlocked state.// The value 1 represents the locked state.protected boolean isHeldExclusively() {return getState() != 0;}protected boolean tryAcquire(int unused) {if (compareAndSetState(0, 1)) {setExclusiveOwnerThread(Thread.currentThread());return true;}return false;}protected boolean tryRelease(int unused) {setExclusiveOwnerThread(null);setState(0);return true;}public void lock()        { acquire(1); }public boolean tryLock()  { return tryAcquire(1); }public void unlock()      { release(1); }public boolean isLocked() { return isHeldExclusively(); }void interruptIfStarted() {Thread t;if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {try {t.interrupt();} catch (SecurityException ignore) {}}}}
    

    ​ Work类实现了Runnable接口,需要重写run()方法,而Worker的run方法本质是调用ThreadPoolExecutor的runWorker()方法

  • 拒绝策略

    在线程池中,如果WorkQueue队列满了,且没有空闲线程,当再提交新任务时,将会执行拒绝策略;

    而线程池总共提供了四种拒绝策略:

    • 直接抛出异常,也是默认的策略;实现类为AbortPolicy
    • 用调用者线程来执行任务;实现类为CallerRunsPolicy
    • 丢弃队列中最靠前的任务,并执行当前任务;DiscardOldestPolicy
    • 直接丢弃当前任务;实现类为DiscardPolicy

    在ThreadPoolExecutor中,提供了四个内部类来实现对应的策略;

    public static class CallerRunsPolicy implements RejectedExecutionHandler {/*** Creates a {@code CallerRunsPolicy}.*/public CallerRunsPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {r.run();}}}/*** A handler for rejected tasks that throws a* {@code RejectedExecutionException}.*/public static class AbortPolicy implements RejectedExecutionHandler {/*** Creates an {@code AbortPolicy}.*/public AbortPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {throw new RejectedExecutionException("Task " + r.toString() +" rejected from " +e.toString());}}/*** A handler for rejected tasks that silently discards the* rejected task.*/public static class DiscardPolicy implements RejectedExecutionHandler {/*** Creates a {@code DiscardPolicy}.*/public DiscardPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}}/*** A handler for rejected tasks that discards the oldest unhandled* request and then retries {@code execute}, unless the executor* is shut down, in which case the task is discarded.*/public static class DiscardOldestPolicy implements RejectedExecutionHandler {/*** Creates a {@code DiscardOldestPolicy} for the given executor.*/public DiscardOldestPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {e.getQueue().poll();e.execute(r);}}}
    

    我们也可以通过实现RejectedExecutionHandler接口,并重写rejectedExecution()方法来自定义拒绝策略;

    在创建线程次时,通过ThreadPoolExecutor的构造方法,传入我们自定义的拒绝策略;

源码解析ThreadPoolExecutor核心流程

ThreadPoolExecutor中存在一个workers工作线程集合,用户可以向线程池中添加任务,workers集合中的线程可以直接执行任务,或者从任务队列中获取任务后执行;

ThreadPoolExecutor提供了线程池从创建、执行任务、到消亡的整个流程;

ThreadPoolExecutor中,线程池的逻辑主要体现在execute(Runnable command),addWorker(Runnable firstTask, boolean core),addWorkerFailed(Worker w)等方法和拒绝策略上;接下来深入分析这个几个核心方法

execute(Runnable command)

此方法的作用是提交Runnable类型的任务到线程池中;

public void execute(Runnable command) {if (command == null)//若提交的任务为空,则提交空指针异常throw new NullPointerException();//获取线程池的状态,和线程池中线程数量int c = ctl.get();//若线程池中线程数小于核心线程数if (workerCountOf(c) < corePoolSize) {//重新开启线程执行任务if (addWorker(command, true))return;c = ctl.get();}//若线程池处于RUNNING状态,则将任务添加到阻塞队列中//只有线程池处于RUNNING状态时,才能添加到队列if (isRunning(c) && workQueue.offer(command)) {//再次获取线程次状态和线程池数量,用于二次检查//向队列中添加线程成功,但由于其他线程可能会修改线程池状态,所以这里需要进行二次检查int recheck = ctl.get();//如果线程池没有再处于RUNNING状态,则从队列中删除任务if (! isRunning(recheck) && remove(command))//执行拒绝策略reject(command);else if (workerCountOf(recheck) == 0)//若线程池为空,则新建一个线程加入addWorker(null, false);}else if (!addWorker(command, false))//任务队列已满,则新建一个Worker线程,若新增失败,则执行拒绝策略reject(command);}

addWorker(Runnable firstTask, boolean core)

此方法总体上可分为三部分,第一部分主要是CAS安全向线程池中添加工作线程;第二部分是添加新的工作线程;第三部分则是通过安全的并发方式,将线程添加到workers中,并启动线程执行任务

private boolean addWorker(Runnable firstTask, boolean core) {//循环标签,重试的标识retry:for (;;) {int c = ctl.get();int rs = runStateOf(c);// Check if queue empty only if necessary.//检查队列是否在某些特定条件下为空if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;//此循环中主要是通过CAS的方式增加线程个数for (;;) {int wc = workerCountOf(c);if (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))return false;//CAS的方式增加线程数量if (compareAndIncrementWorkerCount(c))break retry;c = ctl.get();  // Re-read ctlif (runStateOf(c) != rs)continue retry;// else CAS failed due to workerCount change; retry inner loop}}//跳出最外层循环,说明已通过CAS增加线程成功//此时创建新的线程boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {//将新建线程封装成Wokerw = new Worker(firstTask);final Thread t = w.thread;if (t != null) {//独占锁,保证操作workers的同步final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// Recheck while holding lock.// Back out on ThreadFactory failure or if// shut down before lock acquired.int rs = runStateOf(ctl.get());if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {if (t.isAlive()) // precheck that t is startablethrow new IllegalThreadStateException();//将Worker加入队列workers.add(w);int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;}} finally {//释放独占锁mainLock.unlock();}if (workerAdded) {t.start();workerStarted = true;}}} finally {if (! workerStarted)addWorkerFailed(w);}return workerStarted;
}

addWorkerFailed(Worker w)

在addWorker(Runnable firstTask, boolean core)方法中,若添加工作线程失败,或工作线程启动失败,则调用此addWorkerFailed(Worker w)方法;此方法业务较简答,获取独占锁,从workers中移除任务,并通过CAS将任务数量减一,最后释放锁;

private void addWorkerFailed(Worker w) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {if (w != null)workers.remove(w);decrementWorkerCount();tryTerminate();} finally {mainLock.unlock();}
}
  • 拒绝策略

    /*** Invokes the rejected execution handler for the given command.* Package-protected for use by ScheduledThreadPoolExecutor.*/
    final void reject(Runnable command) {handler.rejectedExecution(command, this);
    }
    

    RejectExecutionHandler的四种实现类,正是线程次提供的四种拒绝策略的实现类;

    此方法具体哪种策略,是根据创建线程池时,传入的参数来决定的;缺省的话则使用默认的拒绝策略;

源码解析线程池中Woker的执行流程

Woker类分析

Woker从类结构上看,继承了AQS(AbstractQueueSynchronizer)类,并实现了Runnable接口;本质上Woker类即是一个同步组件,也是一个执行任务的线程;

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
{/*** This class will never be serialized, but we provide a* serialVersionUID to suppress a javac warning.*/private static final long serialVersionUID = 6138294804551838833L;/** Thread this worker is running in.  Null if factory fails. */final Thread thread;/** Initial task to run.  Possibly null. */Runnable firstTask;/** Per-thread task counter */volatile long completedTasks;/*** Creates with given first task and thread from ThreadFactory.* @param firstTask the first task (null if none)*/Worker(Runnable firstTask) {setState(-1); // inhibit interrupts until runWorkerthis.firstTask = firstTask;this.thread = getThreadFactory().newThread(this);}/** Delegates main run loop to outer runWorker  */public void run() {runWorker(this);}// Lock methods//// The value 0 represents the unlocked state.// The value 1 represents the locked state.protected boolean isHeldExclusively() {return getState() != 0;}protected boolean tryAcquire(int unused) {if (compareAndSetState(0, 1)) {setExclusiveOwnerThread(Thread.currentThread());return true;}return false;}protected boolean tryRelease(int unused) {setExclusiveOwnerThread(null);setState(0);return true;}public void lock()        { acquire(1); }public boolean tryLock()  { return tryAcquire(1); }public void unlock()      { release(1); }public boolean isLocked() { return isHeldExclusively(); }void interruptIfStarted() {Thread t;if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {try {t.interrupt();} catch (SecurityException ignore) {}}}
}

在Worker类的构造方法中可以看出,首先将同步状态state设置为-1,这是为了防止runWorker方法运行之前被中断;

这是因为如果有其他线程调用线程池中的shutdownNow()方法时,若Worker类中state > 0,则会中断线程,state为-1则不会中断线程;

Worker类实现了Runnable接口,需要重写run方法,而run方法内部实际是调用ThreadPoolExecutor的runWorker()方法;

runWorker(Worker w)

final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;//释放锁,将state设置为0,允许中断w.unlock(); // allow interruptsboolean completedAbruptly = true;try {//若任务不为空,或队列中获取的任务不为空,则进入循环while (task != null || (task = getTask()) != null) {//任务不为空,则先获取woker线程的独占锁w.lock();// If pool is stopping, ensure thread is interrupted;// if not, ensure thread is not interrupted.  This// requires a recheck in second case to deal with// shutdownNow race while clearing interrupt//若线程次已经停止,线程中断时未中断成功if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())//执行中断操作wt.interrupt();try {//任务执行前置逻辑beforeExecute(wt, task);Throwable thrown = null;try {//任务执行task.run();} catch (RuntimeException x) {thrown = x; throw x;} catch (Error x) {thrown = x; throw x;} catch (Throwable x) {thrown = x; throw new Error(x);} finally {//任务执行后置逻辑afterExecute(task, thrown);}} finally {//任务执行完成后,将其置空task = null;//已完成任务数加一w.completedTasks++;//释放锁w.unlock();}}completedAbruptly = false;} finally {//执行Worker完成退出逻辑processWorkerExit(w, completedAbruptly);}
}

从以上源码分析可知,当Woker从线程中获取任务为空时,会调用getTask()方法从队列中获取任务;

getTask()

private Runnable getTask() {boolean timedOut = false; // Did the last poll() time out?//自旋for (;;) {int c = ctl.get();//获取线程池状态int rs = runStateOf(c);// Check if queue empty only if necessary.//检测队列在线程池关闭或停止时,是否为空if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {//减少Worker线程数量decrementWorkerCount();return null;}//获取线程池中线程数量int wc = workerCountOf(c);// Are workers subject to culling?boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;if ((wc > maximumPoolSize || (timed && timedOut))&& (wc > 1 || workQueue.isEmpty())) {if (compareAndDecrementWorkerCount(c))return null;continue;}try {//从任务队列中获取任务Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();if (r != null)//任务不为空,直接返回return r;timedOut = true;} catch (InterruptedException retry) {timedOut = false;}}
}
  • beforeExecute(Thread t, Runnable r)

    protected void beforeExecute(Thread t, Runnable r) { }
    

    此方法体为空,说明可以创建ThreadPoolExecutor的子类,来重写该方法,这样可在线程池实际执行任务前,执行我们自定义的前置逻辑;

  • afterExecute(Runnable r, Throwable t)

    protected void afterExecute(Runnable r, Throwable t) { }
    

    同上,我们可以在子类中重写此方法,这样可在线程池执行任务之后,执行我们自定义的后置处理逻辑

  • processWorkerExit(Worker w, boolean completedAbruptly)

    此方法的主要逻辑是执行退出Worker线程逻辑,并执行一些清理工作;

  • tryTerminate()

    final void tryTerminate() {//自旋for (;;) {//获取ctlint c = ctl.get();if (isRunning(c) ||runStateAtLeast(c, TIDYING) ||(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))return;if (workerCountOf(c) != 0) { // Eligible to terminate//若当前线程池中线程数量不为0,则中断线程interruptIdleWorkers(ONLY_ONE);return;}//获取线程池的全局锁final ReentrantLock mainLock = this.mainLock;//加锁mainLock.lock();try {if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {try {terminated();} finally {//将线程状态设置为TERMINATEDctl.set(ctlOf(TERMINATED, 0));//唤醒所有因调用awaitTermination()而阻塞的线程termination.signalAll();}return;}} finally {//释放锁mainLock.unlock();}// else retry on failed CAS}
    }
    
  • terminate()

    protected void terminated() { }
    

    此方法体为空,我们可在其子类中重写该方法,这样在tryTerminated()方法中,可以执行我们自定义的该 方法;

源码解析线程池如何实现优雅退出

shutdown()

使用线程池的时候,当调用了shutdown()方法时,线程池将不再接收新提交任务,已经运行中的线程将继续执行;

此方法是非阻塞方法,调用后会立即返回,并不会等待所有线程任务执行完成才返回;

public void shutdown() {//获取线程池的全局锁final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {//检查是否有关闭线程池的权限checkShutdownAccess();//将当前线程池的状态设置为SHUTDOWNadvanceRunState(SHUTDOWN);//中断woker线程interruptIdleWorkers();//调用ScheduledThreadPoolExecutor的钩子函数onShutdown(); // hook for ScheduledThreadPoolExecutor} finally {//释放锁mainLock.unlock();}tryTerminate();
}

shutdownNow()

如果调用了线程池的shutdownNow()方法,那么线程次将不再接收新提交的任务,workQueue队列中的线程也将被丢弃,运行中的线程将被中断;方法会立即返回,返回结果为任务队列workQueue中被丢弃的任务列表

public List<Runnable> shutdownNow() {List<Runnable> tasks;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();advanceRunState(STOP);interruptWorkers();//清空、丢弃队列中任务tasks = drainQueue();} finally {mainLock.unlock();}tryTerminate();//返回任务列表return tasks;
}

awaitTermination(long timeout, TimeUnit unit)

当线程池调用awaitTermination时,会阻塞调用者所在线程,直到线程池状态变为TERNINATED才返回,或者到达了超时时间返回

public boolean awaitTermination(long timeout, TimeUnit unit)throws InterruptedException {long nanos = unit.toNanos(timeout);final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {for (;;) {if (runStateAtLeast(ctl.get(), TERMINATED))return true;if (nanos <= 0)return false;nanos = termination.awaitNanos(nanos);}} finally {//释放锁mainLock.unlock();}
}

此方法总体逻辑为:先获取Worker线程的独占锁,然后自旋,并判断线程池状态变为TERMINATED;如果是,则返回true,否则检测是否超时,若超时,则返回false;未超时则重置超时剩余时长;

AQS中的关键类

CountDownLatch

  • 概述

同步辅助类,它可以阻塞当前线程的执行。也就是可以实现一个或多个线程一直等待,直到其他线程执行完毕。使用一个给定的计数器进行初始化,该计数器的操作是原子操作,即同一时刻只有一个线程可操作计数器。

调用改类的await()方法的线程会一直等待,直到其他线程调用该类的countDown()方法,并使当前计数器的值为0为止;

每次调用该类的countDown()方法,都会让改计数器值减一;

当该计数器的值减小为0时,所有因调用await()方法而阻塞等待的线程,都将继续往下执行;这种操作只能出现一次,因为该计数器的值不能重置;

如果需要一个可以重置计数次数的版本,可以考虑使用CyclicBarrier.

CountDownLatch支持给定超时时间的等待,超过给定时间不在等待;使用时只需在await()方法中传入给定时间即可;

public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException {return dowait(true, unit.toNanos(timeout));
}
  • 使用场景

    在某些场景中,程序需要等待某个或某些条件完成后,才能继续执行后续的操作。典型的应用为并行计算:当处理某个运算量很大的任务时,可拆分为多个小任务,等待所有子任务完后,父任务再拿到所有子任务的结果进行汇总

  • 代码示例

    调用ExecutorService的shutdown()方法,并不会第一时间将线程全都销毁掉,而是让当前已有线程全部执行完,

    之后再把线程池销毁掉

    package io.binghe.concurrency.example.aqs;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;@Slf4j
    public class CountDownLatchExample {private static final int threadCount = 200;public static void main(String[] args) throws InterruptedException {ExecutorService exec = Executors.newCachedThreadPool();final CountDownLatch countDownLatch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {final int threadNum = i;exec.execute(() -> {try {test(threadNum);} catch (InterruptedException e) {e.printStackTrace();} finally {countDownLatch.countDown();}});}countDownLatch.await();log.info("finish");exec.shutdown();}private static void test(int threadNum) throws InterruptedException {Thread.sleep(100);log.info("{}", threadNum);}
    }
    

    支持给定时间等待的代码如下:

    package io.binghe.concurrency.example.aqs;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;@Slf4j
    public class CountDownLatchExample {private static final int threadCount = 200;public static void main(String[] args) throws InterruptedException {ExecutorService exec = Executors.newCachedThreadPool();final CountDownLatch countDownLatch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {final int threadNum = i;exec.execute(() -> {try {test(threadNum);} catch (InterruptedException e) {e.printStackTrace();} finally {countDownLatch.countDown();}});}countDownLatch.await(10, TimeUnit.MICROSECONDS);log.info("finish");exec.shutdown();}private static void test(int threadNum) throws InterruptedException {Thread.sleep(100);log.info("{}", threadNum);}
    }
    

Semaphore

  • 概述

    控制同一时间并发线程的数目。能够完成对于信号量的控制,可控制某个资源被同时访问的线程个数。

    提供了两个核心方法:acquire()和release()

    acquire()表示获取一个访问许可,没有获得则阻塞等待;release()则是在完成后释放一个许可。

    Semaphore维护了当前可访问的个数;通过同步机制来控制可同时访问的个数。

    Semaphore可以实现有限大小的链表

  • 使用场景

    Semaphore常用于可有限访问的资源

  • 代码示例

    package io.binghe.concurrency.example.aqs;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;@Slf4j
    public class SemaphoreExample {private static final int threadCount = 200;public static void main(String[] args) throws InterruptedException {ExecutorService exec = Executors.newCachedThreadPool();final Semaphore semaphore = new Semaphore(3);for (int i = 0; i < threadCount; i++) {final int threadNum = i;exec.execute(() -> {try {semaphore.acquire(); //获取一个许可test(threadNum);semaphore.release(); //释放一个许可} catch (InterruptedException e) {e.printStackTrace();}});}exec.shutdown();}private static void test(int threadNum) throws InterruptedException {log.info("{}", threadNum);Thread.sleep(1000);}
    }
    

    每次获取并释放多个许可

    package io.binghe.concurrency.example.aqs;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;@Slf4j
    public class SemaphoreExample {private static final int threadCount = 200;public static void main(String[] args) throws InterruptedException {ExecutorService exec = Executors.newCachedThreadPool();final Semaphore semaphore = new Semaphore(3);for (int i = 0; i < threadCount; i++) {final int threadNum = i;exec.execute(() -> {try {semaphore.acquire(3); //获取多个许可test(threadNum);semaphore.release(3); //释放多个许可} catch (InterruptedException e) {e.printStackTrace();}});}log.info("finish");exec.shutdown();}private static void test(int threadNum) throws InterruptedException {log.info("{}", threadNum);Thread.sleep(1000);}
    }
    

    假设有这样一个场景,假设系统当前允许的最大并发数是3,超过3之后就需要丢弃,这样的场景也可通过Semaphore来实现:

    package io.binghe.concurrency.example.aqs;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;@Slf4j
    public class SemaphoreExample {private static final int threadCount = 200;public static void main(String[] args) throws InterruptedException {ExecutorService exec = Executors.newCachedThreadPool();final Semaphore semaphore = new Semaphore(3);for (int i = 0; i < threadCount; i++) {final int threadNum = i;exec.execute(() -> {try {//尝试获取一个许可,也可以尝试获取多个许可,//支持尝试获取许可超时设置,超时后不再等待后续线程的执行//具体可以参见Semaphore的源码if (semaphore.tryAcquire()) {test(threadNum);semaphore.release(); //释放一个许可}} catch (InterruptedException e) {e.printStackTrace();}});}log.info("finish");exec.shutdown();}private static void test(int threadNum) throws InterruptedException {log.info("{}", threadNum);Thread.sleep(1000);}
    }
    

CyclicBarrier

  • 概述

    是一个同步辅助类,允许一组线程相互等待,直到到达某个公共的屏障点;通过它可以完成多个线程之间相互等待,只有每个线程都准备就绪后,才允许各自继续往下执行。

    与countDownLatch相似的地方,都是使用计数器实现,当某个线程调用了CyclicBarrier的await()方法后,就进入了等待状态,而且计数器执行加一操作;当计数器的值增加到设置的初始值,所有因await()方法进入等待状态的线程将被唤醒,继续执行各自后续的操作。CyclicBarrier在释放等待线程后可以重用,因此CyclicBarrier又被成为循环屏障

  • 使用场景

    可以用于多线程计算数据,最后合并计算结果的场景

  • 和countDownLatch的区别

    1. countDownLatch的计数器只能使用一次;而CyclicBarrier的计数器可使用reSet()重置,循环使用
    2. countDownLatch实现的是1个或n个线程等待其他线程完成后,才能继续往下执行,描述的是1个或多个线程等待其他线程的关系;而CyclicBarrier主要是一个线程组内线程相互等待,知道每个线程都满足了共有条件,才继续往下执行,描述的多个线程内部相互等待的关系。
    3. CyclicBarrier可以处理更复杂的场景,当计算出错时,可以重置计数器,让线程重新再执行一次
    4. CyclicBarrier提供了更多有用的方法,比如getNumberByWaiting()可获取等待线程的数量,可通过isBroken()方法判断线程是否被中断
  • 代码示例

    package io.binghe.concurrency.example.aqs;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.CyclicBarrier;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;@Slf4j
    public class CyclicBarrierExample {private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);public static void main(String[] args) throws Exception {ExecutorService executorService = Executors.newCachedThreadPool();for (int i = 0; i < 10; i++) {final int threadNum = i;Thread.sleep(1000);executorService.execute(() -> {try {race(threadNum);} catch (Exception e) {e.printStackTrace();}});}executorService.shutdown();}private static void race(int threadNum) throws Exception {Thread.sleep(1000);log.info("{} is ready", threadNum);cyclicBarrier.await();log.info("{} continue", threadNum);}
    }
    

    设置等待超时示例代码如下:

    package io.binghe.concurrency.example.aqs;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.*;@Slf4j
    public class CyclicBarrierExample {private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);public static void main(String[] args) throws Exception {ExecutorService executorService = Executors.newCachedThreadPool();for (int i = 0; i < 10; i++) {final int threadNum = i;Thread.sleep(1000);executorService.execute(() -> {try {race(threadNum);} catch (Exception e) {e.printStackTrace();}});}executorService.shutdown();}private static void race(int threadNum) throws Exception {Thread.sleep(1000);log.info("{} is ready", threadNum);try {cyclicBarrier.await(2000, TimeUnit.MILLISECONDS);} catch (BrokenBarrierException | TimeoutException e) {log.warn("BarrierException", e);}log.info("{} continue", threadNum);}
    }
    

    在声明CyclicBarrier的时候,还可以指定一个Runnable,当线程到达屏障的时候,可以优先执行Runnable的方法,示例代码如下:

    package io.binghe.concurrency.example.aqs;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.CyclicBarrier;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;@Slf4j
    public class CyclicBarrierExample {private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {log.info("callback is running");});public static void main(String[] args) throws Exception {ExecutorService executorService = Executors.newCachedThreadPool();for (int i = 0; i < 10; i++) {final int threadNum = i;Thread.sleep(1000);executorService.execute(() -> {try {race(threadNum);} catch (Exception e) {e.printStackTrace();}});}executorService.shutdown();}private static void race(int threadNum) throws Exception {Thread.sleep(1000);log.info("{} is ready", threadNum);cyclicBarrier.await();log.info("{} continue", threadNum);}
    }
    

AQS中的关键锁

ReentrantLock

  • 概述

    Java中提供的锁主要分为两类,一类是Synchronized修饰的锁,另一类就是JUC中提供的锁,而JUC中的核心锁就是ReentrantLock

    ReentrantLock和Synchronized的区别:

    1. 可重入性

      二者都是同一个线程进入一次,锁的计数器就加一,当锁的计数器下降为0时,才会释放锁

    2. 锁的实现

      Synchronized是基于JVM实现的;而ReentrantLock是基于JDK实现的

    3. 性能区别

      Synchronized优化之前性能比ReentrantLock差很多;但JDK6之后,Synchronized引入偏向锁、轻量级锁(即自旋锁)之后,性能差不多了

    4. 功能区别

      便利性:

      Synchronized使用起来较方便,并且由编译器加锁和释放锁;ReentrantLock需要手动加锁和释放锁,最好是在finally中释放锁

      灵活度和细粒度:

      ReentrantLock 这里优于Synchronized

    ReentrantLock独有的功能:

    1. ReentrantLock 可以指定公平锁还是非公平锁。而Synchronized只能使用非公平锁。公平锁的意思就是先等待的线程先获得锁
    2. 提供了一个Condition类,可以分组唤醒需要唤醒的线程。而Synchronized只能随机的唤醒一个线程,或者唤醒全部线程
    3. 提供能够中断等待锁的线程的机制,lock.lockInterruptily()。ReentrantLock 实现是一种自旋锁,通过调用CAS操作来实现加锁,性能比较好是因为避免了使线程进入内核态的阻塞状态
    4. 总的来说,Synchronized能做的,ReentrantLock 都能做。性能上,ReentrantLock 比Synchronized的要好

    Synchronized的优势:

    1. 不用手动释放锁,JVM自动处理,如果出现异常,JVM也会自动释放锁
    2. JVM进行锁定管理请求和释放时,JVM在生成线程转储时能够锁定信息,这些信息对调试非常有用,因为他们能够标识死锁和其他异常行为来源。而ReentrantLock 只是普通的类,JVM不知道哪个线程拥有锁
    3. Synchronized可以再所有版本的JVM上使用,ReentrantLock 在某些1.5版本之前的JVM可能不支持

    ReentrantLock中部分方法说明:

    1. boolean tryLock():仅在调用时锁定未被另一个线程保持的情况下才获取锁定
    2. boolean tryLock(long timeout, TimeUnit unit):如果锁定在给定时间没有被另一个线程保持,且当前线程没有中断,则获取这个锁定
    3. void lockInterruptibly():若当前线程没有被中断,就获取锁定;若被中断,则抛出异常
    4. boolean isLocked():查询此锁定是否由任意线程所保持
    5. boolean isHeldByCurrentThread():查询当前线程是否保持锁定状态
    6. boolean isFair():判断是否是公平锁
    7. boolean hasQueuedThread(Thread thread):查询指定线程是否在等待获取此锁定
    8. boolean hasQueuedThreads():是否有线程在等待获取此锁定
    9. int getHoldCount():查询当前线程保持锁定的个数
  • 代码示例

    package io.binghe.concurrency.example.lock;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;@Slf4j
    public class LockExample {//请求总数public static int clientTotal = 5000;//同时并发执行的线程数public static int threadTotal = 200;public static int count = 0;private static final Lock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {ExecutorService executorService = Executors.newCachedThreadPool();final Semaphore semaphore = new Semaphore(threadTotal);final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);for (int i = 0; i < clientTotal; i++) {executorService.execute(() -> {try {semaphore.acquire();add();semaphore.release();} catch (Exception e) {log.error("exception", e);}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();log.info("count:{}", count);}private static void add() {lock.lock();try {count++;} finally {lock.unlock();}}
    }
    

ReentrantReadWriteLock

  • 概述

    读写锁,在没有任何读锁的时候,才可以获取写锁;若一直无法获得写锁,就会导致写锁饥饿;

    在读多写少的场景,ReentrantReadWriteLock性能比ReentrantLock高不少,在多线程读时互不影响,不像ReentrantLock即使是多线程读,也需要每个线程获取读锁;不过任何一个线程在写的时候,就和ReentrantLock类似,无论其他线程是读还是写,都必选获取写锁。需要注意的是同一个线程,可以同时持有读锁和写锁。

  • 代码示例

    package io.binghe.concurrency.example.lock;import lombok.extern.slf4j.Slf4j;import java.util.Map;
    import java.util.Set;
    import java.util.TreeMap;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;@Slf4j
    public class LockExample {private final Map<String, Data> map = new TreeMap<>();private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();private final Lock readLock = lock.readLock();private final Lock writeLock = lock.writeLock();public Data get(String key) {readLock.lock();try {return map.get(key);} finally {readLock.unlock();}}public Set<String> getAllKeys() {readLock.lock();try {return map.keySet();} finally {readLock.unlock();}}public Data put(String key, Data value) {writeLock.lock();try {return map.put(key, value);} finally {writeLock.unlock();}}class Data {}
    }
    

StampedLock

  • 概述

StampedLock是ReentrantReadWriteLock的实现,主要不同是StampedLock不允许重入,多了乐观读的功能,使用上会更加复杂一些,但是会有更好的性能表现。

StampedLock控制锁的三种模式:读、写、乐观读

StampedLock的状态由版本和模式两个部分组成,锁获取方法返回的是一个数字所谓票据,用相应的锁状态来表示并控制相关的访问,数字0表示写锁没有被授权访问

在读锁上分为悲观锁和乐观锁,乐观读就是在读多写少的场景,乐观的认为写入和读取同时发生的几率很小,因此,乐观的完全用读锁锁定。程序可以查看读取之后,是否遭到写入进行了变更,再采取后续的措施,这样的改进可大幅提升程序的吞吐量

总之,在读线程越来越多的场景下,StampedLock大幅提升了程序的吞吐量

import java.util.concurrent.locks.StampedLock;class Point {private double x, y;private final StampedLock sl = new StampedLock();void move(double deltaX, double deltaY) { // an exclusively locked methodlong stamp = sl.writeLock();try {x += deltaX;y += deltaY;} finally {sl.unlockWrite(stamp);}}//下面看看乐观读锁案例double distanceFromOrigin() { // A read-only methodlong stamp = sl.tryOptimisticRead(); //获得一个乐观读锁double currentX = x, currentY = y; //将两个字段读入本地局部变量if (!sl.validate(stamp)) { //检查发出乐观读锁后同时是否有其他写锁发生?stamp = sl.readLock(); //如果没有,我们再次获得一个读悲观锁try {currentX = x; // 将两个字段读入本地局部变量currentY = y; // 将两个字段读入本地局部变量} finally {sl.unlockRead(stamp);}}return Math.sqrt(currentX * currentX + currentY * currentY);}//下面是悲观读锁案例void moveIfAtOrigin(double newX, double newY) { // upgrade// Could instead start with optimistic, not read modelong stamp = sl.readLock();try {while (x == 0.0 && y == 0.0) { //循环,检查当前状态是否符合long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁if (ws != 0L) { //这是确认转为写锁是否成功stamp = ws; //如果成功 替换票据x = newX; //进行状态改变y = newY; //进行状态改变break;} else { //如果不能成功转换为写锁sl.unlockRead(stamp); //我们显式释放读锁stamp = sl.writeLock(); //显式直接进行写锁 然后再通过循环再试}}} finally {sl.unlock(stamp); //释放读锁或写锁}}
}
  • 代码示例

    package io.binghe.concurrency.example.lock;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.locks.StampedLock;@Slf4j
    public class LockExample {//请求总数public static int clientTotal = 5000;//同时并发执行的线程数public static int threadTotal = 200;public static int count = 0;private static final StampedLock lock = new StampedLock();public static void main(String[] args) throws InterruptedException {ExecutorService executorService = Executors.newCachedThreadPool();final Semaphore semaphore = new Semaphore(threadTotal);final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);for (int i = 0; i < clientTotal; i++) {executorService.execute(() -> {try {semaphore.acquire();add();semaphore.release();} catch (Exception e) {log.error("exception", e);}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();log.info("count:{}", count);}private static void add() {
    //加锁时返回一个long类型的票据long stamp = lock.writeLock();try {count++;} finally {
    //释放锁的时候带上加锁时返回的票据lock.unlock(stamp);}}
    }
    

    我们可以这样初步判断选择Synchronized还是ReentrantLock:

    1. 当只有少量竞争者时,Synchronized是一个很好的通用锁实现
    2. 竞争者不少,但是线程的增长趋势是可预估的,此时使用ReentrantLock是个很好的通用锁实现
    3. Synchronized不会引发死锁,其他锁使用不当可能会引发死锁

Condition

  • 概述

    Condition是一个多线程间协调通讯的工具类,使用它可以有更好的灵活性,比如可以实现多路通知功能,也就是一个Lock对象里,可以创建多个Condition实例,线程对象可以注册在指定的Condition中,从而选择性的进行线程通知,在调度线程上更加灵活

  • 特点

    1. Condition的前提是Lock,由AQS中的newCondition()方法来创建Condition对象
    2. Condition的await()方法表示线程从AQS中删除,并释放线程获取的锁;并进入Condition等待队列,等待被通知
    3. Condition的signal()方法表示唤醒Condition等待队列中的节点,准备获取锁
  • 代码示例

    package io.binghe.concurrency.example.lock;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;@Slf4j
    public class LockExample {public static void main(String[] args) {ReentrantLock reentrantLock = new ReentrantLock();Condition condition = reentrantLock.newCondition();new Thread(() -> {try {reentrantLock.lock();log.info("wait signal"); // 1condition.await();} catch (InterruptedException e) {e.printStackTrace();}log.info("get signal"); // 4reentrantLock.unlock();}).start();new Thread(() -> {reentrantLock.lock();log.info("get lock"); // 2try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}condition.signalAll();log.info("send signal ~ "); // 3reentrantLock.unlock();}).start();}
    }
    

ThreadLocal

  • 概述

ThreadLocal是JDK提供的,支持线程本地变量。意思是ThreadLocal中存放的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。

如果我们创建了一个ThreadLocal变量,则访问这个变量的每个线程都会拥有这个变量的本地副本,当多个线程对这个变量进行操作时,实际上是操作的该变量的本地副本,从而避免了线程安全问题

  • 使用示例

    使用ThreadLocal保存并打印相关变量信息

    public class ThreadLocalTest {private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();public static void main(String[] args) {//创建第一个线程Thread threadA = new Thread(() -> {threadLocal.set("ThreadA:" + Thread.currentThread().getName());System.out.println("线程A本地变量中的值为:" + threadLocal.get());});//创建第二个线程Thread threadB = new Thread(() -> {threadLocal.set("ThreadB:" + Thread.currentThread().getName());System.out.println("线程B本地变量中的值为:" + threadLocal.get());});//启动线程A和线程BthreadA.start();threadB.start();}
    }
    

    运行程序,打印信息如下:

    线程A本地变量中的值为:ThreadAThread-0
    线程B本地变量中的值为:ThreadBThread-1
    

    此时,我们为线程A增加删除变量操作:

    public class ThreadLocalTest {private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();public static void main(String[] args) {//创建第一个线程Thread threadA = new Thread(() -> {threadLocal.set("ThreadA:" + Thread.currentThread().getName());System.out.println("线程A本地变量中的值为:" + threadLocal.get());threadLocal.remove();System.out.println("线程A删除本地变量后ThreadLocal中的值为:" + threadLocal.get());});//创建第二个线程Thread threadB = new Thread(() -> {threadLocal.set("ThreadB:" + Thread.currentThread().getName());System.out.println("线程B本地变量中的值为:" + threadLocal.get());System.out.println("线程B没有删除本地变量:" + threadLocal.get());});//启动线程A和线程BthreadA.start();threadB.start();}
    }
    

    打印信息如下:

    线程A本地变量中的值为:ThreadAThread-0
    线程B本地变量中的值为:ThreadBThread-1
    线程B没有删除本地变量:ThreadBThread-1
    线程A删除本地变量后ThreadLocal中的值为:null
    

    通过上述程序,我们可以看出:线程A和线程B存储在ThreadLocal中的变量互不干扰,线程A存储的变量只能线程A可以访问,线程B存储的变量只能由线程B访问

  • ThreadLocal原理

    public class Thread implements Runnable {/***********省略N行代码*************/ThreadLocal.ThreadLocalMap threadLocals = null;ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    /***********省略N行代码*************/
    }
    

    由以上源码可看出,ThreadLocal类中存在threadLocals和inheritableThreadLocals,这两个变量都是ThreadLocalMap类型的变量,而且二者的初始值都为null,只有当前线程第一次调用set()方法或get()方法时才会实例化变量。

    须注意的时,每个线程的本地变量不是存放在ThreadLocal实例里面的,而是存放在调用线程的threadLocals变量里面的;也就是说,调用ThreadLocal的set()方法,存储的本地变量是存放在具体调用线程的内存空间中的,而ThreadLocal只提供了get()和set()方法来访问本地变量值;当调用set()方法时,将要设置的值存储在调用线程的threadLocals中,当调用get()方法来取值时,将从当前线程获取threadLocals中存放的变量;

    1. set()

      public void set(T value) {//获取当前线程Thread t = Thread.currentThread();//以当前线程为key,获取ThreadLocalMap对象ThreadLocalMap map = getMap(t);if (map != null)//获取的获取ThreadLocalMap不为空,则赋值操作map.set(this, value);else//获取ThreadLocalMap为空,则为当前线程创建并赋值createMap(t, value);
      }/*** Create the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param t the current thread* @param firstValue value for the initial entry of the map*/void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}
      
    2. get()

      /*** Returns the value in the current thread's copy of this* thread-local variable.  If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local*/
      public T get() {//获取当前线程Thread t = Thread.currentThread();//获取当前线程ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {//ThreadLocalMap不为空,则从中取值ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//ThreadLocalMap为空,则返回默认值return setInitialValue();
      }
      
    3. remove()

      public void remove() {//获取当前线程中的threadLocalsThreadLocalMap m = getMap(Thread.currentThread());if (m != null)//若threadLocals不为空,则清除m.remove(this);
      }
      

      注意:若线程一直不终止,则本地变量会一直存在于调用线程的ThreadLocal中;所以,如果不需要本地变量时,可以调用ThreadLocal的remove()方法删除,以免出现内存溢出问题

  • ThreadLocal变量不具传递性

    使用ThreadLocal存储的本地变量,不具有传递性;也就是说同一个ThreadLocal,在父线程中设置值后,在子线程中是获取不到值的;

    public class ThreadLocalTest {private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();public static void main(String[] args) {//在主线程中设置值threadLocal.set("ThreadLocalTest");//在子线程中获取值Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("子线程获取值:" + threadLocal.get());}});//启动子线程thread.start();//在主线程中获取值System.out.println("主线程获取值:" + threadLocal.get());}}
    

    运行上述字段代码之后,结果如下所示:

    主线程获取值:ThreadLocalTest
    子线程获取值:null
    

    通过上述示例可以看出,在父线程中对ThreadLocal进行设置值之后,在子线程中是取不到这个值的;

    那有没有什么办法,可以在子线程中取到父线程的值呢?我们可以通过InheritableThreadLocal来实现;

  • InheritableThreadLocal

    InheritableThreadLocal类继承自ThreadLocal,它可以实现在子线程中获取到父线程中设置的值;

    public class ThreadLocalTest {private static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();public static void main(String[] args) {//在主线程中设置值threadLocal.set("ThreadLocalTest");//在子线程中获取值Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("子线程获取值:" + threadLocal.get());}});//启动子线程thread.start();//在主线程中获取值System.out.println("主线程获取值:" + threadLocal.get());}}
    

    运行上述程序,结果如下:

    主线程获取值:ThreadLocalTest
    子线程获取值:ThreadLocalTest
    

    可以看出,使用InheritableThreadLocal,子线程可以取到父线程中设置的本地变量;

  • InheritableThreadLocal原理

    public class InheritableThreadLocal<T> extends ThreadLocal<T> {protected T childValue(T parentValue) {return parentValue;}/*** Get the map associated with a ThreadLocal.** @param t the current thread*/ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}/*** Create the map associated with a ThreadLocal.** @param t the current thread* @param firstValue value for the initial entry of the table.*/void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);}
    }
    

    由源码可知,InheritableThreadLocal继承自ThreadLocal,并重写了childValue()/getMap()/createMap()方法,

    也就是说,当调用ThreadLocal的set()方法时,创建的是当前线程的inheritableThreadLocals变量,而不是threadLocals变量;此时如果父线程创建子线程,在Thread类的构造函数中,会把父线程的inheritableThreadLocals变量中的本地变量复制一份到子线程的inheritableThreadLocals中。

并发问题

可见性问题

可见性问题,即一个线程对共享变量进行了修改,另一线程不能立刻见到这种修改,这是由CPU添加了缓存导致的;

单核CPU不存在可见性问题,多核CPU才存在可见性问题;单核CPU由于是单核时间片调度,实际是串行执行;而多核则可以实现并行;

可见性示例代码:

public class ConcurrentTest {private int num = 0;public static void main(String[] args) throws InterruptedException {ConcurrentTest concurrentTest = new ConcurrentTest();concurrentTest.threadsTest();}public void threadsTest() throws InterruptedException {Thread thread = new Thread("test-1") {@Overridepublic void run() {for (int i = 0; i < 20; i++) {try {addNum();} catch (InterruptedException e) {e.printStackTrace();}}}};Thread thread2 = new Thread("test-2") {@Overridepublic void run() {for (int i = 0; i < 20; i++) {try {addNum();} catch (InterruptedException e) {e.printStackTrace();}}}};thread.start();thread2.start();thread.join();thread2.join();System.out.println("执行完毕");}private void addNum() throws InterruptedException {Thread.sleep(1000);num++;System.out.println(Thread.currentThread().getName() + ":" + num);}
}

原子性问题

原子性是指一个或多个操作,在CPU执行过程中不被中断的特性。原子性操作一旦开始运行,就会一直到运行结束为止,中间不会有中断的情况发生。

原子性问题,是指一个或多个操作,在CPU执行过程中,出现了中断的情况;

线程在执行某项操作时,此时由于CPU切换,转而去执行其他任务,导致当前任务中断,这就会造成原子性问题;

在JAVA中,并发程序是基于多线程来编写的,这也会涉及到CPU对于线程的切换问题,正是由于CPU对线程的切换,导致并发编程可能出现原子性问题;

有序性问题

有序性是指:代码按执行顺序执行

指令重排序:编译器或解释器为了优化程序性能,有时会修改程序的执行顺序,但这种修改,可能会导致意想不到的问题;

在单线程情况下,指令重排序仍可保证最终结果与程序顺序执行结果一致,但在多线程情况下,可能就会存在问题;

有序性问题:CPU为了对程序进行优化,进行指令重排序,此时执行顺序可能和编码顺序不一致,这可能会引起有序性问题;

总结

导致并发编程可能导致问题的主要原因有三个:缓存导致的可见性问题,CPU线程切换导致的原子性问题和性能优化指令重排序导致的有序性问题

Happens-Before原则

JDK1.5版本中,JAVA内存模型引入了Happens-Before原则

示例代码1:

class VolatileExample {int x = 0;volatile boolean v = false;public void writer() {x = 1;v = true;}public void reader() {if (v == true) {//x的值是多少呢?}}
}
  1. 程序次序规则

    ​ 单个线程中,按照代码顺序,前面的操作Happens-Before于后面的任意操作

    ​ 例如:示例1中,代码 x = 1会在 v = true 之前完成;

  2. volatile变量规则

    ​ 对一个volatile的写操作,Happens-Before于后续对它的读操作

  3. 传递规则

    ​ 若A Happens-Before B,B Happens-Before C,则有 A Happens-Before C

    ​ 结合原则1、2、3和示例代码1,我们可得出如下结论:

    • x = 1 Happens-Before v = true ,符合原则1;
    • 写变量 v = true Happens-Before 读变量 v = true,符合原则2
    • 则根据原则3得出,x = 1 Happens-Before 读变量 v = true;
    • 就是也就是说,如果线程B读取到了 v = true,那么线程A设置的 x = 1 对于B就是可见的,就是说此时的B线程可以访问到 x = 1
  4. 锁定规则

    ​ 对一个锁的解锁操作Happens-Before于后续该锁的加锁操作

  5. 线程启动规则

    ​ 若线程A调用线程B的start()启动线程B,则start()方法 Happens-Before 于线程B中的任意操作

    //在线程A中初始化线程B
    Thread threadB = new Thread(()->{//此处的变量x的值是多少呢?答案是100
    });
    //线程A在启动线程B之前将共享变量x的值修改为100
    x = 100;
    //启动线程B
    threadB.start();
    
  6. 线程终结规则

    ​ 线程A等待线程B完成(调用线程B的join()方法),当线程B完成后(线程B的join()方法返回),则线程A能够访问到线程B对共享变量的修改

    Thread threadB = new Thread(()-{
    //在线程B中,将共享变量x的值修改为100 x = 100;
    });
    //在线程A中启动线程B
    threadB.start();
    //在线程A中等待线程B执行完成
    threadB.join();
    //此处访问共享变量x的值为100
    
  7. 线程中断规则

    ​ 对线程interrupt()方法的调用,Happens-Before于被中断线程检测到中断事件发生

  8. 对象终结规则

    ​ 一个对象的初始化完后才Happens-Before于该对象finilize()方法的开始

ForkJoin框架

  • 概述

    Java1.7引入了一种新的并发框架——Fork/Join框架;主要用于实现“分而治之”的算法,特别是分而治之后递归调用的函数。

    Fork/Join框架的本质是一个并行执行任务的框架,能够把一个大任务分割成多个小任务,最终汇总每个小任务的结果得到大任务的结果。Fork/Join框架与ThreadPool共存,并不是要替换ThreadPool

  • 原理

    Fork/Join使用无限队列来保存需要执行的任务,而线程数量则是通过构造函数传入,若没有传入线程数量,则当前计算机可用CPU数量会被默认设置为线程数量。

    ForkjoinPool主要采用分治法来解决问题。典型应用比如快速排序算法。

  • 框架实现

    1. ForkJoinPool

      实现了ForkJoin框架中的线程池

    2. ForkJoinWorkerThread

      实现ForkJoin框架中的线程

    3. ForkJoinTask

      封装了数据及其相应的运算,并且支持细粒度的数据并行。

      ForkJoinTask主要包括两个方法fork()和join(),分别实现任务的分拆和合并;

      fork()方法类似于Thread方法中的start()方法,但是它并不立即执行任务,而是将任务放入执行队列中。

      join()方法和Thread的join()方法不同,它并不简单的阻塞线程;而是利用工作线程执行其他任务,当一个工作线程调用join(),它将处理其他任务,直到注意到子线程执行完成。

    4. RecursiveTask

      有返回结果的ForkJoinTask实现Callable

    5. RecursiveAction

      无返回结果的ForkJoinTask实现Runnalbe

    6. CountedCompleter

      在任务执行完成后会触发执行一个自定义的钩子函数

  • 示例代码

    public class ForkJoinTaskExample extends RecursiveTask<Integer> {public static final int threshold = 2;private int start;private int end;public ForkJoinTaskExample(int start, int end) {this.start = start;this.end = end;}@Overrideprotected Integer compute() {int sum = 0;//如果任务足够小就计算任务boolean canCompute = (end - start) <= threshold;if (canCompute) {for (int i = start; i <= end; i++) {sum += i;}} else {// 如果任务大于阈值,就分裂成两个子任务计算int middle = (start + end) / 2;ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);// 执行子任务leftTask.fork();rightTask.fork();// 等待任务执行结束合并其结果int leftResult = leftTask.join();int rightResult = rightTask.join();// 合并子任务sum = leftResult + rightResult;}return sum;}public static void main(String[] args) {ForkJoinPool forkjoinPool = new ForkJoinPool();//生成一个计算任务,计算1+2+3+4ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);//执行一个任务Future<Integer> result = forkjoinPool.submit(task);try {log.info("result:{}", result.get());} catch (Exception e) {log.error("exception", e);}}
    }
    

v = true 之前完成;

  1. volatile变量规则

    ​ 对一个volatile的写操作,Happens-Before于后续对它的读操作

  2. 传递规则

    ​ 若A Happens-Before B,B Happens-Before C,则有 A Happens-Before C

    ​ 结合原则1、2、3和示例代码1,我们可得出如下结论:

    • x = 1 Happens-Before v = true ,符合原则1;
    • 写变量 v = true Happens-Before 读变量 v = true,符合原则2
    • 则根据原则3得出,x = 1 Happens-Before 读变量 v = true;
    • 就是也就是说,如果线程B读取到了 v = true,那么线程A设置的 x = 1 对于B就是可见的,就是说此时的B线程可以访问到 x = 1
  3. 锁定规则

    ​ 对一个锁的解锁操作Happens-Before于后续该锁的加锁操作

  4. 线程启动规则

    ​ 若线程A调用线程B的start()启动线程B,则start()方法 Happens-Before 于线程B中的任意操作

    //在线程A中初始化线程B
    Thread threadB = new Thread(()->{//此处的变量x的值是多少呢?答案是100
    });
    //线程A在启动线程B之前将共享变量x的值修改为100
    x = 100;
    //启动线程B
    threadB.start();
    
  5. 线程终结规则

    ​ 线程A等待线程B完成(调用线程B的join()方法),当线程B完成后(线程B的join()方法返回),则线程A能够访问到线程B对共享变量的修改

    Thread threadB = new Thread(()-{
    //在线程B中,将共享变量x的值修改为100 x = 100;
    });
    //在线程A中启动线程B
    threadB.start();
    //在线程A中等待线程B执行完成
    threadB.join();
    //此处访问共享变量x的值为100
    
  6. 线程中断规则

    ​ 对线程interrupt()方法的调用,Happens-Before于被中断线程检测到中断事件发生

  7. 对象终结规则

    ​ 一个对象的初始化完后才Happens-Before于该对象finilize()方法的开始

ForkJoin框架

  • 概述

    Java1.7引入了一种新的并发框架——Fork/Join框架;主要用于实现“分而治之”的算法,特别是分而治之后递归调用的函数。

    Fork/Join框架的本质是一个并行执行任务的框架,能够把一个大任务分割成多个小任务,最终汇总每个小任务的结果得到大任务的结果。Fork/Join框架与ThreadPool共存,并不是要替换ThreadPool

  • 原理

    Fork/Join使用无限队列来保存需要执行的任务,而线程数量则是通过构造函数传入,若没有传入线程数量,则当前计算机可用CPU数量会被默认设置为线程数量。

    ForkjoinPool主要采用分治法来解决问题。典型应用比如快速排序算法。

  • 框架实现

    1. ForkJoinPool

      实现了ForkJoin框架中的线程池

    2. ForkJoinWorkerThread

      实现ForkJoin框架中的线程

    3. ForkJoinTask

      封装了数据及其相应的运算,并且支持细粒度的数据并行。

      ForkJoinTask主要包括两个方法fork()和join(),分别实现任务的分拆和合并;

      fork()方法类似于Thread方法中的start()方法,但是它并不立即执行任务,而是将任务放入执行队列中。

      join()方法和Thread的join()方法不同,它并不简单的阻塞线程;而是利用工作线程执行其他任务,当一个工作线程调用join(),它将处理其他任务,直到注意到子线程执行完成。

    4. RecursiveTask

      有返回结果的ForkJoinTask实现Callable

    5. RecursiveAction

      无返回结果的ForkJoinTask实现Runnalbe

    6. CountedCompleter

      在任务执行完成后会触发执行一个自定义的钩子函数

  • 示例代码

    public class ForkJoinTaskExample extends RecursiveTask<Integer> {public static final int threshold = 2;private int start;private int end;public ForkJoinTaskExample(int start, int end) {this.start = start;this.end = end;}@Overrideprotected Integer compute() {int sum = 0;//如果任务足够小就计算任务boolean canCompute = (end - start) <= threshold;if (canCompute) {for (int i = start; i <= end; i++) {sum += i;}} else {// 如果任务大于阈值,就分裂成两个子任务计算int middle = (start + end) / 2;ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);// 执行子任务leftTask.fork();rightTask.fork();// 等待任务执行结束合并其结果int leftResult = leftTask.join();int rightResult = rightTask.join();// 合并子任务sum = leftResult + rightResult;}return sum;}public static void main(String[] args) {ForkJoinPool forkjoinPool = new ForkJoinPool();//生成一个计算任务,计算1+2+3+4ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);//执行一个任务Future<Integer> result = forkjoinPool.submit(task);try {log.info("result:{}", result.get());} catch (Exception e) {log.error("exception", e);}}
    }
    

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

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

android 实现屏幕录制功能

https://github.com/guaju/ScreenRecordLibrary 本库是基于MediaProjection封装的手机屏幕录制开源库&#xff0c;并提交到Jcenter&#xff0c;方便大家使用 使用方法&#xff1a; module中的build.gradle中的depandencies中添加依赖即可&#xff0c;如下 dependencies { x…...

Android限制录制屏幕无声音,屏幕录制没有声音如何解决?

首先&#xff0c;请确保你使用的是Windows 7&#xff0c;8或者10&#xff0c;目前傲软录屏不支持windows XP系统&#xff1b;使用傲软录屏录制音频&#xff0c;请确保在声音选项下选择系统声音、麦克风或两者都选。然后&#xff0c;进入“选项”&#xff0c;在设置下选择您的电…...

基于机器学习、人工智能和区块链技术的物联网安全

物联网安全&#xff1a;基于机器学习、人工智能和区块链技术的挑战和解决方案背景介绍物联网IoT基础设施物联网协议IoT 应用物联网面临的攻击三种技术下的物联网安全调研区块链机器学习人工智能物联网当前的挑战背景介绍 物联网(IoT)是过去十年在各种应用中使用最快的技术之一…...

windows录制android屏幕,如何使用Android Studio录制屏幕

如何使用Android Studio录制屏幕我将手机连接到Android Studio和代码。 我想记录我的手机屏幕。 我看到了这个&#xff0c;但是该按钮在我的Android Studio中被禁用。 我可以捕获屏幕&#xff0c;但无法录制。 有人可以帮我弄这个吗&#xff1f;UPDATE这就是我的Android工作室中…...

H3C认证网络工程师H3CNE

H3CNE&#xff08;H3C Certified Network Engineer&#xff0c;H3C认证网络工程师&#xff09;认证主要定位于中小型网络的规划、设计、配置与维护&#xff0c;通过H3CNE认证&#xff0c;将证明您对数据通信网络有全面深入的了解&#xff0c;掌握面向中小型企业的网络通用技术&…...

LeetCode 1.两数之和

题目&#xff08;8&#xffe5;&#xff09; 题目地址&#xff1a;https://leetcode-cn.com/problems/two-sum/ 题解 只需遍历一遍&#xff0c;在遍历的过程中用 HashMap 结构存储。 key 存数字的值&#xff0c;value 存数字在数组中的下标。 一边存储一边判断 HashMap 中是…...

文本聚类!

目录 第10章 文本聚类 10.1 概述 10.2 文档的特征提取 10.3 k均值算法 10.4 重复二分聚类算法 10.5 标准化评测 10.6 总结 第10章 文本聚类 上一章我们在字符、词语和句子的层级上应用了一些无监督学习方法。这些方法可以自动发现字符与字符、词语与词语、乃至句子与句…...

android手机操控及屏幕录制,手机操作视频记录 Android版屏幕录制

俗话说“每一个Android女的背后&#xff0c;都有一个帮她刷ROM的男人”&#xff0c;由此可见玩转Android确实是一项技术活&#xff0c;而作为手机玩家&#xff0c;在生活中我们也确实会遇到有人请教各种关于手机操作的问题&#xff0c;特别是异地网络指导对方操作就更加麻烦。以…...

数据结构的定义以及其相关概念

数据结构的定义 数据结构:存在一种或多种关系的数据类型&#xff08;我们老师说的&#xff09;。数据结构&#xff1a;设计的数据元素的集合以及数据元素之间的关系&#xff0c;由数据元素之间的关系构成结构。 因此&#xff0c;可以把数据结构看成是带结构的数据元素的集合&…...

Web前端 html css学习笔记(更新)

HTML CSS学习笔记2021/9/29网页简介HTML简介第一个网页自结束标签和注释文档声明进制字符编码完整的文档结构2021/9/30VScode安装及使用实体meta标签语义化标签2021/9/29 网页简介 服务器开发语言 JavaPHPNode.jsPythonC# 客户端形式 文字客户端图形化界面网页 不需安装无须更…...

极路由 连不上网 华三认证 h3c MD5-Challenge 中山大学

2018.03.162018.03.16今天连不上网&#xff0c;极路由的日志显示部分日志&#xff1a;[2] Server: Request MD5-Challenge! [2] Client: Response MD5-Challenge. [3] Server: Failure. errtype 0x00 [*] Client: Start. MD5校验方式改成0或1都没用&#xff0c;日志依然总是MD…...

华三IPsec

interface GigabitEthernet0/0 ip address 10.0.2 24 # interface GigabitEthernet 0/1 ip address 11.0.0.1 24 # interface GigabitEthernet0/0 ip address 10.0.0.1 255.255.255.0 nat outbound 2000 # interface GigabitEthernet0/1 ip address 192.168.1.254 255.255...

Java面向对象之equals 方法、hashCode 方法、toString 方法及finalize 方法

文章目录1、Object 类1.1、equals 方法1.1.1、 和 equals 的对比1.1.2、如何重写 equals 方法1.2、hashCode 方法1.2.1、hashCode 小总结1.2.2、代码实现1.3、toString 方法1.3.1、定义1.3.2、代码实现1.4、finalize 方法1.4.1、定义1.4.2、代码实现1、Object 类 1.1、equals …...

newifi3 web认证_新路由3(Newifi 3 D2)解锁刷PandoraBox教程

新路由3是一款百元以内非常超值的千兆二手路由器&#xff0c;采用了MT7621A处理器MT7612EN(5G芯片)&#xff0c;512MB内存和32MB存储空间&#xff0c;此外还搭载了Skyworks的SKY85717-21&#xff0c;它集成了PA与LNA。今天博主就教各位朋友如何把新路由3解锁并刷成PandoraBox。…...

第5章 IP基本原理1-H3C认证网络工程师(H3CNE)

TCP/IP协议栈的网络层位于网络接口层和传输层之间&#xff0c;其主要协议包括IP&#xff08;Internet Protocol&#xff0c;互联网协议&#xff09;、ARP&#xff08;Address Resolution Protocol&#xff0c;地址解析协议&#xff09;、RARP&#xff08;Reverse Address Reso…...

第4章 广域网基本原理-H3C认证网络工程师(H3CNE)

广域网&#xff08;Wide Area Network&#xff0c;WAN&#xff09;是随着相距遥远的局域网互联的要求产生的。广域网应该能延伸到比较远的物理距离&#xff0c;可以是城市范围、国家范围甚至于全球范围。分散在各个不同地理位置的局域网通过广域网互相连接起来。 早期局域网采…...

基因组学课件整理

1.什么是SNP和SSLP&#xff1f; SNP&#xff1a;即单核苷酸多态性&#xff0c;是由于基因组中等位位点上单个核苷酸改变而导致的核酸序列多态性(Polymorphism)。 SSLP&#xff1a;简单序列长度多态性&#xff0c;是一系列不同长度的重复序列&#xff0c;包括卫星DNA&#xff…...

论文笔记 - Invisible Backdoor Attack with Sample-Specific Triggers

文章目录 订制样本触发器方法的隐蔽式后门攻击基本信息论文贡献算法思路前提假设问题定义基本步骤实验结论(后续会整理实验)订制样本触发器方法的隐蔽式后门攻击 基本信息 论文标题Invisible Backdoor Attack with Sample-Specific Triggers作者Yuezun Li, Yiming Li, Baoyu…...

华三指定启动配置文件_华三交换机的一些配置命令

h3c_s3100-si系列的交换机h3c_s3100-si# 配置AUX接口的认证方式为本地口令认证。&ltH3C&gt system-viewSystem View: return to User View with CtrlZ.[H3C] user-interface aux0[H3C-ui-aux0] authentication-mode password# 显示当前用户界面的使用信息。&ltH3C&…...

【HUAWEIH3C】对比华为和华三的本地AAA登陆配置

其实这些知识是很简单的啦&#xff0c;但是知识还是需要总结的嘛。最近我在看华为的认证的知识&#xff0c;每看一点&#xff0c;做一次实验&#xff0c;我都会对比华为和华三的配置&#xff0c;甚至拿上思科的配置&#xff0c;一起做个参考。综合性的整理自己的知识。 图很简…...

学术论文的英文文献,国内外有哪些网站可以找得到?

英文文献怎么找&#xff0c;这也是之前令我很苦难的问题&#xff0c;中文文献查询常用的中国知网、万方、维普。这几个数据库一般在使用校园网&#xff08;学校购买&#xff09;的情况下可以方便使用&#xff0c;这里就不再赘述。除此之外这里学姐给大家推荐几个我常用的网站&a…...

华三的AC对接绿洲平台的无线认证配置

由于设备与云简网络的通信是基于解析云简网络域名&#xff0c;因此需要配置DNS服务器&#xff0c;host。 cloud-management server domain oasis.h3c.com dns server 114.114.114.114 ip host oasisauth.h3c.com 101.36.161.146 ip host oasis.h3c.com 101.36.161.141 查看AC…...

android屏幕录制功能,Android利用ADB进行屏幕录制

前言在写博客时&#xff0c;为了方便大家理解&#xff0c;我们经常需要把一些操作或动画录制成Gif&#xff0c;一般需要下载一个屏幕录制App将手机屏幕录制成视频(可能需要Root权限)&#xff0c;然后导出到电脑&#xff0c;再转为Gif。今天就来教大家一键录制手机屏幕并导出到电…...

最新视频打赏系统全开源版本源码+附教程

☑️ 编号&#xff1a;ym346 ☑️ 品牌&#xff1a;无 ☑️ 语言&#xff1a;php ☑️ 大小&#xff1a;18.4MB ☑️ 类型&#xff1a;视频打赏系统 ☑️ 支持&#xff1a;pcwap &#x1f389; 欢迎关注&#xff0c;私信&#xff0c;领取 &#x1f389; ✨ 源码介绍 最新视频打…...

MySQL之库表设计篇:一到五范式、BC范式与反范式详解

引言 MySQL的库表设计&#xff0c;在很多时候我们都是率性而为&#xff0c;往往在前期的设计中考虑并不全面&#xff0c;同时对于库表结构的划分也并不明确&#xff0c;所以很多时候在开发过程中&#xff0c;代码敲着敲着会去重构某张表结构&#xff0c;甚至大面积重构多张表结…...

RWA(现实世界资产):架起从DeFi到TradFi的桥梁

三大支柱&#xff1a;资产托管、流动性和信贷协议。本文来自 thetie&#xff0c;原文作者&#xff1a;Vaish Puri&#xff0c;由 Odaily 星球日报译者 Katie 辜编译。DeFi 正在慢慢扩大加密货币的业务边界&#xff0c;并在现实世界中产生影响。随着越来越多的资产被代币化&…...

C语言基础——初识C语言

目录 一、机器语言 二、汇编语言 三、高级语言 四、C语言的标准化 五、C语言特点 计算机&#xff0c;堪称是人类史上最伟大的技术结晶之一&#xff0c;继农业革命和工业革命之后&#xff0c;直接促进了人类社会的第三次革命——信息革命。它从1946年诞生至今&#xff0c;还不到…...

java实现多层嵌套循环参数转换

有这么一个需求&#xff0c;将一个二维的json对象&#xff0c;根据一定的配置&#xff0c;转化成多维度嵌套的json。如下图所示&#xff1a; 这种的我姑且称之为参数嵌套转化算法。 下面进入正题----上代码 一步到位难度比较大&#xff0c;我才去两端式转换。 首先我现将原始…...

jquery设置css,style样式

动态自定义css样式 //设置多个属性 用,隔开 $("#id").css({color:"red",background:"black"}) $("#id").css({"margin-left":"red","background-color":"black"}) //属性带有-必须使用引号…...

Idea 解决SVN冲突

Idea提交包的时候报包冲突先新建对应的包然后 选择Subversion> Reconvert subversion > ignore 忽略文件...

exit在c语言里的作用,C语言中exit函数的使用

exit() 结束当前进程/当前程序/&#xff0c;在整个程序中&#xff0c;只要调用 exit &#xff0c;就结束return() 是当前函数返回&#xff0c;当然如果是在主函数main, 自然也就结束当前进程了&#xff0c;如果不是&#xff0c;那就是退回上一层调用。在多个进程时.如果有时要检…...

【JavaEE初阶】之 多线程基础【上篇】

✨✨hello&#xff0c;愿意点进来的小伙伴们&#xff0c;你们好呐&#xff01; &#x1f43b;&#x1f43b;系列专栏&#xff1a;【JavaEE】 &#x1f432;&#x1f432;本篇内容&#xff1a;带你从0到1了解多线程基槽 &#x1f42f;&#x1f42f;作者简介:一名现大二的三非编程…...

C# wpf NotifyIcon空间模仿qqz最小化,关闭功能(12)

1&#xff0c;新建wpf项目 <Grid><StackPanel><TextBlock>这是这主程序&#xff0c;模仿qq 最小化功能</TextBlock><TextBlock>功能1&#xff1a;运行这个程序最小化托盘里面有这个程序的图标</TextBlock><TextBlock>功能2&#xf…...

用Unity进行网络游戏开发(一)

这是我之前写的了&#xff0c;一直保存在电脑里&#xff0c;现在学习写博客。希望多和大家交流&#xff0c;共同进步&#xff0c;文章中说得不好的地方请指出&#xff0c;谢谢&#xff01;使用Unity3D进行网络游戏开发一.Unity3d简介Unity3d是时下比较流行的一款游戏引擎&#…...

python画实心圆_在Matplotlib中以平面为中心绘制实心圆柱

I fit a plane to a bunch of points in 3d and initially gave it an arbitrary size using np.meshgrid, but now Im trying to plot a cylinder centered on that plane and oriented the same way (such that the plane fit would cut the height of the cylinder in half...

android开发板摄像头问题

经过一年的开发时间&#xff0c;最近看了一下摄像头的相关参数问题发现android开发板使用后置摄像头显示的是镜像而前置的不是。 转载于:https://my.oschina.net/u/2396529/blog/968033...

11月12日,一起见证关于边缘计算的顶级盛宴

号外&#xff01;号外&#xff01;第五届全球边缘计算大会将于11月12日&#xff08;周六&#xff09;在上海普陀区安曼纳卓悦大酒店召开&#xff0c;距离这场边缘计算年度顶级盛宴开幕仅剩两周啦&#xff01;这是我们最近三个月来最期待的事情&#xff01;为什么说这次全球边缘…...

python培训报道

千锋为中钞印制技术研究院 提供的Python数据分析内训圆满结束 收获了内训企业学员的无数好评 不得不说&#xff0c;在数字化快速发展的今天成为企业重要的资源&#xff0c;Python数据分析已成为企业决策的关键性支撑。 这次内训课程是通过千锋锋云智慧在线平台进行线上直播授…...

自动定时执行任务、定时任务执行的几种实现方法

自动定时执行任务&#xff0c;常见的方法有三种&#xff1a; Windows 任务计划程序Windows Service定时执行软件&#xff08;例如&#xff1a;定时执行专家&#xff09; 这三种方法大多数人都用过&#xff0c;我在这里只做一个比较简单的介绍和小结&#xff0c;后续&#xff…...

2022年金九银十互联网高级架构师Java面试八股汇总(附答案整理)

此文包含 Java 面试的各个方面&#xff0c;史上最全&#xff0c;苦心整理最全 Java 面试题目整理包括基础JVM算法数据库优化算法数据结构分布式并发编程缓存等&#xff0c;使用层面广&#xff0c;知识量大&#xff0c;涉及你的知识盲点。要想在面试者中出类拔萃就要比人付出更多…...

汉字转换拼音 java_java 汉字转换拼音

maven依赖com.belerwebpinyin4j2.5.0import net.sourceforge.pinyin4j.PinyinHelper;import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;import net.sourceforge.pinyin4j.format.HanyuPinyinToneT…...

数据库关系的规范化

在关系数据库中&#xff0c;所有的数据文件都以 二维表的形式存在&#xff0c;这些二维表之间通常会 产生数据冗余&#xff0c;这样容易造成数据的不一致 或不完整&#xff0c;从而使数据的检索、插入、删除 和更新和等操作可能会出现错误。解决这种 问题的一个办法就是将这些关…...

Svn常见问题

Svn常见问题 1. 隐藏文件.svn目录删除了怎么办Checkout后&#xff0c;工作空间下.svn目录下有大量隐藏文件&#xff0c;占用比较大的空间&#xff0c;他们是工作空间的管理文件&#xff0c;不能删除&#xff0c;如果不小心删除了也不要抓狂&#xff0c;不会影响服务器端的&…...

Spring Cloud Alibaba入门篇

学习条件了解web三层架构 熟练应用SSM架构 了解Maven管理工具的使用 熟练使用SpringBoot,以及了解SpringBoot基本原理。 了解部分术语:应用、工具、耦合、负载等温馨提示:注意版本问题,不复杂,就是呀呀的版本选不好,Game Over..... 没有从入门开始写,这点应该能入门,装…...

Adobe Illustrator CS6 直装版下载

http://d.pcsoft.com.cn/download/pc/AdobeIllustratorCS6.zip...

看小企业如何玩转大数据? 智慧商贸添助力

7月22日消息&#xff0c;美国有句谚语“除了上帝&#xff0c;任何人都必须用数据说话”&#xff0c;从目前来看&#xff0c;我们正处于一个大数据无限发展的时代&#xff0c;就连最热门的巴西世界杯冠军——德国&#xff0c;据传也是利用一款大数据分析“工具”为胜利添置更多筹…...

python中numpy zeros_为什么numpy.zeros和numpy.zeros的性能不同?

我在Ipython中的计时是(使用更简单的timeit接口)&#xff1a;In [57]: timeit np.zeros_like(x)1 loops, best of 3: 420 ms per loopIn [58]: timeit np.zeros((12488, 7588, 3), np.uint8)100000 loops, best of 3: 15.1 s per loop当我使用IPython(np.zeros_like??)查看代…...

其他服务或操作

Rabbitmq消息队列服务 rabbitmqctl add_user chinaskill rabbitmqctl set_permissions chinaskill “." ".” “.*” rabbitmqctl set_user_tags chinaskill administrator RabbitMQ集群 创建三台云主机 rabbitmq1 和 rabbitmq2 和 rabbitmq3 并配好 hosts 文件 ho…...

Python学习之文件操作

文件操作 说明:本blog绝大部分内容来自于Python数据分析与可视化(清华大学出版社&#xff0c;魏伟一&#xff0c;李晓红)&#xff0c;仅用于 自己学习使用。 文件处理过程 &#xff08;1&#xff09;打开文件&#xff1a;open()函数 &#xff08;2&#xff09;读取/写入文件…...

在ROS中使用Python3

原链接: https://community.bwbot.org/topic/499 运行测试平台:小强ROS机器人 当前ROS是只支持Python2.7的。Python3的支持在ROS的计划中&#xff0c;详细的可以看这里。简单说来就是要到2019年ROS的N版本才能完全支持Python3。 首先要了解为什么ROS不能支持Python3.对于纯的…...

Android之拍照后删除图片

以上为做的一个测试&#xff0c;拍摄照片后&#xff0c;android系统会在/storage/emulated/0/Pictures和/storage/emulated/0/DCIM/Camera两个路径下各创建一张照片 相关文章&#xff1a;android 删除图片后通知系统图库删除图片...

肠癌早筛学习笔记

我们注意到肠癌&#xff0c;日渐成为大家关注的话题&#xff0c;由于它难以被发现&#xff0c;并且越晚发现治疗效果越差&#xff0c;正逐渐成为大家关注的焦点。最近一直在关注肠癌早筛方面的内容&#xff0c;查阅了一些资料&#xff0c;记一下笔记&#xff0c;备忘&#xff0…...

fpga vivado fft/ifft ip核实现

timescale 1ns / 1psmodule fft(input aclk,input aresetn,output [7:0] fft_real,output [7:0] fft_imag,output [7:0] ifft_real,output [7:0] ifft_imag);//DDS corewire [15:0] dds_m_data_tdata;wire fft_s_data_tready;wire dds_m_data_tvalid;wire dds_m_data_tlast;dd...

JavaScript学习指南集锦

在过去的一年间&#xff08;2017年&#xff09;&#xff0c;我们对比了近24000篇 JavaScript 文章&#xff0c;并从中挑选出了最好的55篇。我们做了这个目录&#xff0c;认为阅读有经验的程序员写的文章是一个很好的学习方式。在学习了一两门课程之后&#xff0c;您可能在构建和…...

wazuh-monitord agent连接监控

ossecc -monitord程序监视agent的连接。此外&#xff0c;它每天或当内部日志达到一定的可配置大小时对其进行旋转和压缩。 1.掉线时间间隔配置 agent掉线时间间隔 <agents_disconnection_time>1m</agents_disconnection_time> agent掉线告警时间间隔 <…...

guid linux 识别的分区表_GUID分区与MBR分区有什么区别?

展开全部GUID分区与MBR分区是两种磁盘的组织方式&#xff0c;主要是分区信息信息储存方式、备份功能和分62616964757a686964616fe4b893e5b19e31333431366238区数量和最大容量上的区别。具体的介绍如下&#xff1a;MBR的意思是【主引导记录】&#xff0c;是IBM公司早年间提出的&…...

人生苦短,该是及时行乐?或是该苦尽甘来?

在开始阅读之前&#xff0c;先和大家讨论一个问题。&#xff08;欢迎大家将答案发在评论区&#xff09; 如果你的人生可以选择&#xff0c;你会选择&#xff1a; A. 及时行乐的人生 B. 苦尽甘来的人生 C. 否极泰来的人生在阅读这篇文章的同时&#xff0c;我希望大家可以作为一…...

微信小程序的登录流程

基本流程&#xff1a;1、调用 wx.login() 获取 临时登录凭证code &#xff0c;并回传到开发者服务器。2、调用 auth.code2Session 接口&#xff08;本接口应在服务器端调用&#xff09;&#xff0c;换取 用户唯一标识 OpenID 、 用户在微信开放平台帐号下的唯一标识 UnionID&am…...

mian函数

mian函数 什么是main函数 main函数&#xff0c;又称主函数&#xff0c;是程序执行的起点 程序从哪里开始执行-main函数 人生&#xff0c;就是一个程序。在不同的阶段&#xff0c;做不同的事。 每件事&#xff0c;就是一个函数。 每个人的人生都不相同&#xff0c; 以不同的顺…...

生日倒生日计时html代码,一款非常精美实用的生日倒计时代码

this.p{ m:2,b:2,loftPermalink:,id:fks_085065085086089066081095086095081086083069093087,blogTitle:一款非常精美实用的生日倒计时代码,blogAbstract:这里提供一款非常精美实用的生日倒计时代码,可以把你的生日日期替换到代码中,变成你的生日倒计时.这个代码也可以用做其他…...

视频压缩 ffmpeg

用到的开源库&#xff1a;https://github.com/WritingMinds/ffmpeg-android-java...

未压缩视频数据计算方式

未压缩视频数据计算方式1280 x 720的视频码率为604Mbps。我有一个6秒钟的不压缩视频大小为491MB。视频格式显示为RGB。码率的算法也很简单128072024(颜色位数)*30&#xff08;帧率&#xff09;663552000bps632Mbps。文件大小632&#xff08;码率&#xff09;*6&#xff08;秒&a…...

视频太大怎么在线压缩变小

一些比较大的视频放在手机中很容易占内存&#xff0c;那么我们是怎么进行视频压缩的呢&#xff1f;下面就简单给大家介绍一下。 步骤一&#xff1a;点击在线视频压缩&#xff0c;我们可以看到在线视频压缩的立即使用&#xff1b; 步骤二&#xff1a;我们可以进行文件的压缩设置…...

IB究竟在考什么?IB真的有传闻中的那么难吗?

提起学习“国际课程之王”IB的感受&#xff0c;学生们估计都能开一个“吐槽大会”了&#xff0c;IB很难&#xff0c;不少人学IB&#xff0c;GET到的第一个技能&#xff0c;就是“熬夜”。▲IB学生做的搞笑图&#xff1a;凌晨三点&#xff0c;唯一一扇亮着灯的窗户一定是属于IB学…...

写一个类似大众点评的城市选择控件

来自Leo的原创博客&#xff0c;转载请著名出处 我的stackoverflow 最终效果 当然&#xff0c;这个项目也支持push 和 present方式选择 源代码 LHCityPickerController 支持 搜索所有县级市快速索引自定义热门城市自动保存访问历史&#xff08;最多6个&#xff09;提供接口…...

Python案例:查询城市天气并绘制最高气温与最低气温折线图

Python案例:查询城市天气并绘制最高气温与最低气温折线图 一、解决思路 比如要查询“泸州”的天气。 1、首先获取泸州的城市代码 http://toy1.weather.com.cn/search?cityname=泸州 其中“?cityname=泸州 ”是查询字符串。 在返回的数据里,看第一项数据的ref值,其开头…...

基于STM32单片机的智能家居测量系统设计

当今的家庭生活面临着各种环境和健康问题&#xff0c;周围的生活参数存在潜在的隐患&#xff0c;包括室温、气体中有害物质的浓度等。在新时代&#xff0c;人们越来越关注健康及其相关因素。随着微电子技术的应用&#xff0c;电器的普及&#xff0c;以及单片机和传感器性能的快…...

ACS基于Tr069协议的通信终端测试和管理平台

编写目的用户使用本系统对Cpe进行操作。本系统适用于电信、移动、联通&#xff08;EPON、GPON、ETH上行接口&#xff09;各种型号的Cpe。为整新、检测Cpe的用户提供方便、快捷、高效的生产工具。 项目背景组网后终端PC可以通过&#xff08;ONU-分光器-Olt或ETH上行&#xff09…...

移动终端基础数据管理系统

一、背景 随着移动设备硬件和移动互联技术的发展&#xff0c;移动设备在城市管理工作中广泛应用&#xff0c;通过移动GIS技术将规划数据与空间信息在移动端展现&#xff0c;便于领导和设计人员在移动终端上查阅。移动系统在给我们带来便利的同时&#xff0c;也面临着数据和设备…...

一、微信支付介绍和接入指引

目录一、微信支付介绍和接入指引1、微信支付产品介绍1.1、付款码支付1.2、JSAPI支付1.3、小程序支付1.4、Native支付1.5、APP支付1.6、刷脸支付1.7、申请费用2、接入指引申请流程简述接入微信支付&#xff08;以小程序为例&#xff09;2.1、获取登录账户2.2、获取AppID2.3、绑定…...

微信小程序 - 核心

1、Web页面 2、项目结构 Ps1&#xff1a;应用程序的三个文件app.js、app.json、app.wxss在整个小程序中是唯一的&#xff0c;是全局的&#xff1b;页面级别的会覆盖全局的样式和json&#xff08;配置&#xff09;,就近原则。 Ps2&#xff1a;project.config.json是自动生成的,…...

android微信下拉出现小程序,Android仿微信首页下拉显示小程序列表

花点时间重新熟悉一下AndroidUI方面的东西&#xff0c;把古董PullToRefreshView又撸了一遍&#xff0c;技术这种东西真是忘得快啊...在基础上新增一点东西&#xff0c;粗糙地实现了仿微信首页下拉显示小程序列表的样式&#xff0c;是的&#xff0c;粗糙粗糙...PullToRefreshVie…...

HTML5和JAVA在微信,html5 调起微信支付

本文主要是梳理在微信浏览器中调起微信支付的整个过程&#xff0c;以及主要过程当中界面的展现效果。虽然整个交易过程看起来很简单&#xff0c;就是输入金额&#xff0c;而后调起微信支付&#xff0c;输入密码完成交易&#xff0c;可是在实现过程当中&#xff0c;是须要对接微…...

容器编译apache,部署web

部署httpd [rootlocalhost ~]# mkdir /data [rootlocalhost ~]# cd /data/ [rootlocalhost data]# wget https://mirrors.aliyun.com/apache/apr/apr-1.6.5.tar.gz --2022-08-11 09:12:01-- https://mirrors.aliyun.com/apache/apr/apr-1.6.5.tar.gz Resolving mirrors.aliyu…...

ngrok内网穿透教程 -- 将本地IP映射成对外可访问的域名

下载安装 打开官网 https://ngrok.com/ 首先注册账号&#xff0c;之后下载安装&#xff0c;windows版本直接解压.zip文件 配置 双击安装的 ngrok.exe 文件&#xff0c;进入如下命令窗口&#xff0c;根据官网给出的命令&#xff0c;运行连接自己的帐号&#xff0c;保存 authto…...

Docker源码部署Web服务自启动

Docker源码部署Web服务自启动 文章目录基于容器源码部署httpd基于容器制作镜像测试镜像&#xff0c;部署家具商城网站基于容器源码部署httpd //下载编译安装httpd所需的源码包 [rootlocalhost ~]# mkdir /data [rootlocalhost ~]# cd /data/ [rootlocalhost data]# wget https…...

容器内源码部署httpd

docker源码部署httpd 文章目录容器内源码部署httpd制作镜像测试镜像容器内源码部署httpd //拉取centos的镜像到本地仓库 [rootlocalhost ~]# docker pull centos Using default tag: latest latest: Pulling from library/centos a1d0c7532777: Pull complete Digest: sha256…...

在容器中部署一个web站点

在容器中部署一个web站点 文章目录在容器中部署一个web站点编译安装apache制作镜像测试镜像&#xff0c;并部署网页编译安装apache 拉取centos镜像 [rootlocalhost ~]# docker pull centos:8 8: Pulling from library/centos a1d0c7532777: Pull complete Digest: sha256:a2…...

【超详细】Docker从入门到干活,就看这一篇文章

容器简介 什么是 Linux 容器 Linux容器是与系统其他部分隔离开的一系列进程&#xff0c;从另一个镜像运行&#xff0c;并由该镜像提供支持进程所需的全部文件。 容器提供的镜像包含了应用的所有依赖项&#xff0c;因而在从开发到测试再到生产的整个过程中&#xff0c;它都具…...

Devops常用工具软件之ansible部署使用

一、背景 因某业务环境部署agent采集&#xff0c;且OS多为Linux&#xff0c;部分windows&#xff1b;考虑采用ansible进行批量部署&#xff1b; 二、概要 ansible作为一款自动化运维工具&#xff0c;它是基于Python开发&#xff0c;集合了众多运维工具&#xff08;puppet、c…...

docker镜像管理与镜像制作

docker镜像管理与镜像制作 文章目录docker镜像管理与镜像制作1:镜像制作2&#xff1a;commit制作镜像3&#xff1a; 镜像上传dockerhub4:dockerfile制作镜像1:镜像制作 制作属于自己的Docker镜像&#xff0c;一般有两种方式&#xff1a; 第一种为commit方式&#xff0c;利用已…...

2021 HECTF 部分WP

MISC 快来公众号ya 关注公众号回复签到 SangFor{AaKjtQr_OjJpdA3QwBV_ndsKdn3vPgc_}JamesHarden 一个未知文件&#xff0c;拖入010 发现是zip的前缀&#xff0c;改个文件后缀压缩 是一个.class文件&#xff0c;没见过&#xff0c;用记事本打开 发现疑是凯撒密码的字符串…...

让Windows加倍好看

前言 之前也给大家分享过好多期美化桌面的小工具 包括改图标、任务栏、壁纸、桌面整理等等但这些对桌面外观的改变都很小&#xff0c;要想进行Windows整体风格的美化&#xff0c;修改主题是最方便的 主题美化之前没发过&#xff0c;今天给大家带来大量Windows主题 Win7-Win…...

【2022最全最细】Docker 从入门到精通(建议收藏的教程)

docker不是一个值得投入的领域&#xff0c;它解决的问题是Unix系统最初设计的一个疏忽。从一个不会用docker的小白&#xff0c;自己一步一步的摸索&#xff0c;中间也踩过许多坑。但任然&#xff0c;坚持从哪里跌倒就从哪里爬起来。不求感动自己&#xff0c;但求人生无悔。 1 容…...

Docker基于apache镜像 使用存储挂载 制作web站点

在容器中部署安装HTTP服务 把官方源换成阿里云源 [rootbfc8e35857cb /]# cd /etc/yum.repos.d/ [rootbfc8e35857cb yum.repos.d]# rm -rf * [rootbfc8e35857cb yum.repos.d]# ls -l total 0 [rootbfc8e35857cb yum.repos.d]# curl -o /etc/yum.repos.d/CentOS-Base.repo http…...

最新天龙八部环境-GS环境教程-【长期稳定版本】

前言&#xff1a;本环境开源免费&#xff0c;无毒无后门。有疑问或者BUG&#xff0c;请提交到is https://gitee.com/yulinzhihou/gstlenv/issues 不要再猜测安装了环境被远控&#xff0c;被注入偷取服务端&#xff0c;偷取数据库&#xff0c;远程注入木马及其他程序的可能 开…...

dockers部署web站点

dockers部署web站点 文章目录dockers部署web站点环境需求拉取centos镜像基于centos镜像启动一个容器并进入在容器中部署Apache服务下载并解压APR源码包&#xff0c;编译安装APR下载并解压httpd源码包&#xff0c;编译安装httpd配置Apache服务&#xff0c;验证服务基于容器制作镜…...

[青少年CTF]Crypto—Easy by 周末

闲的没事写份wp(&#xff5e;&#xffe3;▽&#xffe3;)&#xff5e; 个人博客&#xff1a;https://www.st1ck4r.top 0x01 一起下棋 考点&#xff1a;棋盘密码 在线解密&#xff1a;https://www.qqxiuzi.cn/bianma/qipanmima.php 0x02 emoji 考点&#xff1a;Emoji表情符…...

Debezium同步之oracle rac数据到oracle单机的安装步骤

1、创建目录 [rootdbz ~]# mkdir /dbz 2、上传执行文件&#xff0c;文件内容如下&#xff1a; [rootdbz ~]# cd /dbz [rootdbz dbz]# ll total 4 -rwxr-xr-x 1 root root 1827 Apr 28 11:14 startdbz1.8.sh 编辑startdbz1.8.sh执行文件 [rootdbz dbz]# vi startdbz1.8.sh #…...

Ubuntu安装Oracle时解压报错,Ubuntu 14.04搭建PHP5+Apache2+Oracle及Oracle管理软件SQL Developer...

Ubuntu 14.04搭建PHP5Apache2Oracle环境最近开发的一个项目&#xff0c;数据库使用Oracle。Oracle本身支持Red Hat&#xff0c;对Ubuntu的支持并不好&#xff0c;如果Ubuntu需要安装Oracle&#xff0c;系统本身需要做伪装等很多工作&#xff0c;所以我只打算使用远程服务器上的…...

wamp扩展php zip.dll,WAMP环境中扩展oracle函数库(oci)

同事昨天接到一个任务,要用php处理oracle数据库的内容,但是php打开oracle扩展不是像mysql那样直接用就行,需要下一点东西才能打开第一步 需要到oracle官方下载一个install client 包,在win下找到你对应系统版本的zip(注意这里是系统版本)例如选择 Instant Client for Microsoft…...

Java算法——判断一个数是否是回文数

原文:https://blog.csdn.net/beyond1123/article/details/52176240?utm_mediumdistribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.control&dist_request_id&depth_1-utm_sourcedistribute.pc_relevant.none-task-blog-BlogCommendFromMachine…...

回文数算法分析

判断一个整数是否是回文数。回文数是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左&#xff09;读都是一样的整数。 示例 1: 输入: 121 输出: true示例 2: 输入: -121 输出: false 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个…...

java判断一个数是否是回文数_java编写判断是否是回文数

一个五位数&#xff0c;要你用java编写程序判断它是不是回文数你知道代码是怎样的吗?下面给大家分享的就是这方面的一道java编程题目&#xff0c;一起来看看题目以及解题方法吧。一、题目下面是具体的题目&#xff0c;大家要详细看看哦。一个5位数&#xff0c;判断它是不是回文…...

【Mybatis 使用】mybatis-config.xml 配置(properties 和 settings)

【Mybatis 使用】mybatis-config.xml 配置&#xff08;properties 和 settings&#xff09; 文章目录【Mybatis 使用】mybatis-config.xml 配置&#xff08;properties 和 settings&#xff09;一、可配置的属性二、properties&#xff08;属性&#xff09;1. 基础配置2. 外部配…...

python求100到200的回文数_Python计算回文数的方法

本文实例讲述了Python计算回文数的方法。分享给大家供大家参考。具体如下&#xff1a;这里检查数字是不是回文数&#xff0c;用196算法生成一个数字的回文数num 905;def is_Palindrome(num):"""判断一个数字是不是回文数&#xff0c;这里有些取巧了:param num:…...

python找出回文数_使用python实现回文数的四种方法小结

回文数就是指整数倒过来和原整数相等。 Example 1:Input: 121Output: true Example 2:Input: -121Output: falseExplanation: From left to right, it reads -121. From right to left, it becomes 121-. Therefore it is not a palindrome. Example 3:Input: 10Output: falseE…...

五位数回文数c语言程序,五位数的回文数有多少个

2018-12-16数学中有哪些回文数&#xff1f;简介折叠编辑本段回文数是指一个像16461这样“对称”的数&#xff0c;即&#xff1a;将这个数的数字按相反的顺序重新排列后&#xff0c;所得到的数和原来的数一样。这里&#xff0c;“回文”是指像“妈妈爱我&#xff0c;我爱妈妈”这…...

Java算法:回文数

一个正整数&#xff0c;如果交换高低位以后和原数相等&#xff0c;那么称这个数为回文数。比如 121121&#xff0c;23322332都是回文数&#xff0c;13,456713,4567 不是回文数。 任意一个正整数&#xff0c;如果其不是回文数&#xff0c;将该数交换高低位以后和原数相加得到一…...

c语言里判断回文数的函数,(C语言)回文数的判断

问题描述&#xff1a;判断一个数是否为回文数&#xff1b;121&#xff1b;12321&#xff1b;1234321&#xff1b;程序分析&#xff1a;1.回文数(palindromic number)&#xff1a;是指一个数的最高位和最低位上的数相等&#xff0c;第二高位与次低位上的数相等&#xff0c;也就是…...

C语言中判断回文数用数组的方法,【C语言】回文数和回文字符串的判断

一、名词解释&#xff1a;如果一个数正着反着都是一样,就称为这个数是回文数。例如:6, 66, 606, 6666同理如果一个字符串正着反着都是一样就称为这个字符串是回文字符串&#xff0c;例如“aba”&#xff0c;"aabb"二、解题思路回文数解题思路一&#xff1a;可以将整数…...

C语言求三位数的回文数,回文数 C语言求解

#9unsigned long getRevNum(unsigned long x, int n){unsigned long y0;for(i0;i{ y*10;yx%10;}return y;}unsigned long sumrev(unsigned long x, int n)return x getRevNum(x,n);)int isplalindrome(unsigned long x,int n){ int i,j1;int res1;for(i1;iif(xfor(i10;i<...

10000内的回文数c语言,回文数

“回文”是指正读反读都能读通的句子&#xff0c;它是古今中外都有的一种修辞方式和文字游戏&#xff0c;如“我为人人&#xff0c;人人为我”等。在数学中也有这样一类数字有这样的特征&#xff0c;成为回文数(palindrome number)。[1]设n是一任意自然数。若将n的各位数字反向…...

Rust中的所有权是什么

文章目录所有权规则变量作用域内存与分配变量与数据交互的方式移动克隆所有权&#xff08;系统&#xff09;是 Rust 最为与众不同的特性&#xff0c;对语言的其他部分有着深刻含义。它让 Rust 无需垃圾回收&#xff08;garbage collector&#xff09;即可保障内存安全&#xff…...

java学习计划

java学习计划 虚拟机软件vmware 后端全部&#xff0c;从架构开始&#xff0c;开发&#xff0c;部署&#xff0c;调优 https://baijiahao.baidu.com/s?id1711770044606618180&wfrspider&forpc 知乎&#xff0c;腾讯技术总监推荐&#xff1a;https://www.zhihu.com/que…...

springboot的web练手项目,适合新手,以及初级程序员项目实战,也适合老手进行二次开发的众多项目

文章目录6个非常实用的OAuth开源项目7个有视频和博文的项目综合项目练手12个高质量二次开发必备后台管理系统项目6个非常实用的OAuth开源项目 1.oauth2-shiro 项目地址&#xff1a;https://gitee.com/mkk/oauth2-shiro 整合Apache Oltu 与 Shiro&#xff0c;提供一个轻量的OA…...

idea10个常用的Debug技巧

文章目录一 回到上一步二 字段断点三 Stream调试四 表达式结果查看五 debug筛选条件六 异常断点七 远程调试八 强制返回九 运行时修改变量十 多线程调试重用快捷键一 回到上一步 进行代码调试的过程中&#xff0c;有的时候由于自己点击下一步的速度比较快&#xff0c;可能之前…...

SpringBoot项目分享及其分析

我今天分享是一个github的开源的Springboot项目&#xff0c;采用的是SpringbootVuemybatis-plus的架构体系&#xff0c;完全属于前后端分离的经典之作。 文章目录项目简介前端项目搭建项目简介 项目名字&#xff1a;SmartAdmin SmartAdmin由河南洛阳 1024创新实验室团队研发的…...

当前java web架构形式_GitHub - jack-zdl/horus-web: 这是独立的horus监控系统,无状态的javaweb工程,使用spring,springmvc后端框架,Smar

horus-web这是持续开发的独立的horus监控系统&#xff0c;无状态的javaweb工程&#xff0c;使用spring,springmvc后端框架&#xff0c;SmartAdmin的前端框架。spring的技术如下 IOC容器DI依赖注入 AOP切面编程&#xff0c;事物管理&#xff0c;及SpringMVC技术。1springIOC技术…...

smartadmin mysql_GitHub - xiangnanhai/smart-admin: SmartAdmin 使用 SpringBoot和Vue,前后端分离,我们希望用一套漂亮的代码和一

简介SmartAdmin由河南洛阳 1024创新实验室团队研发的一套互联网企业级的通用型中后台解决方案&#xff01;使用最前沿的前后台技术栈SpringBoot和Vue&#xff0c;前后端分离&#xff0c;我们开源一套漂亮的代码和一套整洁的代码规范&#xff0c;让大家在这浮躁的代码世界里感受…...

smartadmin mysql_GitHub - 601404501/smart-admin: SmartAdmin 使用 SpringBoot和Vue,前后端分离,我们希望用一套漂亮的代码和一套整

简介SmartAdmin由河南洛阳 1024创新实验室团队研发的一套互联网企业级的通用型中后台解决方案&#xff01;使用最前沿的前后台技术栈SpringBoot和Vue&#xff0c;前后端分离&#xff0c;我们开源一套漂亮的代码和一套整洁的代码规范&#xff0c;让大家在这浮躁的代码世界里感受…...

smartadmin mysql_GitHub - 1024-lab/smart-admin: SmartAdmin 使用 SpringBoot和Vue,前后端分离,我们希望用一套漂亮的代码和一套整洁

简介SmartAdmin由河南洛阳 1024创新实验室团队研发的一套互联网企业级的通用型中后台解决方案&#xff01;使用最前沿的前后台技术栈SpringBoot和Vue&#xff0c;前后端分离&#xff0c;我们开源一套漂亮的代码和一套整洁的代码规范&#xff0c;让大家在这浮躁的代码世界里感受…...

smartadmin mysql_smart-admin: SmartAdmin 使用 SpringBoot和Vue,前后端分离,我们希望用一套漂亮的代码和一套整洁的代码规范,让大家在这浮躁的代码世界

简介SmartAdmin由河南洛阳 1024创新实验室团队研发的一套互联网企业级的通用型中后台解决方案&#xff01;使用最前沿的前后台技术栈SpringBoot和Vue&#xff0c;前后端分离&#xff0c;我们开源一套漂亮的代码和一套整洁的代码规范&#xff0c;让大家在这浮躁的代码世界里感受…...

springboot拿来即用的项目

不得不佩服 Spring Boot 的生态如此强大&#xff0c;今天我给大家推荐几款 Gitee 上优秀的后台开源版本的管理系统&#xff0c;小伙伴们再也不用从头到尾撸一个项目了&#xff0c;简直就是接私活&#xff0c;挣钱的利器啊。 SmartAdmin 我们开源一套漂亮的代码和一套整洁的代…...

这 6 个 SpringBoot 项目够经典!

来源&#xff1a;http://suo.im/6i95ny前言不得不佩服 SpringBoot 的生态如此强大&#xff0c;今天给大家推荐几款优秀的后台管理系统&#xff0c;小伙伴们再也不用从头到尾撸一个项目了。SmartAdmin我们开源一套漂亮的代码和一套整洁的代码规范&#xff0c;让大家在这浮躁的代…...

jsqlparser:基于语法分析实现SQL中的CAST函数替换

最近遇到一个问题&#xff0c;应用层提供的SQL语句中有CAST(local_time AS DATE)这样的语句&#xff0c;在MySQL中执行肯定是没问题的&#xff0c;但是后台数据库切换到了HBase,使用apache phoenix 提供的JDBC驱动访问时却报错了&#xff0c;按照phoenix官方的文档&#xff0c;…...

结构型模式大全(Java讲解)

文章目录适配器&#xff08;adapter&#xff09;模式 &#xff08;常用&#xff09;代理模式 &#xff08;常用&#xff09;分类&#xff1a;静态代理(静态定义代理类)动态代理(动态生成代理类)&#xff1a;桥接模式(bridge)组合模式(composite)装饰器模式(decorator) &#xf…...

js 对象数组排序

var person [{name:"Rom",age:12},{name:"Bob",age:22},{name:"Ma",age:5},{name:"Tony",age:25}]person.sort((a,b)>{ return a.age-b.age})//升序person.sort((a,b)>{ return b.age-a.age})//降序...

JavaScript数组排序,翻转数组和冒泡排序

下面是JavaScript数组排序中实现翻转数组和冒泡排序的实例&#xff1a; 1、翻转数组 定义一个数组arr var arr [pink, red, blue]; arr.reverse();//直接用提供的方法 console.log(arr); 2、冒泡排序 var arr1 [3, 7, 6, 11, 8]; arr1.sort(function (a, b) { return…...

JS数组对象排序(es6)

效果&#xff1a;升序&#xff1a; 降序&#xff1a; 升序是&#xff1a;a.value-b.value 降序是&#xff1a;b.value-a.value 代码&#xff1a; let arrObj[{"name": "银行转账","value": 2},{"name": "支付宝支付","…...

SVG 语法入门教程(绘制矩形、圆形、椭圆、多边形等)

文章目录一、SVG 简介1.1 什么是 SVG &#xff1f;1.2 SVG 的历史和优势1.3 查看 SVG 文件1.4 创建SVG文件1.5 学习之前应具备的基础知识二、SVG 实例三、在 HTML 中嵌入 SVG3.1 使用 \<embed> 标签3.2 使用 \<object> 标签3.3 使用 \<iframe> 标签3.4 直接在…...

从0开始学python -23

Python3 条件控制 Python 条件语句是通过一条或多条语句的执行结果&#xff08;True 或者 False&#xff09;来决定执行的代码块。 可以通过下图来简单了解条件语句的执行过程: 代码执行过程&#xff1a; if 语句 Python中if语句的一般形式如下所示&#xff1a; if conditi…...

访问学者申请信标准模版

今天知识人网访问学者小编为大家分享一封标准的申请信模版&#xff0c;希望大家注意格式的书写&#xff1a;意向导师名称(如Dr. XXX)意向单位名称意向单位地址(如XXX号XX路XX市XX省)年月日开头(Dear XXX)&#xff0c;第一部分结合自己的研究兴趣向该单位的访学职位表示浓厚的兴…...

数字时代,VR云招聘“零见面”火了

招聘信息刷了又刷&#xff0c;上班谈、下班聊&#xff0c;终于到了可以预约时间来公司面试时&#xff0c;一句“我考虑下”或干脆没回音了&#xff0c;直接把HR打回原地。新年伊始&#xff0c;随着疫情的放开&#xff0c;金三银四的求职、招聘旺季仿佛也提前到来&#xff0c;但…...

Hudi(20):Hudi集成Flink之可以离线进行的操作

目录 0. 相关文章链接 1. 离线 Compaction 1.1. 设置参数 1.2. 原理 1.3. 使用方式 1.3.1. 执行命令 1.3.2. 参数配置 1.3.3. 案例演示 2. 离线 Clustering 2.1. 设置参数 2.2. 原理 2.3. 使用方式 2.3.1. 执行命令 2.3.2. 参数配置 2.3.3. 案例演示 0. 相关文章…...

【Spring-Security】详细使用用指南

Spring-Security使用详细指南简介简单入门认证登录校验流程原理解读认证流程登录校验&#xff1a;授权授权基本流程授权实现限制访问资源所需的权限简介 Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。 在安全框架中&#xff0c;我们最常提到的两个概…...

sd卡图片损坏怎么修复?

在旅途中&#xff0c;正常情况下用相机拍的照片都是存在相机的SD卡里的。等到我们需要时&#xff0c;在进行导出。但如果是出现意外导致sd卡图片遭到损坏&#xff0c;遇到这种情况&#xff0c;sd卡图片损坏怎么修复呢?这里小编将为大家分享一些图片修复技巧。操作很简单。相信…...

博物馆ar景点创意化交互体验的优点

一直以来&#xff0c;博物馆注重展览深度的拓展和藏品价值的挖掘&#xff0c;而缺少传播与推广。“博物馆的研究成果和藏品价值应该让公众有更多机会和渠道了解。 在博物馆中&#xff0c;为了保护古代文物不受到破坏而收藏到展柜中&#xff0c;参观者因受到展柜的距离限制无法近…...

一道经典面试题透彻理解面向对象编程思想和简单工厂模式

一道经典的面试题如下&#xff1a; 用一种面向对象编程语言实现一个计算器来满足整数的加减乘除运算。 大部分人的代码如下&#xff1a; 1.0版本 #include<iostream> using namespace std; #include<string> //1.0版本 int main() {int num1 0;int num2 0;st…...

Java软件开发需要掌握的技术分阶-2023版

简单的东西考虑全面&#xff0c;复杂的东西分成简单&#xff0c;枪在手&#xff0c;路在前&#xff0c;独闯难关&#xff0c;大破无往 第一阶段 计算机理论基础&#xff1a;操作系统、网络安全、数据结构与算法、硬件与软件基本常识 Java&#xff1a;各环境部署与程序运行原理…...

使用 React hooks 怎么实现类里面的所有生命周期?

在 React 16.8 之前&#xff0c;函数组件也称为无状态组件&#xff0c;因为函数组件也不能访问 react 生命周期&#xff0c;也没有自己的状态。react 自 16.8 开始&#xff0c;引入了 Hooks 概念&#xff0c;使得函数组件中也可以拥有自己的状态&#xff0c;并且可以模拟对应的…...

shell脚本简介+编写

1、常用Linux命令 2、Linux下脚本编写 3、windows下CMD常用命令 文章目录一、变量1、系统预定义变量2、自定义变量3、特殊变量&#xff1a;n、n、n、#、∗、*、∗、、$?二、运算符三、条件判断1、两个整数之间比较2、文件权限判断3、文件类型判断4、多条件判断四、流程控制1、…...

新项目如何提交代码到已有仓库,并创建新分支

1、步骤 2、创建远程仓库或者打开已有仓库页面 3、打开工程&#xff0c;创建本地git仓库 4、本地仓库与远程仓库关联 1、新建项目&#xff0c;此时的项目工程如下图所示 2、创建远程仓库或者打开已有仓库页面 此时在新建仓库或者已有仓库页面复制好仓库地址&#xff08;仓库…...

分享36个JS滚动,29个JS进度条,12个JS日历代码,总有一款适合您

分享36个JS滚动&#xff0c;29个JS进度条&#xff0c;12个JS日历代码&#xff0c;总有一款适合您 36个JS滚动29个JS进度条12个JS日历代码下载链接&#xff1a;https://pan.baidu.com/s/1zvSK9EAPd4dnMRl7V4Cc1g?pwdsu9i 提取码&#xff1a;su9i Python采集代码下载链接&a…...

ElasticSearch-学习笔记03【ElasticSearch集群】

Java后端-学习路线-笔记汇总表【黑马程序员】ElasticSearch-学习笔记01【ElasticSearch基本介绍】【day01】ElasticSearch-学习笔记02【ElasticSearch索引库维护】ElasticSearch-学习笔记03【ElasticSearch集群】ElasticSearch-学习笔记04【Java客户端操作索引库】【day02】Ela…...

MySQL面试题:保证MySQL隔离性的MVCC

文章目录一、MVCC是什么二、为什么需要MVCC三、InnoDB中的MVCC学习视频&#xff1a;后端面试问题系列参考文章&#xff1a;MVCC 机制的原理及实现一、MVCC是什么 MVCC机制&#xff1a;生成一个数据快照&#xff0c;并用这个快照来提供一定级别的一致性的读取&#xff0c;也称为…...

基于RNN文本生成 为男朋友写诗歌 附代码 详细教程

一、亮出效果 世界上美好的事物很多,当我们想要表达时,总是感觉文化底蕴不够。 看到大海时,心情舒畅,顿时感觉激情澎湃,想了很久,说了句:真大啊!看到鸟巢时,心情舒畅,顿时感觉激情澎湃,想了很久,说了句:真大啊!看到美女时,心情舒畅,顿时感觉激情澎湃,想了很久…...

2.5 Java基础 day06 数组 九层妖塔数组实现 字符串的拼接、比较等方法

数组 一维数组&#xff1a; String[] nums new String[3]; // //nums[0] 1; // for (Object num : nums) { // System.out.println(num); // } // String[] names {"zhangsan", "lisi", "wangwu"}; …...

中创教育带你了解培训机构投诉退费纠纷增多原因

教育培训机构纠纷近年来案件越来越多&#xff0c;主要聚焦于退费问题。交费容易&#xff0c;退费难上加难&#xff0c;为此&#xff0c; 提醒各位学员在选择学历提升、学科类或职业技能、职业资格类教育培训机构时要注意查看对方是否已经取得教育行政部门或人力社保部门颁发的…...

【CSDN竞赛26】题解

文章目录等差数列题目描述解题思路AC代码阿波罗的魔力宝石题目描述解题思路AC代码任务分配问题题目描述解题思路AC代码单词逆序题目描述解题思路AC代码CSDN编程竞赛报名地址&#xff1a;https://edu.csdn.net/contest/detail/40 等差数列 题目描述 一个等差数列是一个能表示成…...

Visual Transformer算法汇总总结

深度学习知识点总结 专栏链接: https://blog.csdn.net/qq_39707285/article/details/124005405 此专栏主要总结深度学习中的知识点&#xff0c;从各大数据集比赛开始&#xff0c;介绍历年冠军算法&#xff1b;同时总结深度学习中重要的知识点&#xff0c;包括损失函数、优化器…...

【2】深度学习之Pytorch——数据类型、索引张量、Numpy的互通性的概念、序列化张量

目录数据类型创建时指定数据类型转换数据类型索引张量numpy与pytorch的互通性序列化张量保存加载将张量转移到GPU上运行张量常见的API总结每文一语本期文章我们继续介绍张量的数据类型和一些基本的操作 数据类型 张量构造函数&#xff08;即tensor、ones、zeros之类的函数&am…...

Pandas读取excel合并单元格的正确姿势(openpyxl合并单元格拆分并填充内容)

问题介绍&#xff08;ffill填充存在的问题&#xff09; 在pandas读取excel经常会遇到合并单元格的问题。例如&#xff1a; 此时使用pandas读取到的内容为&#xff1a; 如果去百度&#xff0c;几乎所有人会说应该用如下代码&#xff1a; df[班级] df[班级].ffill()这样看起来…...

jsp员工管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 jsp 员工管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开 发&#xff0c;数据库为Mysql&#xff0c;使用ja…...

Shebang Line解释行

Shebang Line Shebang Line也被叫做 Hashbang Line&#xff0c;主要是一个由井号#和叹号!开头&#xff0c;并构成的字符序列&#xff0c;如#! xx/xx/x就叫做 Shebang Line。在开头字符之后&#xff0c;可以有一个或数个空白字符。这个字符串&#xff0c;通常只出现在 Script 文…...

片内和片间时间同步,时间戳

时间同步的概念给出几个应用场景&#xff1a;GPS授时车载系统 传感器与处理器之间的时间同步汽车上的各个ECU基本都是实时性非常强的控制器&#xff0c;在关联ECU之间或ECU内部各个软件模块之间通常需要在大致同步的时间节拍上运行&#xff0c;特别是在某些高速场景&#xff0c…...

通过 Microsoft Visual Studio 构建NotepadFree

通过 Microsoft Visual Studio 构建NotepadFree 前置要求: Microsoft Visual Studio 2019 (C/C Compiler, v142 toolset for win32, x64, arm64) 由一个 Visual Studio 解决方案构建的三个组件&#xff1a; notepad.exe: (包含 libSciLexer.lib) libScintilla.lib : 基于 Sc…...

给大家推荐一些非常实用的JavaScript、TypeScript一行代码,建议收藏

给大家推荐一些非常实用的JavaScript、TypeScript一行代码&#xff0c;建议收藏一、数组相关1、数值类型转数组2、校验数组是否为空3、将对象数组转为单个对象4、两个数组比较5、将字符串数组转为数字6、统计一个值在数组出现的次数二、日期处理1、两个日期相差月份2、两个日期…...

One-Hot 的使用

Sklearn 中 OneHotEncoder 的使用&#xff1a; import numpy as np from sklearn.preprocessing import OneHotEncoder samples np.array([ [1, 3, 2], [7, 5, 4], [1, 8, 6], [7, 3, 9] ]) # 独热编码 sparse 是否采用稀疏矩阵 ohe OneHotEncoder(sparseFalse, dtype&quo…...

Springboot整合AOP和注解,实现丰富的切面功能

简介 我们在文章《Spring AOP与AspectJ的对比及应用》介绍了AOP的使用&#xff0c;这篇文章讲解一下AOP与注解的整合&#xff0c;通过注解来使用AOP&#xff0c;会非常方便。为了简便&#xff0c;我们还是来实现一个计时的功能。 整合过程 首先创建一个注解&#xff1a; Re…...

【Flutter入门到进阶】跨平台相关

1 跨平台 1.1 跨平台概念 1.1.1 概念 跨平台概念是软件开发中一个重要的概念&#xff0c;即不依赖于操作系统&#xff0c;也不依赖硬件环境。一个操作系统下开发的应用&#xff0c;放到另一个操作系统下依然可以运行。相对而言如果某种计算机语言不用修改代码即可做到高度跨…...

阿里前端一面必会react面试题总结

React中怎么检验props&#xff1f;验证props的目的是什么&#xff1f; React为我们提供了PropTypes以供验证使用。当我们向Props传入的数据无效&#xff08;向Props传入的数据类型和验证的数据类型不符&#xff09;就会在控制台发出警告信息。它可以避免随着应用越来越复杂从而…...

各国政府成为量子投资“狂热”主力军!

&#xff08;图片来源&#xff1a;网络&#xff09;即使在资本寒冬&#xff0c;量子计算也吸引着投资者的极大兴趣。今年&#xff0c;尤其是美国以外的初创公司吸引了投资者的目光&#xff0c;获得了巨额投融资。2023年1月&#xff0c;总部位于澳大利亚悉尼的量子计算初创公司Q…...

Makefile 整理

1 简介 一个工程中的源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;makefile定义了一系列的规则来指定&#xff0c;哪些文件需要先编译&#xff0c;哪些文件需要后编译&#xff0c;哪些文件需要重新编译&#xff0c;甚至于进行更复杂的功能…...

全局事务服务 GTS

目录 1.什么是全局事务服务GTS 2.为什么需要全局事务服务 3.产品架构 4.应用场景 SOA 和分布式事务 消息事务 通用分布式事务管理 共享出行 5.产品功能 跨数据库分布式事务 跨服务的分布式事务 消息队列分布式事务 混合的分布式事务 多种事务模式 6.产品优势 超强…...

【C#项目】图书管理系统-WinForm+MySQL

文章目录前言一、业务梳理与需求分析1.功能描述2.实现步骤3.功能逻辑图二、数据库设计1.实体-关系&#xff08;E-R图&#xff09;概念模型设计2.数据表设计三、WinForm界面交互设计四、通用类设计五、系统代码实现与分析总结前言 图书馆管理系统主要功能有普通用户&#xff08…...

STK + C# + Cesium联合编程(一):技术路线验证

概述本文演示了一个基于STK C# Cesium联合编程的应用实例。关于STK和Cesium编程网上在线资料丰富&#xff0c;本文主要解决了如果配置IIS服务以使得远程客户端能访问、初始化、以及执行服务器端STK的接口服务。请参考本作者之前关于STK、Cesium&#xff08;CZML&#xff09;、…...

2023-02-07 mysql创建user并配置权限

查询数据库版本 select version 查询所有ip和用户 select * from mysql.user; mysql> select * from mysql.user; mysql> select host,user from mysql.user; ---------------------------------------- | host | user …...

STM32CubeMX学习笔记(51)——读写内部Flash

一、简介 在STM32芯片内部有一个 FLASH 存储器&#xff0c;它主要用于存储代码&#xff0c;我们在电脑上编写好应用程序后&#xff0c;使用下载器把编译后的代码文件烧录到该内部 FLASH 中&#xff0c;由于 FLASH 存储器的内容在掉电后不会丢失&#xff0c;芯片重新上电复位后…...

互斥锁、读写锁、自旋锁

目录 为什么需要加锁&#xff1f; Demo1 分析原因 解决方法一&#xff1a;互斥锁(mutexlock) 解决方法二&#xff1a;自旋锁(spinlock) 自旋锁与互斥锁效率对比 Linux自旋锁与互斥锁的区别 Linux自旋锁与互斥锁的选用原则 C/CLinux服务器开发/后台架构师【零声教育】-学…...

Linux 项目使用命令整理

一. 从一个服务器到另一个服务器拷贝命令 拷贝单个文件命令&#xff1a; scp file usernameip:filepath 说明&#xff1a; username: 远程登录的用户名&#xff0c; 拷贝文件夹命令如下(多加上一个-r 参数即可)&#xff1a; scp -r file usernameip:filepath 二. 常用命令积…...

@Aspect注解背后的奥秘--下

Aspect注解背后的奥秘--下前言手动化进行到自动化靠的是什么自动代理创建器如何搜寻并对增强器集合进行过滤1.寻找所有可用的候选advisor1.1 isEligibleBean两种分支情况2.过滤候选增强器3.扩展增强器4.对增强器进行排序搜寻所有切面类并完成解析转换过程创建代理对象拦截器链执…...

Dlib+Opencv进行人脸识别检测

本案例主要用于对图片中的人脸进行检测&#xff0c;并用矩形进行框出import dlibimport cv2# 需要被识别的图片img_path "D:\\xxx.jpg"imgcv2.imread(img_path)#转换为灰阶图片graycv2.cvtColor(img,cv2.COLOR_BGR2GRAY)# 正向人脸检测器detector dlib.get_frontal…...

汽车行业进入「换帅+换将」周期,2023年关键词:变

「换帅换将」&#xff0c;成为汽车制造商进入下一个行业十年发展周期的新常态&#xff0c;同时也是解决企业现阶段难题&#xff08;包括新能源转型、智能化技术升级、市场销量疲软等等&#xff09;的不二之选。 1月30日&#xff0c;小鹏汽车宣布&#xff0c;原长城汽车总经理王…...

Android---PhotoView

目录 准备工作 1、双击放大和缩小 2、惯性滑动 3、双指放大和缩小 4、完整DEMO 准备工作 自定义PhotoView 自定义 PhotoView 继承(extends)自 View。并在最中间显示后面操作的图片。绘制图片可以重写 onDraw()方法&#xff0c;并在里面通过Canvas.drawBitmap()来要绘制图片…...

GardenPlanner 下载,园林绿化设计

garden planner拥有花园式和景观设计的工具&#xff0c;软件使用简单放&#xff0c;是一个简单而有效的软件解决方案&#xff0c;可以帮助你设计你梦想中的花园,以及安排植物,树木,建筑物和对象。1、garden planner支持更简单的园林规划方案2、软件采用二维的方式建立规划图3、…...

关于Python3异步非阻塞Web框架Tornado:真实的异步和虚假的异步

我们知道Tornado 优秀的大并发处理能力得益于它的 web server 从底层开始就自己实现了一整套基于 epoll 的单线程异步架构&#xff0c;其他 web 框架比如Django或者Flask的自带 server 基本是基于 wsgi 写的简单服务器&#xff0c;并没有自己实现底层结构。而tornado.ioloop 就…...

传统文件同步方式有哪些问题?该如何寻找替代同步方案?

企业每天都在产生大量数据&#xff0c;不断累加&#xff0c;大部分数据都会存储在服务器、数据中心等位置&#xff0c;所以在数据中心、服务器节点、异地分支机构、外部合作伙伴之间等&#xff0c;存在多种文件交换场景。 很多企业一开始会选择一些传统的传输调度方式&#xff…...

8-还在用Replication Controller吗、不妨考虑Deployment

8-还在用Replication Controller吗、不妨考虑Deployment 前言 在前一天我们介绍到Replication Controller。如果读者看过 Replication Controller官方文件 &#xff0c;可以看到官方在文件一开头就表示&#xff1a; NOTE: A Deployment that configures a ReplicaSet is now …...

Mysql5.7解压版安装教程(安装第二个服务)

一、 安装步骤 &#xff08;1&#xff09;解压Mysql5…7版本&#xff0c;在根目录下创建文件“my.ini”&#xff0c;并编辑相关内容如下&#xff1a; 注意&#xff1a; port、basedir、datadir、server-id不要和第一个&#xff08;已安装的&#xff09;MySQL相同。 [mysql] #…...

深圳医药净化厂房设计装修要点SICOLAB深圳医药净化厂房设计装修公司

1 GMP的作用药厂的新建、改建和扩建均要按照GMP 进行GMP 是药品生产和质量管理规范的简称。为了保证药品的生产质量&#xff0c;保证人民安全用药&#xff0c;中国国家药品监督管理局1998 年颁布的《GMP》是借鉴国外和国内以往的《GMP》&#xff0c;经过逐步完善&#xff0c;结…...

02-PS工具栏介绍

1.移动工具 用来移动图片位置的, 用鼠标左键点击图片不松手, 移动鼠标就可以进行拖动 2.画板工具 ps中选择画板工具之后&#xff0c;可以根据需要添加多个画板。以下为详细的操作步骤&#xff1a; 1.ps软件中&#xff0c;新建画布之后&#xff0c;在左侧工具栏选择画板工具 …...

小区系 统 信 号 及 电 源 传 输

&#xff08; 1&#xff09; 系 统 的 信 号 电 缆本 次 设 计 主 要 道 路 的 数 字 监 控 &#xff0c; 采 用 超 五 类 室 外 防 水 网 线 传 输 视 频 信 号 &#xff0c; 传输 距 离 小 于 75米 &#xff0c;当 距 离 大 于 75米 时 &#xff0c;全 部 采 用 交 换 机 千 …...

锐捷(十三)MPLS VXN optionA基础配置实验

mpls vxn的一个特点是asbr之间启用了vrf&#xff0c;这样比较简单&#xff0c;asbr上只需要启动asbr就行了&#xff0c;asbr之间不用启mpls&#xff0c;因为他传的是ipv4报文&#xff0c;但是过多的vrf会造成路由器压力过大&#xff0c;所以一般用opyionA的少。一 实验拓扑二 实…...

图表控件LightningChart.NET 系列教程(八):LightningChart 组件——从工具箱添加至 Windows Forms 项目

LightningChart.NET SDK 是一款高性能数据可视化插件工具&#xff0c;由数据可视化软件组件和工具类组成&#xff0c;可支持基于 Windows 的用户界面框架&#xff08;Windows Presentation Foundation&#xff09;、Windows 通用应用平台&#xff08;Universal Windows Platfor…...

Thanos + Prometheus + Grafana

1.基本信息 2.架构图 解释&#xff1a; Prometheus: 是一个开源监控解决方案, 用于收集和聚合指标作为时间序列数据Thanos Sidecar: 需要和Prometheus安装在一起,其作用是 (1) 获取prometheus的数据供query查询 (2)每两小时会将prometheus收集的数据同步到对象存储。Thanos S…...

ChatGPT搅动AI又一波风潮,却扒出了百度的长板

文|智能相对论作者| 叶一城野火燎原&#xff0c;openAI搞出的chatGPT大有当年alphaGo的架势&#xff0c;搅动整个AI业界春心荡漾。从openAI的大金主微软&#xff0c;到昔日明星alphaGo的爸爸Google&#xff0c;都火急火燎地要出来表态——前者要彰显主权并在内部全面应用&#…...

【华为OD机试真题2023 JAVA】统一限载货物数最小值

华为OD机试真题,2023年度机试题库全覆盖,刷题指南点这里 统一限载货物数最小值 知识点二分查找 时间限制:1s 空间限制:64MB 限定语言:不限 题目描述: 火车站附近的货物中转站负责将到站货物运往仓库,小明在中转站负责调度2K辆中转车(K辆干货中转车,K辆湿货中转车)。货…...

C语言学生综合管理系统[2023-02-07]

C语言学生综合管理系统[2023-02-07] 学生综合管理系统 在 Visual Studio 开启平台下使用 C 语言通过控制台应用程序实现一个简单的《学生综合管理系统》&#xff0c;该系统由学生信息管理、学生课程管理、学生图书管理、学生宿舍管理、学生成绩管理、学生实验管理 6 个子系统…...

信息系统项目管理师论文范文(一)

今天分享信息系统项目管理师考试的论文项目背景&#xff0c;大家需要的可以参考修改。论文背景&#xff1a;随着我国城市化进程的加快&#xff0c;城市垃圾产生量和清运量逐年递增&#xff0c;“垃圾围城“已成为城市化的切肤之痛。2018年11月&#xff0c;沿海某市在充分调研的…...