java多线程(下)

public class DeadLockTest {
    private static Object work = new Object();
    private static Object money = new Object();
    public static void main(String[]args){
        Thread worker = new Thread(new Runnable() { //匿名内部类
            @Override
            public void run() {
                synchronized (work) {
                    System.out.println("先给我钱,我再工作");
                    synchronized (money) {
                        System.out.println("给我钱");
                    }
                }
            }
        });
        Thread boss = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (money) {
                    System.out.println("先给我干活,在给钱");
                    synchronized (work) {
                        System.out.println("给我干活");
                    }
                }
            }
        });
        worker.start();
        boss.start();
    }
}

死锁:

产生条件:以下四种条件同时满足才会导致死锁

1、互斥条件:

共享资源只能同时被一个线程占用

2、占用且等待

eg:拿到woker锁,不释放的同时去申请money锁

3、不可抢占:

线程Thread1拿到对象锁后,其他线程无法进行抢占锁

4、循环等待:

线程T1拿到了资源X的锁,去申请Y的锁

线程T2拿到了资源Y的锁,去申请X的锁

银行家算法解决死锁问题

ThreadLocal-线程本地变量

属于线程私有资源不与其他线程共享

set() 设置线程私有属性值

get()取得线程私有属性值

public class ThreadLocalTest {
    private static String commStr;
    private static ThreadLocal<String> threadLocalStr = new ThreadLocal<>();
    public static void main(String[]args) throws InterruptedException {
        //主线程
        commStr = "main-commStr";
        threadLocalStr.set("main-ThreadLocalStr");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
           //子线程
                commStr = "thread0-commStr";
                threadLocalStr.set("thread0-ThreadLocalStr");
            }
        });
        thread.start();
        thread.join();
        System.out.println(commStr);
        System.out.println(threadLocalStr.get());

    }
}

每个Thread对象内部都有一个ThreadLocal.ThreadLocalMap对象(存放元素)

该对象属于每个线程自己,因此其保存的内容也是线程私有,在多线程场景下并不共享。

生产消费者模型

多线程三大核心问题:

分工

互斥

同步(线程间通信)

 

Object:wait()  ,notify()必须搭配synchronized使用

使用wait()  ,notify()有一个前提,必须在同步方法或同步代码块中使用

必须拿到相应的锁才能调用,否则抛出IllegalMonitorStateException

wait():痴汉方法

持有锁的线程调用wait()方法后会一直阻塞,直到有别的线程调用notify()将其唤醒

public final native void wait (long time):、等待一段时间,若还未被唤醒,继续执行,默认单位为毫秒

notify()

唤醒任意一个处于wait的线程

任意一个Object及其子类对象都有两个队列

同步队列:所有尝试获取该对象Monitor失败的线程,都加入同步队列,排队获取锁

等待队列:已经拿到锁的线程在等待其他资源时,主动释放锁,置入该对象的等待队列中,等待被唤醒。

当调用notify()会在等待队列中任意唤醒一个线程,将其置入同步队列尾部,排队获取锁。

notifyAll() :将等待队列中的所有线程唤醒,并且加入同步队列。

 

生产—消费者模型

设计模式:先找到第三方

生产者

消费者

例子1:一个生产者,一个消费者消费一件商品

class Goods {
    private String goodsName;
    private int count;

    @Override
    public String toString() {
        return "Goods{" +
                "goodsName='" + goodsName + '\'' +
                ", count=" + count +
                '}';
    }

    protected synchronized void set(String goodsName) {
        if (count == 1) {
            System.out.println("此时还有商品,需要消费者消费了再继续生产");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.goodsName = goodsName;
        count++;
        System.out.println(Thread.currentThread().getName()+"生产"
        +this);
        //唤醒消费者线程
        this.notify();
    }

    public synchronized void get() {
        if (count == 0) {
            System.out.println("商品已售完,需要生产者上架商品");
            //需要让消费者阻塞
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        System.out.println(Thread.currentThread().getName()+"消费"
        +this);
        //唤醒生产者线程
        this.notify();
    }
}

class Producer implements Runnable{
    private Goods goods;

    public Producer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
    this.goods.set("一个小黑瓶");
    }
}

class Consumer implements Runnable{
    private Goods goods;

    public Consumer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        this.goods.get();
    }
}


public class CPTest {
    public static void main(String[]args) throws InterruptedException {
        Goods goods = new Goods();
        Producer producer = new Producer(goods);
        Consumer consumer = new Consumer(goods);
        Thread produceThread = new Thread(producer,"生产者");
        Thread consumeThread = new Thread(consumer,"消费者");
        //produceThread.start();
        consumeThread.start();
        Thread.sleep(1000);
        //consumeThread.start();
        produceThread.start();
    }
}
//改进,让其一直生产消费,只需改下面两段代码
  @Override
    public void run() {
        while (true) {
            this.goods.set("一个小黑瓶");
        }
    }

 @Override
    public void run() {
        while (true) {
            this.goods.get();
        }
    }

