多线程

  • 多线程的创建
  • Thread类的方法
  • 线程安全、线程同步
  • 线程通信、线程池
  • 定时器、线程状态..

Thread类

  • java是通过java.lang.Thread类来代表线程的
  • 按照面向对象的思想,Thread类应该提供了实现多线程的方式

创建线程

方式一

1.定义一个线程类:extends Thread

public class MyThread extends Thread{
//2.重写run方法
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.print(i);
}
}
} public clss ThreadDemo1{
main(){
//3.new 一个新线程
Tread t =new MyThread();
//4.调用start方法
t.start();
for(int i=0;i<5;i++){
System.out.print(i);
}
}
}

优缺点:

1.编码简单

2.线程类继承了Thread类不利于扩展

为什么用start()不用run()?

直接run()是普通方法执行,依然是一个主线程

不要把主线程的任务放到执行之前,还是单线程

方式二

  • 创建一个线程任务实现接口Runnable接口,重写run()方法

  • 创建线程类对象

  • 把对象任务交给Tread处理

    public MyThread implements Runnable{

    @Override

    public void run(){

    for(int i=0;i<5;i++){

    System.out.print(i);

    }

    }

    }

    public class a{

    main(){

    //创建一个任务对象

    Runnable a = new MyThread();

    //任务对象交给Thread处理

    Thread t = new Thread(a);

    t.start();

    for(int i=0;i<5;i++){

    System.out.print(i);

    }

    }

    }

Thread的构造器:

  • public Thread(String name) 可以为当线程指定名称
  • public Thread(Runnable target)分装Runnable对象为线程对象
  • public Thread(Runnable target,String name)

优缺点:

  • 实现接口,扩展性强
  • 多一层封装,如果线程有执行结果是不可以直接返回的 run()没有返回值

匿名内部类:

public class a{
main(){
//创建一个任务对象
Runnable a = new Runable(){
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.print(i);
}
};
//任务对象交给Thread处理
Thread t = new Thread(a);
t.start();
for(int i=0;i<5;i++){
System.out.print(i);
}
}
}

方式三:JDK5.0 实现Callable接口和FutureTask

  • 得到任务对象

    • 实现Callable重写call(),
    • 用FutureTask把Callable对象分装成线程的任务对象
  • 把线程对象交给Thread

  • 调用Thread的strat()启动线程

  • 线程执行完毕后,通过FutureTask的get方法区获取任务执行的结果

    pubilc class A implements Callable{

    private int n;

    public A(n){

    this.n=n;

    }

    @Override

    public String Call() throw Exception{

    String sum=0;

    for(int i=0;i<n;i++){

    sum+=i;

    System.out.print(i);

    }

    return "结果是"+sum;

    }

    }

    public class B {

    main(){

    Callable c = new A(10);

    FutureTask f = new FutureTask<>(c);

    //FutureTask实现了Runable接口

    Thread t =new Thread(f);

    t.start();

    //没有结果会等待

    try{

    String s =f.get();

    }catch(Excetion e){

    e.printStackTrace();

    }

    }

    }

优缺点:

  • 实现接口扩展性强
  • 可以得到结果
  • 较为复杂

线程的常用方法

  • getName()获取线程名字

  • setName()修改线程名字

  • currentThread()获取线程的对象

    定义一个线程类:extends Thread

    public class MyThread extends Thread{

    //2.重写run方法

    @Override

    public void run(){

    for(int i=0;i<5;i++){

    System.out.print(i);

    }

    }

    }

    public clss ThreadDemo1{

    main(){

    //3.new 一个新线程

    Tread t =new MyThread();

    //4.调用start方法

    t.setName("1号线程")

    t.start();

    Thread m =Thread,currentThread();

    for(int i=0;i<5;i++){

    System.out.print(i);

    }

      }

    }

主线程的名称就是main

线程的休眠方法:技术优化--百度网盘

  • public static void sleep(long time) 让当前的线程休眠,单位毫秒

    main(){

    for(int i =0;i<5;i++){

    if(i==3){

    //技术优化点

    Thread.sleep(3000);

    }

    }

    }

