多线程基础操作
- 进程:是指运行中的应用程序,CPU进行分配资源的基本单位。
- 线程:是指进程中的一个执行流程,有时也成为执行场景。CPU执行的基本单位。
- 进程和线程的区别?
- 进程是一个独立的运行环境,而线程是在进程中执的的一个任务。他们两个本质区别是是否单独占有内存地址空间及其它系统资源
- 进程是操作系统进行资源分配的基本单位,而线程是操作系统进行资源调度的基本单位即CPU分配时间的单位 。
- 上下文切换:指CPU从一个进程(或线程)切换到另一个进程(或线程)。上下文是指某一时间点CPU寄存器和程序计数器的内容。
一、java线程的运行机制
虚拟机中,执行代码的任务主要是由线程来完成的,每个线程都有一个独立的程序计数器和方法调用栈。
- 程序计数器:也称为PC寄存器,当线程执行一个方法时,程序计数器指向方法区中下一条要执行的字节指令。
- 方法调用栈:简称方法栈,用来跟踪线程运行中的一系列的方法调用过程,栈中的元素称为栈桢,每当线程调用一个方法的时候,就会向方法栈压入一个新桢,桢用来存储方法的参数、局部变量和运算过程中的临时数据。
- 桢由三部分组成
- 局部变量区:存放局部变量和方法参数。
- 操作数栈:是线程的工作区,用来存放运算过程中生成的临时数据。
- 栈数据区:为线程执行指令提供相关的信息。
二、线程的创建
2.1、继承Thread类
public class Test01 extends Thread { private int count =5; @Override public void run() { count--; System.out.println(Thread.currentThread().getName()+" , "+count); } public static void main(String[] args) { Test01 t1 = new Test01(); t1.setName("jdy"); Test01 t2 = new Test01(); t2.setName("ws"); t1.start(); t2.start(); } }
2.2、实现Runnble接口
public class Thread02 implements Runnable { private int count=5; @Override public void run() { count--; System.out.println(Thread.currentThread().getName()+" , "+count); } public static void main(String[] args) { new Thread(new Thread02(),"jdy").start(); new Thread(new Thread02(),"ws").start(); } }
2.3、使用Callable和Future创建线程
使用Callable创建启动线程的步骤:
-
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值。
- 创建Callable实现类的实例,使用FutureTask类包装Callable对象。该FutureTask对象封装了该Callable对象的call()方法的返回值
- 使用FutureTask对象作为Thread对象的target创建并启动线程。
- 调用FutureTask对象的get()方法来获得子线程结束的返回值。
public class Thread03 implements Callable<Integer> { @Override public Integer call() throws Exception { int sum =0; for (int i = 0; i < 100; i++) { sum+=i; } return sum; } public static void main(String[] args) { FutureTask<Integer> futureTask = new FutureTask<>(new Thread03()); new Thread(futureTask).start(); try { Integer integer = futureTask.get(); System.out.println("integer = " + integer); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
Future接口用来代表Callable接口里call()方法的返回值,并且Future接口的实现类FutureTask实现了Runnable接口-—可以作为Thread类target。
- boolean cancel(boolean mayInterruptIfRunning):尝试取消该Future里关联的Callable任务。
-
V get():返回Callable 任务里 call()方法的返回值,调用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值。
-
V get(long timeout,TimeUnit unit):返回Callable 任务里call()方法的返回值。该方法让程序最多阻塞timeout和unit指定的时间,指定时间后Callable任务依然没有返回值,抛出TimeoutException异常。
-
boolean isCancelled():如果在Callable任务正常完成前被取消,则返回ture。
-
boolean isDone():如果Callable任务已完成,则返回true。
三、线程的状态转化
3.1、新建状态
用New语句创建的线程对象处于新建状态,此时它和其他的java对象一样,仅仅在堆区中分配了内存。
3.2、就绪状态
当一个线程对象创建后,其他的线程调用它的是他statr()方法,该线成就进入就绪状态,虚拟机会为他创建方法调用栈和程序计数器。处于这种状态的线程位于可运行池中,等待CPU的使用权。
3.3、运行状态
抢占到了CPU
3.4、阻塞状态
阻塞状态分为3种
-
- 位于对象等待池中的阻塞:运行中的线程执行wait();进入对象等待池
- 位于对象锁池中的阻塞:运行中的线程试图获取某个对象的同步锁。如果该线程的同步锁已经被其他线程占用,虚拟机就会把这个线程放到这个对象的锁池中。
- 其他阻塞:线程调用sleep(),或者join(),或者发出I/O请求时。就会进入这种状态
3.5、死亡状态
线程执行完run()方法:
Thread.isAlive(),判断一个线程是否活着,当线程处于死亡活着新建状态就返回false.
四、线程调度
所谓多线程的并发执行,线程轮流获取CPU的执行权。分别执行各自的任务。在可运行池中,会有多个处于就绪状态的线程等待CPU,虚拟机的一个任务就是负责线程的调度。线程调度,是指按照特定的机制为多个线程分配CPU的执行权。
- 分时调度:轮流执行
- 抢占式调度:让可运行池中优先级高的线程占用CPU,如果优先级相同则随机。
如果希望一个线程给另一个线程运行的机会:
- 调整线程的优先级
- 处于运行状态的线程调用Thread.sleep()
- 处于运行状态的线程调用Thread.yield();
- 处于运行状态的线程调用另一个线程的join();
4.1、调整线程的优先级
Thread.setPriority(int )优先级为1-10 默认为5,优先级高意味着抢占到CPU的机会多,但不保证保证每次都抢到
4.2、线程睡眠
当一个运行状态的线程执行sleep(),它就会放弃CPU,转入阻塞状态。不释放锁,Thread的sleep(long millis),以毫秒为单位。
4.3、线程让步
当线程在运行中执行Thread的yield()静态方法,如果此时,具有相同优先级的其他线程处于就绪状态,yield()方法将把当前运行的线程放到可运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,yield()方法什么也不做。
sleep()和yield()方法的区别:
-
sleep()方法会给其他线程运行机会,不考虑其他线程的优先级,因此会给较低优先级线程一个运行机会,yield()方法只会给相同优先级或者更高优先级的线程一个运行机会。
-
当线程执行sleep(long millis)方法后,将会到阻塞状态,参数millis指定睡眠时间,当线程执行了yield()方法以后,将转入就绪状态。
-
sleep()方法比yield()方法具有更好的可移植性,不依靠yield()方法来提高程序的并发性能,对于大多数情况,yield()方法唯一的用途就是测试期间人为地提高程序的并发性能,以帮助发现一些隐藏的错误。
4.4、等待其他线程结束
当前运行的线程可调用另一个线程的join()方法,当前运行的线程将转到阻塞状态,直到另一个线程运行结束,它才恢复运行。
public void join(); Public void join(long join);
五、后台线程
后台线程是指为其他线程提供服务的线程,也成为守护线程。Java虚拟机的垃圾回收线程是典型的后台线程,它负责回收其他线程不在使用的内存。
后台线程和前台线程相伴相随,只要有一个前台线程没有结束生命周期,那么后台线程就不会结束生命周期。
//设置当前线程是守护线程(设置成后台线程)。 Thread.setDaemon(true); //判断当前线程是否是守护线程。 Thread.isDaemon();
使用后台线程的注意事项:
-
java虚拟机能保证的是:当所有前台线程结束后,假如后台线程还在运行,java虚拟机中只能后台线程。
-
只有在线程启动前(即调用start()方法前)才能把他设置成守护线程。如果启动后再设置就会报异常(IllegalThreadStateException)。
-
前台线程创建的线程默认是前台线程,后台线程创建的线程默认是后台线程。
六、线程同步
原子操作是由相关的一组操作完成的,这些操作可能会操作与其他线程共享的资源。为了保证得到正确的运算结果,一个线程在执行原子操作期间,应该措施是其他线程不能操作共享资源。
线程不安全,是每个线程都有自己各自的工作内存,同时操作主内存时由于有网络延迟,导致每个线程的工作内存从主内存复制数据时,数据的不一至。
6.1、synchronized
为了保证每个线程能正常执行原子操作,java引入了同步机制。在代表原子操作的代码前加synchronized编辑,这样的代码叫同步代码。
因为某一个线程进入synchronized代码块前后,线程会获得锁,清空工作内存,从主内存拷贝共享变量最新的值到工作内存成为副本,执行代码,将修改后的副本的值刷新回主内存中,线程释放锁。
而获取不到锁的线程会阻塞等待,所以变量的值肯定一直都是最新的。
//同步代码块
synchronized (obj){ }
obj:就是线程同步器。上面代码的含义就是,线程开始执行前,必须获取同步监视 器的锁定。Java允许使用任何对象作为同步监视器。
同步监视器的目的:阻止两个线程对同一个共享资源进行并发访问,因此通常使用可能被并发访问的共享资源充当同步监视器。
public class Test01 extends Thread { private int count = 5; @Override public void run() { synchronized (Test01.class) { count--; System.out.println(Thread.currentThread().getName() + " , " + count); } } }
同步方法就是使用Synchronized关键字修饰某个方法,同步方法的监视器是this
// synchronized同步方法
public class Test01 extends Thread { private int count = 5; @Override public synchronized void run() { count--; System.out.println(Thread.currentThread().getName() + " , " + count); } }
6.2、释放同步监听器的锁定
释放监听器锁定的情况:
- 同步监听器的监听的代码结束。
- 同步代码块、同步方法中出现 return 或者 break终止了代码的执行。
- 同步代码块、同步方法中出现出现Exception导致改代码块,该方法结束。
- 当前线程的同步代码块、同步方法中执行了监听器的wait()方法,则当前线程暂停,释放锁资源。
不是释放同步监听器情况:
- 当前线程调用Thread.sleep(),Thread.yield(),来暂停线程不会释放同步监听器。
- 线程执行同步代码快时,调用线程的suspend(),将线程挂起,该线程不会释放同步监听器。
6.3、同步锁(LOCK)
使用Lock与使用统发方法有点相似,只是使用Lock时显式使用Lock对象作为同步锁,而使用同步方法时系统隐式使用当前对象作为同步监视器,同时符合“加锁->修改->释放锁”的操作模式。
6.4、死锁
/** *口红 */ public class LipStick { }
/** * 镜子 */ public class Mirror { }
/** * 化妆间 */ public class Markup extends Thread { // 化妆间只有一个镜子,一个口红 static LipStick lipStatic = new LipStick(); static Mirror mirror = new Mirror(); boolean choice = false;// 选择 public Markup(boolean choice) { this.choice = choice; } @Override public void run() { try { markup(choice); } catch (InterruptedException e) { e.printStackTrace(); } } // 化妆的行为 public void markup(boolean choice) throws InterruptedException { if (choice) { synchronized (lipStatic) { System.out.println(Thread.currentThread().getName() + "拿到口紅"); //抹口红 Thread.sleep(1000L); synchronized (mirror) { System.out.println(Thread.currentThread().getName() + "拿到鏡子"); } } } else { synchronized (mirror) { System.out.println(Thread.currentThread().getName() + "拿到鏡子"); //照镜子 Thread.sleep(1000L); synchronized (lipStatic) { System.out.println(Thread.currentThread().getName() + "拿到口紅"); } } } } }
public class DeadLock { public static void main(String[] args) { Markup mp1= new Markup(true); mp1.setName("张三"); mp1.start(); Markup mp2= new Markup(false); mp2.setName("李四"); mp2.start(); } }
6.5、Volatile
Volatile保证线程间变量的可见性,即当线程A对变量X进行了修改后,在线程A后面执行的其他线程能看到变量X的变动,更详细的说是要符合一下两个规则:
-
当前线程对变量进行修改后,要立刻回写到主内存
-
其他线程对变量读取的时候,要从主内存中读取,而不是缓存。
各线程的工作内存之间彼此独立、互不相见,在线程启动的时候,虚拟机为每个内存分配一块工作内存,不仅包含了线程内部定义的局部变量,也包含了线程所需要使用的共享变量(非线程内构造的对象)的副本,即为了提高执行效率。
Volatile是不错的机制,但是Volatile不能保证原子性。
public class VolalileTest { //static boolean flag = false;//没有volatile修饰, static volatile boolean flag = false; public static void main(String[] args) { //1.启动一个线程改变改变共享的值 new Thread(() -> { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; System.out.println("flag = " + flag); }).start(); //2.循环获取改变的值 while (true) { if (flag) { System.out.println("============="); break; } } } }
6.6、单例模式(DCL)
public class SingletonTest4 { static volatile SingletonTest4 singleton = null; //构造器私有 private SingletonTest4() { } public static SingletonTest4 getInstance() { //双重检测,如果对象存在不用等待 if (singleton != null) { return singleton; } synchronized (SingletonTest4.class) { if (singleton == null) { singleton = new SingletonTest4(); } } return singleton; } public static void main(String[] args) { new Thread( () ->{ System.out.println(SingletonTest4.getInstance()); }).start(); System.out.println(SingletonTest4.getInstance()); } }
6.7、ThreadLcoal
官方描述:ThreadLcoal类用来提供线程内部的局部变量。这种变量在多线程环境访问(通常通过set()/get()方法访问)时能保证各个线程的变量相对独立于其他线程的变量,ThreadLocal实例通常来说都是 private static 类型的,用于关联线程和线程上下文。
ThreadLcoal的作用是:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量传递的复杂度。
总结
- 线程并发:在多线程并发场景下
-
传递数据:我们可以通过ThreadLocal在同一个线程,不同组件中传递公共变量
- 线程个隔离:每个线程的变量都是独立的,不会相互影响。
基本使用:常用方法:Set/get
Synchronized和threadLocal的区别:
|
Synchronized |
threadLocal |
原理 |
同步机制采用“已时间换空间的方式”,只提供了一份变量,让不同的线程排队访问。 |
ThreadLocal采用“已空间换时间的方法”为每一个线程都提供一个变量的副本,从而实现同时访问而互不干扰 |
侧重点 |
多个线程之间访问资源的同步性 |
多线程中让每个线程之间的数据相互隔离 |
ThreadLocal的核心结构
dk1.8 ThreadLocal的设计是:每个Thread维护一个ThreadLocalMap哈希表,这个哈希表的key是thradlocal实例本身,value才是真正要存储的值Object
1. 每个Thread Local线程内部都有一个Map(ThreadLocalMap)
2. Map里面存储ThreadLocal对象(key)和线程的变量副本(value)
3. Thread内部的map是由Threadlocal维护的,由Threadlcoal负责向Map获取和设置线程线程的变量值
4. 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
七、线程通信
7.1、传统的线程通信
对于使用Synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器,所以在同步方法中直接调用这3个方法。
对象使用Synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用该对象调用这3个方法。
- wait():导致当前线程等待,知道其他线程调用该同步监视器的notify()方法或者notifyAll()方法来唤醒该线程。
- notify():唤醒在此同步监视器上等待的单线程,如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意性的。
- notifyAll():唤醒在此同步监视器上等待的所有线程,只有当前线程放弃对该同步监视器的锁定后,才可以执行唤醒的线程。
7.2、使用Condition 控制线程通信
如果程序不使用Synchronized关键字来保证同步,而是直接使用Lock对象保证同步,则系统中不存在隐式的同步监视器,也就是不能使用Object 的 wait(),notify(),notifyAll()进行线程间痛下,
当使用Lock对象来保证同步时,JAVA提供了一个Condition类来保持协调,使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock 对象,Condition对象也可以唤醒其他处于等待的线程。
调用Lock对象的newCondition()获取去Locks实例的Condition 实例, Condition类的3个方法。
- awite():类似wait()。
- signal():唤醒在此ock对象上等待的单线程对象。
- signalAll():唤醒在此lock对象上等待的所有线程。
7.3、使用阻塞队列(BlockingQueue)控制线程通信
Queue队列接口有一个子接口BlockingQueue作为线程同步工具,BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue中存放数据时,如果该队列已满,则线程被阻塞,当消费者线程试图从BlockingQueue中取元素时,如果队列已空,则线程被阻塞。
BlocakingQueue提供了如下两个支持阻塞的方法。
- put(E e):尝试把元素E放入BlockingQueue中,如果该队列的元素已满,则阻塞该线程。
- take():尝试从BlockingQueue的头部取元素,如果该队列的元素为空,则阻塞该线程。
BlockingQueue继承了Queue,所以也可使用Queue接口中的方法:
- 在队列尾部插入元素。add(E e)、offer(E e)、put(E e)方法,当队列满时,这3个方法分会抛出异常、返回false阻塞队列。
- 在队列头部删除饼干会删除的元素。包括remove()、poll()、take()方法,当该队列已空,这3个方法分别会抛出异常、返回false、阻塞队列。
- 在队列头部取出但不删除元素。包括element()和peek()方法,当队列已空时,这两个方分别抛出异常,返回false:
八、线程组
java 使用ThreadGroup来表示线程组,线程组可以对一批线程进行分类管理,某个线程加入指定的线程组后,该线程将一直属于该线程组,直到该线程死亡,线程运行中途不能改变它所属的线程组。
用构造器来设置新创建的线程属于哪个线程组:
// 以target的run()方法作为线程执行体创建新线程,属于group线程组。 Thread(ThreadGroup group,Runnable target) // 以target的run()方法作为线程执行体创建新线程,该线程属于group线程组,且线程名称为name; Thread(ThreadGroup group,Runnable target,String name) // 创建新线程,新线程名称为name,属于group线程组。 Thread(ThreadGroup group,String name)
Thread 类提供了一个getThreadGroup()方法获取线程组。ThreadGroup提供了两个简单的构造器来创建实例:
// 以指定的线程组名称来创建新的线程组。 ThreadGroup(String name) //已制定的名字,指定的父线程组来创建新鲜承组。 ThreadGroup(ThreadGroup parent,String name)
也就是说,线程组总会具有一个字符串类型的名字,该名字可通过调用ThreadGroup的getName()方法来获取,但不允许改变线程组的名字。
ThreadGroup类提供类如下几个常用的方法来操作整个线程组里面的所有线程。
// 返回此线程组中活动线程的数目。 int activeCount() // 中断此线程中的所有线程 interrupt() // 判断该线程组是否是后台线程。 isDaemon() //把该线程组设置成后台线程组 setDaemon(boolean daemon) //是指线程的优先级 setMaxPriority(int pri)
九、线程池
系统启动一个新的线程的成本是比较高的,线程池在系统启动时即创建大量的空闲的线程,程序将一个Runnable对象或Callable 对象传给线程池,线程池就会启动一个线程来执行它们的run()或者call()方法,当run()或call()方法执行结束后,该线程并不会死,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run()或call()方法。
使用线程池可以有效地控制系统中并发线程的数量,方系统中宝行大量并发线程时,会导致系统性能剧烈下降。甚至导致JVM崩溃,而线程池的最大线程数参数可以控制系统中并发线程数不超过次数。
9.1、生产线程池
使用Executors工厂类来生产线程池。返回ExecutorService对象,该对象代表一个线程池,它可以执行Runnable 对象或Callable对象代表的线程,ExecutorService代表尽快执行(只要线程池中有空闲线程,就立即执行线程任务),程序只要将Runnable或Callable对象(代表的线程任务)提交给该线程池,该线程成池就会尽快执行该任务。
1、创建线程池
- newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中。
//1.创建带有缓存功能的线程池 ExecutorService pool = Executors.newCachedThreadPool();
- newFixedThreadPool(int nThreads):创建一个可重用的、具有固定线程数的线程池。
//2.创建一个带有固定线程数的线程池 ExecutorService pool1 = Executors.newFixedThreadPool(4);
- newSingleThreadExecutor():创建一个只有单线程的线程池,它相当于调用newFixedThreadPool()方法时传入参数1.
//3.创建一个单线程的线程池 ExecutorService executorService = Executors.newSingleThreadExecutor();
2、ExecutorService的3种方法:
//将一个Runnable对象提交给指定的线程池,线程池将在空闲线程时执行Runnable对象代表的任务。其中Future对象代表Runnable任务的返回值---
但是run()方法没有返回值,所有Future对象将在run()方法执行结束后返回null。但可以调用Future的isDone()、isCancelled()方法来获得Runnable对象的执行状态
Future<?>submit(Runnable task)
//线程池将在有空闲线程时,执行Runnable对象代表的任务,其中result显示的指定线程执行结束后的返回值,所以Future对象将在run()方法执行结束后返回result <T> Future<T>submit(Runnable task,T result)
//线程池在有空闲现场时执行Callable对象代表的任务。其中Future代表Callable对象里call()方法的返回值。 <T> Future<T>submit(Callable<T> task)
9.2、ScheduledExecutorService
ScheduledExecutorService ,它是ExecutorService的子类,它可以在指定延迟后执行线程任务。
1、使用ScheduledExecutorService创建线程
//创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。corePoolSize指池中所保存的线程数,即使线程是空闲的也被保存在线程池内。 newScheduledThreadPool(int corePoolsize) //创建只有一个线程的线程池,它可以在指定延迟后执行线程任务。 newSingleThreadScheduleExecutor()
2、ScheduledExecutorService的方法
// 指定Callable任务将在delay延迟后执行 ScheduledFuture<V> schedule(Callable<V> callable,long delay,timeUnit unit) // 指定command任务将在delay延后执行。 ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit) // 指定command任务将在delay延迟后执行,而且以设定频率重复执行。也就是说,在initialDelay后开始执行,依次在initialDelay+period/initialDelay+2*period...处重复执行,依次类推 ScheduleFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit) // 创建并执行一个给定初始延迟后首次启用的定期操作。随后在每一个执行终止和下一个执行开始之间都存在给定的延迟,如果任务在任一次执行时遇到异常,就会取消后续执行,否则,只能通过程序来显示取消或终止任务 ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit)
当完成一个线程池后,应该调用该线程池的shutdown()方法,该方法将启动线程池的关闭序列,调用shutdown()方法后的线程池也不在接收新的任务,另外也可以调用线程池的shutdownNow方法,来关闭线程池,该方法,就将视图停止所有正在执行的活动任务。
使用线程池执行线程任务的步骤
- 调Executors创建ExecutorService对象
- 创建Runnable实现类或Callable实现类的实例,作为线程任务
- 调用ExecutorService对象的submit()方法来提交Runnable实例或callable实例
- 当不想提交任何任务时,调用ExecutorService对象的shutdown()方法来关闭线程池。
9.3、 ForkJoinPool线程池
ForkJoinPool支持将一个任务查分成多个小任务并行计算,在把小任务的结果合并成总的计算结果,ForkJoinPool是ExecutorService的实现类,因此是一种特殊的线程池。
ForkJoinPool的常用构造器:
// 创建一个包含parallelism个并行线程的ForkJoinPool ForkJoinPool(int parallelism): // 以Runtime.AvailableProcessors()方法的返回值作为parallelism参数来创建ForkJoinPool ForkJoinPool():
调用ForkJoinPool的submit(ForkJoinTask task)或者invoke(ForkJoinTask task)方法来执行指定的任务了。其中ForkJoinTask代表一个可以并行、合并的任务。ForkJoinTask为抽象类,同时还有两个抽象的子类RecursiveTask(代表有返回值的任务)和recursiveAction(代表没有返回值的任务)
十、定时器
定时器类java.util.Timer.,TimerTask类。安排指定的任务task在指定的时间firstTime开始进行重复的固定速率period执行.
/** * Timer * 构造器: * Timer():创建一个新的计时器。 * Timer(boolean isDaemon) 创建一个新的定时器,其相关线程可以指定为 run as a daemon 。 * Timer(String name) :创建一个新的定时器,其相关线程具有指定的名称。 * Timer(String name, boolean isDaemon) 创建一个新的定时器,其相关线程具有指定的名称,可以指定为 run as a daemon 。 * 方法:void cancel() 终止此计时器,丢弃任何当前计划的任务 * int purge() 从该计时器的任务队列中删除所有取消的任务。 * void schedule(TimerTask task, Date firstTime, long period) :从指定 的时间开始 ,对指定的任务执行重复的 固定延迟执行 。 * void schedule(TimerTask task, long delay) 在指定的延迟之后安排指定的任务执行。 * void schedule(TimerTask task, long delay, long period) 在指定 的延迟之后开始 ,重新执行 固定延迟执行的指定任务。 * void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 从指定的时间 开始 ,对指定的任务执行重复的 固定速率执行 。 * void scheduleAtFixedRate(TimerTask task, long delay, long period) 在指定的延迟之后 开始 ,重新执行 固定速率的指定任务。 */ public static void timer4() { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR_OF_DAY, 12); // 控制时 calendar.set(Calendar.MINUTE, 0); // 控制分 calendar.set(Calendar.SECOND, 0); // 控制秒 Date time = calendar.getTime(); Timer timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { publicvoid run() { System.out.println("-------设定要指定任务-------"); } }, time, 1000 * 60 * 60 * 24);// 这里设定将延时每天固定执行 }
-
Run()方法中方要定时执行的任务。
-
Timer类的schedule(TimerTask task,long delay,long period);
-
task:任务
-
delay:表示延迟执行时间
-
period:表示每次执行任务的间隔时间。