例子2:多个生产、消费者消费同一件商品

class Goods {
    private String goodsName;
    private int count;

    @Override
    public String toString() {
        return "Goods{" +
                "goodsName='" + goodsName + '\'' +
                ", count=" + count +
                '}';
    }

    protected synchronized void set(String goodsName) {
        while (count == 1) {
            System.out.println("此时还有商品,需要消费者消费了再继续生产");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.goodsName = goodsName;
        count++;
        System.out.println(Thread.currentThread().getName()+"生产"
        +this);
        //唤醒消费者线程
        this.notifyAll();
    }

    public synchronized void get() {
        while (count == 0) {
            System.out.println("商品已售完,需要生产者上架商品");
            //需要让消费者阻塞
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        System.out.println(Thread.currentThread().getName()+"消费"
        +this);
        //唤醒生产者线程
        this.notifyAll();
    }
}

class Producer implements Runnable{
    private Goods goods;

    public Producer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        while (true) {
            this.goods.set("一个小黑瓶");
        }
    }
}

class Consumer implements Runnable{
    private Goods goods;

    public Consumer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        while (true) {
            this.goods.get();
        }
    }
}


public class CPTest {
    public static void main(String[]args) throws InterruptedException {
        Goods goods = new Goods();
        Producer producer = new Producer(goods);
        Consumer consumer = new Consumer(goods);
        Thread produceThread = new Thread(producer,"生产者1");
        Thread produceThread1 = new Thread(producer,"生产者2");
        Thread produceThread2 = new Thread(producer,"生产者3");
        Thread consumeThread = new Thread(consumer,"消费者1");
        Thread consumeThread1 = new Thread(consumer,"消费者2");
        Thread consumeThread2= new Thread(consumer,"消费者3");
        Thread consumeThread3 = new Thread(consumer,"消费者4");
        consumeThread.start();
        produceThread.start();
        produceThread1.start();
        produceThread2.start();
        consumeThread1.start();
        consumeThread2.start();
        consumeThread3.start();
    }
}

例子3:多个生产、消费者消费多件商品

class Goods {
    private String goodsName;
    private int count;
    private int MaxCount;

    public Goods(int maxCount) {
        MaxCount = maxCount;
    }

    @Override
    public String toString() {
        return "Goods{" +
                "goodsName='" + goodsName + '\'' +
                ", count=" + count +
                '}';
    }

    protected synchronized void set(String goodsName) {
        while (count == MaxCount) {
            System.out.println("此时还有商品,需要消费者消费了再继续生产");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.goodsName = goodsName;
        count++;
        System.out.println(Thread.currentThread().getName()+"生产"
        +this);
        //唤醒消费者线程
        this.notifyAll();
    }

    public synchronized void get() {
        while (count == 0) {
            System.out.println("商品已售完,需要生产者上架商品");
            //需要让消费者阻塞
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        System.out.println(Thread.currentThread().getName()+"消费"
        +this);
        //唤醒生产者线程
        this.notifyAll();
    }
}

class Producer implements Runnable{
    private Goods goods;

    public Producer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        while (true) {
            this.goods.set("一个小黑瓶");
        }
    }
}

class Consumer implements Runnable{
    private Goods goods;

    public Consumer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        while (true) {
            this.goods.get();
        }
    }
}


public class CPTest {
    public static void main(String[]args) throws InterruptedException {
        Goods goods = new Goods(10);
        Producer producer = new Producer(goods);
        Consumer consumer = new Consumer(goods);
        //产生20个生产者
     //   List<Thread> list = new ArrayList<>(goods);
        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            list.add(new Thread(producer,"生产者"+(i+1)));
        }
        //产生30个消费者
        for (int i = 0; i < 30; i++) {
            list.add(new Thread(consumer,"消费者"+(i+1)));
        }
        //启动所有生产者,消费者
        for (Thread thread :list) {
            thread.start();
            
        }
    }
}

 

公平锁 lock

解决死锁问题

等待时间最长的线程先获取到锁

可重入锁:

 public synchronized void testA() {
        //线程1
        testB();
    }
    public synchronized void testB() {
        //线程2 wait
    }

sychronized有可重入锁

 

Lock-JDLock-JDK1.5基于java语言层面实现的线程“锁”

产生背景:synchronized 死锁

synchronized 阻塞式获取锁,获取不到就一直阻塞下去。

 

破坏不可抢占(死锁条件之一):

1、不响应中断 interrupt() ——void lockInerruptibly();

2、支持超时操作——boolean tryLock(long time,TimeUnit unit)