线程安全问题

  • 存在多线程共享

  • 同时访问共享资源

  • 存在修改共享资源

    public class ThreadDemo{

    main(){

    Account a =new Account ("郝泾钊",10000);

    //小明小红

    new DrawThread(acc,"小明").start();

    new DrawThread(acc,"小红").start();

    }

    }

    @Date

    @All..

    @Null..

    public class Account{

    private String card;

    private double money;

    public void drawMoney (double money){

    //判断是谁来取钱

    String name= Thread.currentThread().getName();

    //判断是否够钱

    if(this.money>=money){

    //这样更容易不安全

    System.out.print(name+"来取钱成功,出"+money);

    this.money-=moey;

    }else{

    //余额不足

    System.out.print(name+"来取钱,余额不足");

    }

    }

    }

    public class DrawThread extends Thread{

    private Account acc;

    public DrawThread(Account acc,String name){

    super(name);

    this.acc=acc;

    }

    @Override

    public void run(){

    acc.drawMoney(1000);

    }

    }

线程同步

  • 加锁

    @Date

    @All..

    @Null..

    public class Account{

    private String card;

    private double money;

    public void drawMoney (double money){

    //判断是谁来取钱

    String name= Thread.currentThread().getName();

    synchronized("heima"){

    //判断是否够钱

    if(this.money>=money){

    //这样更容易不安全

    System.out.print(name+"来取钱成功,出"+money);

    this.money-=moey;

    }else{

    //余额不足

    System.out.print(name+"来取钱,余额不足");

    }

    }

    }

    }

    synchronized(){}:

  • 锁用随机对象好不好? 不好 同步代码块

建议使用共享资源作为锁对象

@Date
@All..
@Null..
public class Account{
private String card;
private double money;
public void drawMoney (double money){
//判断是谁来取钱
String name= Thread.currentThread().getName();
synchronized(this){
//判断是否够钱
if(this.money>=money){
//这样更容易不安全
System.out.print(name+"来取钱成功,出"+money);
this.money-=moey;
}else{
//余额不足
System.out.print(name+"来取钱,余额不足");
}
}
}
}
  • 实例方法用this
  • 静态方法用类名.class

线程同步

方法上修饰synchronized

默认用this,但是代码要高度面向对象

@Date
@All..
@Null..
public class Account{
private String card;
private double money;
public synchronized void drawMoney (double money){
//判断是谁来取钱
String name= Thread.currentThread().getName();
//判断是否够钱
if(this.money>=money){
//这样更容易不安全
System.out.print(name+"来取钱成功,出"+money);
this.money-=moey;
}else{
//余额不足
System.out.print(name+"来取钱,余额不足");
}
}
}
  • 同步代码块好还是同步方法好?

    • 同步代码块好,性能好
  • 同步方法原理了?
    • 也是用synchronized修饰默认是this

Lock锁

JDK5 加入的,更丰富功能,Lock接口不能实例化

  • public ReentrantLock() 获得Lock的对象

方法:

  • void lock() 获得锁

  • void unlock() 释放锁

    @Date

    @All..

    @Null..

    public class Account{

    private String card;

    private double money;

    private final Lock lock =new ReentrantLock();

    public void drawMoney (double money){

    //判断是谁来取钱

    String name= Thread.currentThread().getName();

    //判断是否够钱

    lock.lock();

    if(this.money>=money){

    //这样更容易不安全

    System.out.print(name+"来取钱成功,出"+money);

    this.money-=moey;

    }else{

    //余额不足

    System.out.print(name+"来取钱,余额不足");

    }

    lock.unlock();

    }

    }

线程通信

  • 线程之间相互发送数据
  • 共享的数据的情况决定自己做什么

常见模型:

  • 生产者与消费者模型:
  • 生产者线程产生数据,唤醒消费者;然后等待自己;消费者消费完数据之后唤醒生产者,然后等待自己。

方法:

Object类中:

  • void wait() 让当前线程等待并释放占用锁,直到另一个线程调用notify或notifyAll方法
  • void notify() 唤醒末个等待的线程
  • void notifyAll()唤醒正在等待的所有线程

上述方法要用同步锁对象来调用

例子:

@Date
@All..
@Null..
public class Account{
private String card;
private double money;
public synchronized void drawMoney (double money){
//判断是谁来取钱
String name= Thread.currentThread().getName();
//判断是否够钱
if(this.money>=money){
//这样更容易不安全
System.out.print(name+"来取钱成功,出"+money);
this.money-=moey;
//没钱了,唤醒别的线程
this.notifyAll();
this.wait();
}else{
//当前等待,唤醒别的线程 先叫醒别人在打晕自己
this.notifyAll();
this.wait();
}
}
public synchronized void deposit(double money){
//判断是谁来存钱
String name= Thread.currentThread().getName();
//判断是否够钱
if(this.money==0){
//这样更容易不安全
System.out.print(name+"来存钱成功,存钱"+money);
this.money+=moey;
//有钱了,唤醒别的线程
this.notifyAll();
this.wait();
}else{
//有钱不存钱
this.notifyAll();
this.wait();
}
}
} main(){
Account acc =new Account("132",0);
//创建两个取钱线程
new DrawThread(acc,"小明");
new DrawThread(acc,"小红");
//创建两个存钱线程
new SaveThread(acc,"亲爹");
new SaveThread(acc,"亲爹");
new SaveThread(acc,"亲爹"); }

取钱:

public class DrawThread extends Thread{
private Account acc;
public DrawThread(Account acc,String name){
super(name);
this.acc=acc;
}
@Override
public void run(){
while(true){
acc.drawMoney(1000);
try{
Thread.sleep(3000);
}catch(Exception e){
e.printStackTrace();
}
}
}
}

存钱:

public class SaveThread extends Thread{
private Account acc;
public DrawThread(Account acc,String name){
super(name);
this.acc=acc;
}
@Override
public void run(){
acc.deposit(1000);
try{
Thread.sleep(3000);
}catch(Exception e){
e.printStackTrace();
}
}
}

线程池(重点)

  • 创建线程的开销很大,----线程池解决

线程池的接口:ExecutorService

得到线程池对象

1.使用ExecutorService的实现类ThreadPoolExecytor自创建一个线程

ExecutorService------->ThreadPoolExecytor

2.使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象