3、非阻塞式获取锁 线程若获取不到锁,线程直接退出——boolean tryLock();

 

Lock体系使用的格式

try {

      //加锁

     lock.lock();

}finally {

   //解锁

   lock.unlock();

}

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

class Task implements Runnable {
    private int ticket = 20;
    private Lock ticketLock = new ReentrantLock();
    
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            //需要对程序上锁
            try {
                //等同于synchronized(this)
                ticketLock.lock();
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName()
                            +"还剩下"+ticket--+"票");
                    Thread.sleep(20);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                ticketLock.unlock();
            }
            if (ticket >0)
                System.out.println(Thread.currentThread().getName()+
                        "还剩下"+ticket--+"票");
        }
    }
}

public class LockTest {

    public static void main(String[]args){
        Task task = new Task();
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        Thread thread3 = new Thread(task);
        thread1.start();
        thread2.start();
        thread3.start();

    }
}

破坏方法一:响应中断 interrupt() ——void lockInerruptibly(); (当线程持有一个锁的时候,外部通过调用interrupt方法打断它,抛出一个中断异常,在catch块中执行相应的逻辑)

class TaskInterrupt implements Runnable {
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        try {
            while (true) {
                lock.lockInterruptibly();
            }
        } catch (Exception e) {
            System.out.println("线程响应中断");
            return;
        } finally {
            lock.unlock();
        }
    }
}
public class LockInterrupt {
    public static void main(String[]args){
        TaskInterrupt taskInterrupt = new TaskInterrupt();
        Thread thread = new Thread(taskInterrupt);
        thread.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

破坏方法二:支持超时

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

class TaskTime implements Runnable {
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        testA();
    }
    public void testA() {
        try {
            if (lock.tryLock(1,TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName()+
                        "获取锁成功");
                Thread.sleep(2000);
            } else {
                System.out.println(Thread.currentThread().getName()+
                "获取锁失败");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
public class LockTime {
    public static void main(String[]args){
        TaskTime taskTime = new TaskTime();
        Thread thread1 = new Thread(taskTime,"线程A");
        Thread thread2 = new Thread(taskTime,"线程B");
        thread2.start();
        thread1.start();
    }
}

独占锁:synchronized,在任意一个时刻,只有一个线程可以拥有此锁

共享锁:在同一时刻,可以有多个线程拥有锁

读写锁——共享锁的一种实现

读锁共享 :多个线程可以同时拿到读锁进行访问,当写线程开始工作时,所有的读线程全部阻塞

写锁独占:任意一个时刻,只有一个线程可以拿到写锁

 

Condition:Lock体系线程通信方式,类比Object wait与notify 

await() 释放lock锁,将线程置于等待队列阻塞

signal() 随机唤醒一个处于等待的线程

signalAll() :唤醒所有等待的线程

小结:

java中实现线程“锁”的方式:synchronized 与 lock

1、synchronized 与 lock都是对象锁,都支持可重入锁,

区别:

2、lock可以实现synchronized不具备的特性,如响应中断,支持超时,非阻塞的获取锁,公平锁,共享锁(读写锁,ReentrantReaderWriteLock实现)

3、Lock的体系的Condition等待队列可以有多个(区分于synchronized只有一个等待队列),可以进一步提高效率,减少线程阻塞与唤醒带来的开销(唤醒不该唤醒额线程)

获取一个lock锁的condition队列:

lock.newCondition():产生一个新的Condition队列

到底用synchronized还是lock?

1、若没有特殊的应用场景,推荐使用synchronized,其使用方便(隐式加减锁),并且由于synchronized是JVM层面的实现,在之后的JDK还有对其优化的空间

2、若要使用公平锁,读写锁,超时获取锁等特殊场景,才会考虑使用Lock

 

FutureTask:可以保证多线程场景下,任务只会被执行一次,其他线程不在执行此任务。

Future接口中的get方法会阻塞当前线程直到取得Callable的返回值。

线程池:

class CallableTest implements Callable<String> {
    private int ticket = 20;
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 20; i++) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName()+
                        "还剩下"+ticket--+"票");
            }
        }
        return Thread.currentThread().getName()+"票卖完了";
    }
}
public class ExecutorTest {
    public static void main(String[]args) throws ExecutionException, InterruptedException {
        //创建一个线程池对象
        ExecutorService executorService = new ThreadPoolExecutor(2,
                3,60,TimeUnit.SECONDS,
                new LinkedBlockingDeque<>());
        CallableTest callableTest = new CallableTest();
        for (int i = 0; i < 5; i++) {
            //executorService.execute();不能用execute启动,execute只能接收Runnable接口
            executorService.submit(callableTest);
            //结果用Future接口接收
            //Future<String> submit = executorService.submit(callableTest);
            //Future接口中的get方法会阻塞当前线程直到取得Callable的返回值。
            //--------main一直卡在此处,此时只创建了一个线程-----
           // System.out.println(submit.get());
        }
        executorService.shutdown();
    }

}