public ThreadPoolExecutor(int corePoolSize,//核心线程数
int maximumPoolSize,//最大线程数
long keepAliveTime,//线程空闲时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//任务队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler//拒绝策略)
{
...
}
  • corePoolSize 核心线程数,默认为1。

    • 设置规则:

      CPU密集型(CPU密集型也叫计算密集型,指的是运算较多,cpu占用高,读/写I/O(硬盘/内存)较少):corePoolSize = CPU核数 + 1

      IO密集型(与cpu密集型相反,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。):corePoolSize = CPU核数 * 2
  • maximumPoolSize
    • 最大线程数,默认为Integer.MAX_VALUE 一般设置为和核心线程数一样
  • keepAliveTime
    • 线程空闲时间,默认为60s,一般设置为默认60s
  • unit
    • 时间单位,默认为秒
  • workQueue
    • 队列,当线程数目超过核心线程数时用于保存任务的队列。(BlockingQueue workQueue)此队列仅保存实现Runnable接口的任务。(因为线程池的底层BlockingQueue的泛型为Runnable)

      无界队列

      队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列作为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM。阅读代码发现,Executors.newFixedThreadPool 采用就是 LinkedBlockingQueue,而博主踩到的就是这个坑,当QPS很高,发送数据很大,大量的任务被添加到这个无界LinkedBlockingQueue 中,导致cpu和内存飙升服务器挂掉。

      当然这种队列,maximumPoolSize 的值也就无效了。当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

      有界队列

      当使用有限的 maximumPoolSizes 时,有界队列有助于防止资源耗尽,但是可能较难调整和控制。常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。PriorityBlockingQueue中的优先级由任务的Comparator决定。

      使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。

      同步移交队列

      如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列
  • threadFactory
    • 线程工厂,用来创建线程。

      为了统一在创建线程时设置一些参数,如是否守护线程,线程一些特性等,如优先级。通过这个TreadFactory创建出来的线程能保证有相同的特性。

      它是一个接口类,而且方法只有一个,就是创建一个线程。

      如果没有另外说明,则在同一个ThreadGroup 中一律使用Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的NORM_PRIORITY 优先级和非守护进程状态。

      通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。

      如果从newThread 返回 null 时ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务
  • handler
    • 拒绝策略,默认是AbortPolicy,会抛出异常。

      当线程数已经达到maxPoolSize,且队列已满,会拒绝新任务。

      当线程池被调用shutdown()后,会等待线程池里的任务执行完毕再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。

      AbortPolicy 丢弃任务,抛运行时异常。

      CallerRunsPolicy 由当前调用的任务线程执行任务。

      DiscardPolicy 忽视,什么都不会发生。

      DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务。

核心线程--临时线程

临时线程什么时候创建?

新任务提交任务是发现核心任务都在忙时,任务队列也满了,并且可以创建临时线程,此时会创建临时线程

什么时候会开始拒绝任务?

核心线程和临时线程都在忙,任务队列也满了,新的任务会被拒绝。

线程池处理Runable、Callable任务

方法:

  • void execute(Runnable command) 执行任务、命令,没返回值--一般Runable任务
  • Future submit(Callable task)执行任务、命令,又返回值,--一般Callable任务
  • void shutdown()等任务执行完毕后关闭线程
  • List shutdownNow()立即关闭线程,停止执行的任务,并返回队列中未执行的任务

新任务的拒绝策略:

  • ThreadPoolExecutor.AborPolicy 丢弃任务并抛出RejectedExccutionException默认

  • ThreadPoolExecutor.DiscardPolicy丢弃任务,但是不抛出异常不推荐

  • ThreadPoolExecutor.DiscardOldestPolicy抛弃队列中等待最久的任务,然后把当前任务加入到队列中

  • ThreadPoolExecutor.CallerRunsPolicy由主线程负责调用任务的run()方法从绕过线程池执行

    public class TreadPoll{

    main(){

    //创建线程池对象

    ExcutorService pool =new ThreadPoolExcutor(3,5,6,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AborPolicy() )

    }

    //模拟线程处理

    Runnable target =new MyRunnable();

    pool.execute(target);

    pool.execute(target);

    pool.execute(target);

    //任务队列

    pool.execute(target);

    pool.execute(target);

    pool.execute(target);

    pool.execute(target);

    pool.execute(target);

    //创建临时线程

    pool.execute(target);

    pool.execute(target);

    //拒绝策略触发

    pool.execute(target);

    //关闭线程池(开发中一般不会使用)

    pool.shutdownNow();

    pool.shutdown();

    }

    public class MyRunnable implement Runnable{

    @Override

    public void run(){

    for(int i =0 ;i<5;i++){

    System.out.print(Thread.cuurentThread().getNmae()+"编写了hello world");

    }

    try{

    Thread.sleep(3000);

    }catch(Exception e){

    e.printStackTrace();

    }

    }

    }

处理Callable任务

public class TreadPoll{
main(){
//创建线程池对象
ExcutorService pool =new ThreadPoolExcutor(3,5,6,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AborPolicy() )
}
Future<String> f1=pool.submit(new MyCallable(100));
Future<String> f2=pool.submit(new MyCallable(100));
Future<String> f3=pool.submit(new MyCallable(100)); String rs = f1.get();
String rs2 =f2.get();
String rs3 = f3.get(); }

Executors工具类

  • public static ExecutorsService newCachedThreadPool() 线程池的数量随着任务的增加而增加,如果执行完毕空闲一段时间会被回收
  • public static ExecutorsService newFixedThreadPool(int nThreads)创建固定的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新的线程替代它
  • public static ExecutorsService newSingleThreadExecutor()创建一个线程池对象,如果线程出现异常而结束,那么线程池会补充一个新的线程
  • public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务

Executors底层也是实现 ThreadPoolExecutor实现的

main(){
ExecutorService pool=Executors.ExecutorsService newFixedThreadPool(3);
}

但是大型的并发项目会出现系统分险

  • 内存溢出,线程的内存溢出
  • 任务没有限制

定时器

方式一:Timer

方法二:newScheduledThreadPool

Timer

构造器:

  • public Timer() 创建定时器对象

方法:

  • public void schedule(TimerTask task,long delay, long period) 开启一个定时器执行task任务

问题:

  • Timer是单线程,存在延时与定时器的时间有出入的情况

  • 可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续的任务执行

    main(){

    Timer timer =new Timer();

    timer.scedule(new TimerTask(){

    @Oberride

    public void run(){

    //业务

    System.out.print("定时器");

    }

    },3000,2000)

    }//3秒延时调用2秒周期调用

newScheduledThreadPool;

JDK1.5引入的并发包

Executors

  • public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务

方法:

  • public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initalDelay ,long period,TimeUnit unit) 周期调度方法

优点:

  • 基于线程池执行,某个任务情况不会影响其他的定时任务的执行

    main(){

    ScheduledExecutorService pool =Executor.newScheduledThreadPool(3);

    pool.scheduleAtFixedRate(new TimeTrask(){

    @Override

    public void run (){

    //业务

    }

    },0,2,TimeUnit.SECONDS);

    }//初始化延迟事件0秒 周期延迟2秒单位秒

并发和并行

多线程:并发和并行同时进行

生命周期

Thread类的枚举类的6中状态

  • new 新建状态
  • Runnable 可运行状态start()
    • Blocked锁阻塞状态
    • waiting无限等待状态wait()
    • Timed Waiting (计时等待)
      • sleep状态的线程好了不用强锁、、、我不要脸
      • wait状态的线程的时间到了,并得到锁,可以跑
      • wait状态时间没到,被唤醒,并得到锁可以跑
      • wait没有得到锁会进入锁阻塞
  • Teminated被终止状态

javaEE(多线程、线程通信、线程安全、线程池、线程池工具)的更多相关文章

  1. Java并发编程扩展(线程通信、线程池)

    之前我说过,实现多线程的方式有4种,但是之前的文章中,我只介绍了两种,那么下面这两种,可以了解了解,不懂没关系. 之前的文章-->Java并发编程之多线程 使用ExecutorService.C ...

  2. java多线程-线程通信

    线程通信的目标是使线程间能够互相发送信号.另一方面,线程通信使线程能够等待其他线程的信号. 通过共享对象通信 忙等待 wait(),notify()和 notifyAll() 丢失的信号 假唤醒 多线 ...

  3. 多线程一共就俩问题:1.线程安全(访问共享数据) 2.线程通信(wait(),notify())

    多线程一共就俩问题:1.线程安全(访问共享数据) 2.线程通信(wait(),notify()) 1.线程安全,无非就是加锁,访问共享资源时,synchronized 2.线程通信,就是控制各个线程之 ...

  4. Java多线程-同步:synchronized 和线程通信:生产者消费者模式

    大家伙周末愉快,小乐又来给大家献上技术大餐.上次是说到了Java多线程的创建和状态|乐字节,接下来,我们再来接着说Java多线程-同步:synchronized 和线程通信:生产者消费者模式. 一.同 ...

  5. JAVA基础知识之多线程——线程通信

    传统的线程通信 Object提供了三个方法wait(), notify(), notifyAll()在线程之间进行通信,以此来解决线程间执行顺序等问题. wait():释放当前线程的同步监视控制器,并 ...

  6. java多线程——线程通信

    一.线程通信目标 1.线程通信的目标是使线程间能够互相发送信号 2.线程通信使线程能够等待其他线程的信号 二.几种方式 1.通过共享对象 2.忙等待 线程 B 运行在一个循环里,以等待信号 (不释放c ...

  7. Java线程同步和线程通信

    一.线程同步 当多个线程访问同一个数据时,非常容易出现线程安全问题.这时候就需要用线程同步. 不可变类总是线程安全的,因为它的对象状态是不可改变的,但可变类对象需要额外的方法来保证线程安全. 1.同步 ...

  8. JavaSE——线程通信

    线程通信: 如果线程A和线程B持有同一个MyObject类的对象object,这两个线程会去调用不同的方法,但是它们是同步执行的,比如:线程B需要等待线程A执行完了methodA()方法之后,它才能执 ...

  9. Python网络编程(线程通信、GIL、服务器模型)

    什么是进程.进程的概念? 进程的概念主要有两点: 第一,进程是一个实体.每一个进程都有它自己的地址空间, 一般情况下,包括文本区域(text region).数据区域(data region)和堆栈( ...

  10. Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

    1.Java使用Thread类代表线程.     所有的线程对象必须是Thread类或其子类的实例. 当线程继承Thread类时,直接使用this即可获取当前线程,Thread对象的getName() ...

随机推荐

  1. 【Devexpress】Gridcontorl动态创建列不显示的问题

    通过代码创建列但是不显示,这个原因是因为代码创建的列Visible属性默认是false 所以需要设置为true就会显示了 gridColumn.Visible = true;

  2. Jmeter——结合Allure展示测试报告

    在平时用jmeter做测试时,生成报告的模板,不是特别好.大家应该也知道allure报告,页面美观. 先来看效果图,报告首页,如下所示: 报告详情信息,如下所示: 运行run.py文件,运行成功,如下 ...

  3. 2.6:Python数据存取-文件、文件夹及目录、数据库

    一.Python文件读写 1.文件的打开模式 <class '_io.TextIOWrapper'>和<class '_io.BufferedReader'>.python使用 ...

  4. Mybatis04:延迟加载、一二级缓存、注解开发

    今日内容 mybatis中的延迟加载 延迟加载的概念 立即加载的概念 如果实现延迟加载 Mybatis中的缓存 什么是缓存 为什么使用缓存 什么数据可以使用缓存,什么样的数据不能使用缓存 Mybati ...

  5. Doris安装部署

    下载安装 Doris运行在Linux环境中,推荐 CentOS 7.x 或者 Ubuntu 16.04 以上版本,同时你需要安装 Java 运行环境(JDK最低版本要求是8) 1.下载安装包 下载地址 ...

  6. 《HTTP权威指南》– 16.重定向与负载均衡

    重定向 重定向 的目标是尽快地将HTTP报文发送到可用的Web服务器上去.在穿过因特网的路径上,HTTP报文传输的方向会受到HTTP应用程序和报文经由的路由设备的影响: 配置创建客户端报文的浏览器应用 ...

  7. js 定时器(setInterval)

    js 定时器 分类 编程技术 js 定时器有以下两个方法: setInterval() :按照指定的周期(以毫秒计)来调用函数或计算表达式.方法会不停地调用函数,直到 clearInterval() ...

  8. 【环境搭建】RocketMQ集群搭建

    前置条件及效果图 条件: 两台服务器,个人是两台腾讯云服务器(其中嫖的朋友一个): 版本: rocketmq-version:4.4.0 rocketmq-console(mq控制台) Java:1. ...

  9. 【转载】EXCEL VBA 通过VBA中的Union合并多个Range选择区域

    在Excel中,Union的功能是合并两个或两个以上的选择区域,合并成为一个更大的区域. 所合并的多个选择区域,这些选择区域,可以是不连续的,也可以是连续的.一般情况下,要使用Union,可通过如下来 ...

  10. [深度学习] Python人脸识别库face_recognition使用教程

    Python人脸识别库face_recognition使用教程 face_recognition号称是世界上最简单的开源人脸识别库,可以通过Python或命令行识别和操作人脸.face_recogni ...