线程池的核心两大接口

ExecutorService:普通线程池

-void execute(Runnable command);

-<T> Future<T> submit(Callable<T> task);

ScheduledExecutorService:定时线程池

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);

线程池的一个核心类:

ThreadPoolExecutor:是普通线程池ExecutorService的一个子类

 

创建线程的方法,推荐使用线程池来创建线程

public ThreadPoolExecutor(int corePoolSize, 
 int maximumPoolSize, 
 long keepAliveTime, 
 TimeUnit unit, 
 BlockingQueue<Runnable> workQueue, 
 RejectedExecutionHandler handler)

1corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其

他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调

用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。

2runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。

·ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。

·LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于

ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。

·SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操

作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool

使用了这个队列。

PriorityBlockingQueue:一个具有优先级的无限阻塞队列

3maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小

于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的任务队列这个参数就没什么

效果。

4) keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且

每个任务执行的时间比较短,可以调大时间,提高线程的利用率。

5) TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫

秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微

秒)。

6) RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种

策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。在JDK 1.5Java线

程池框架提供了以下4种策略。 ·AbortPolicy:直接抛出异常。(默认采用此策略) ·CallerRunsPolicy:只用调用者所在

线程来运行任务。 ·DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。 ·DiscardPolicy:不处

理,丢弃掉。

线程池工作流程:

1、当一个任务提交给线程池时,首先判断核心线程池的数量是否达到corePoolSize,若未达到,线程池创建新的线程执行任务并将其置入核心池中,否则,判断线程池是否有空闲线程,若有,则分配任务执行,否则进入步骤2

2、判断当前线程池中数量有没有达到线程池的最大数量(maximumPoolSize),若没有,创建新的线程执行任务并将其置入步骤3

3、判断阻塞队列是否已满,若未满,将任务置入阻塞队列中等待调度,否则进入步骤4

4、调用相应的拒绝策略打回任务(四种拒绝策略,默认抛出异常给用户AbortPolicy)

核心线程池:

最大线程池:

阻塞队列:

打回策略:

 

关闭线程池:shutdown方法

 

使用线程池创建线程的好处:
第一:线程池中的线程被包装为Worker工作线程,具备可重复执行任务的能力,降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能执行。

第三:提高线程的可管理性,线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控

合理配置线程池

配置核心池以及最大线程池数量:
CPU密集型任务(大数运算):NCPU(电脑CPU数量)+1

//当前操作系统中能同时运行的最大线程数量
System.out.println(Runtime.getRuntime().availableProcessors());

IO密集型任务:2*NCPU

内置的四大线程池:

Exectors - 线程池的工具类

1、固定大小线程池:Executors.newFixedThreadPool(int  nThread)

适用于负载较重的服务器(配置较低),来满足分配资源的要求

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
class RunnnableTest implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName()+
            "、"+i);
        }
    }
}
public class ExecutorTest1 {
    public static void main(String[]args){
        ExecutorService executorService =
                Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i++) {
            executorService.submit(new RunnnableTest());
        }
    }
}

2、单线程池-只有一个线程

当多线程场景下需要让任务串行执行的情况下,采用此方法

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));

3、缓存线程池:

一般用于较轻的服务器,或执行很多短期的异步任务。

Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

当任务提交速度 > 线程执行速度,会不断创建新线程,有可能无限创建线程将内存写满。

当任务提交速度 < 线程执行速度,只会创建若干个有限线程。

class RunableTest implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
           /* try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            System.out.println(Thread.currentThread().getName()+
                    "、"+i);
        }
    }
}

public class ExecutorTest2 {
    public static void main(String[]args){
        ExecutorService executorService = Executors.newCachedThreadPool(); //缓存线程池
  
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executorService.submit(new RunableTest());
        }
        executorService.shutdown();
    }
}
//比较在任务提交阶段和执行阶段时间长短的结果

4、定时调度池

(1)延迟delay个单元后创建nThreads个线程,执行command任务

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

 

class RunableTest implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
           /* try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            System.out.println(Thread.currentThread().getName()+
                    "、"+i);
        }
    }
}

public class ExecutorTest2 {
    public static void main(String[]args){
        ScheduledExecutorService executorService =
                Executors.newScheduledThreadPool(3);
        for (int i = 0; i < 5; i++) {
            //延迟两秒后执行任务
            executorService.schedule(new RunableTest(),2,TimeUnit.SECONDS);
        }
        executorService.shutdown();
    }
}

(2)延迟delay个单元后每隔period时间单元就执行一次command任务 

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值