java基础(6)

java基础(6)

1.初识多线程

单线程与多线程:如果程序只有一条执行路径,那么该程序就是单线程程序;如果程序有多条执行路径,那么该程序就是多线程程序。

想要了解多线程,我们就需要先了解线程,想要了解线程,就必须先了解进程,因为线程是依赖于进程的

进程

概述:进程是系统进行资源分配和调用的独立单位,每一个进程都有自己的内存空间和系统资源,简单的说,进程就是正在运行的程序,如网易云音乐,微信,Word等在运行的程序

多进程的意义:

单进程的计算机只能做一种事情,而我们现在的计算机都可以做多种事情,也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务,如边玩游戏边听音乐,多进程的使用可以提高CPU的使用率

问题:多个进程间是同时发生的么?

答:不是,因为单CPU在某一个时间点上只能做一件事情,而多个进程能给我们同时进行的感觉是因为cpu在做着程序间的高效切换,现在的计算机都是多核的了,但是我们的进程往往是比cpu多的,所以,cpu还是需要高效切换来给我们达到同时进行多个进程的感觉

线程

概述:在同一个进程内又可以执行多个任务,而这每一个任务就可以看成是一个线程,线程是程序的执行单元,执行路径,是调用cpu的最小执行单位,单线程就是程序只有一条执行路径,而多线程就是有多条执行路径

多线程的意义:

多线程的存在,不是提高程序的执行速度,其实是为了提高程序的使用率;程序的执行其实都是在抢CPU的资源,CPU的执行权;多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多的话,就会有更高几率抢到CPU的执行权;我们是不敢保证哪一个线程能够在哪个时刻抢到的,所以线程的执行具有随机性

并行与并发的区别

并行是逻辑上的同时发生,指的是在某一个时间内同时运行多个程序;并发则是物理上的同时发生,指在某一个时间点同时运行多个程序

java程序的运行原理

由java命令启动JVM,JVM启动就相当于启动了一个进程,接着由该进程创建一个主线程去调用main方法

问题:jvm虚拟机的启动是单线程的还是多线程的

答:多线程的,因为垃圾回收线程也要先启动,不然很容易导致内存溢出,现在的垃圾回收线程加上前面的主线程,最少启动两个线程,所以说jvm是多线程的

2.如何实现多线程

由于线程是依赖进程存在的,所以我们应该先创建一个进程出来,而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程,java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序,但是java可以去调用C/C++写好的程序来实现多线程程序,由C/C++去调用系统功能创建进程,然后由java去调用这些东西,然后提供一些类供我们使用,我们就可以实现多线程程序了

实现多线程的方法:

  • 方式一:继承Thread类
  • 方式二:实现Runable接口
  • 方式三:实现Callable接口

继承Thread类实现多线程

步骤:

  • 自定义类MyThread继承Thread类
  • MyThread类里面重写run()方法
    • 这个run方法用来包含那些被线程执行的代码
  • 创建对象
  • 启动线程

run()和start的区别:

run()方法仅仅是封装被线程执行的代码,直接调用是普通方法;start()方法首先启动了线程,然后由jvm去调用该线程的run()方法

获取和设置线程对象的名称的方法(谁先抢到谁就是Thread-0):

  • public final String getName()
  • public final void setName(String name):设置线程的名称,也可以通过带参构造方法设置线程的名称

针对不是Thrad类的子类中如何获取线程对象名称呢?

  • public static Thread currentThread():返回当前正在执行的线程对象
  • Thread.currentThread().getName():获取当前正在执行的线程对象的名称

这种方式相比继承Thread类的好处:

  • 可以避免由于java单继承带来的局限性
  • 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想

代码实现:

//MyThread.java

public class MyThread extends Thread{
	@Override
	public void run() {
		//一般来说,被线程执行的代码肯定是比较耗时的代码,所以我们使用循环
		for(int i = 0; i < 200; i++){
			System.out.println(getName() + ":" + i);
		}
	}
}

//MyThreadDemo.java

public class MyThreadDemo {
	public static void main(String[] args) {
		MyThread mh1 = new MyThread();
		MyThread mh2 = new MyThread();
		
		mh1.start();
		mh2.start();
	}
}

通过实现Runable接口实现多线程(推荐使用)

步骤:

  • 自定义类MyRunnable实现Runnable接口
  • 重写run()方法
  • 创建MyRunnable类的对象
  • 创建Thread类的对象,并把C步骤的对象作为构造参数传递

代码实现:

//MyRunnable.java

public class MyRunnable implements Runnable {

	@Override
	public void run() {
		for(int i = 0; i < 100; i ++){
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}

}

//MyRunnableDemo.java

public class MyRunnableDemo {
	public static void main(String[] args) {
		MyRunnable my = new MyRunnable();
		
		/*Thread t1 = new Thread(my);
		Thread t2 = new Thread(my);
		//设置线程名字
		t1.setName("线程一");
		t2.setName("线程二");*/
		//与上面代码等价
		Thread t1 = new Thread(my, "线程一");
		Thread t2 = new Thread(my, "线程二");
		
		t1.start();
		t2.start();
	}
}

通过实现Callable接口实现多线程

概述:Callable是一个带泛型的接口,通过它做多线程的话需要依赖于线程池(后面回讲线程池)

使用步骤:

1.创建一个线程池对象,控制要创建几个线程对象

  • public static ExecutorService newFixedThreadPool(int nThreads):创建有多个线程对象的线程池

2.调用如下方法即可:

  • Future submit(Callable task)
    • 返回值Future是一个接口,它的get方法返回计算结果

3.线程池开启之后不会自动关闭,如果想关闭,需要执行以下方法

  • public void shutdown():关闭线程池

示例代码1(不带泛型的Callable接口对象代表的多线程)

//Mycallable.java

import java.util.concurrent.Callable;
//不带泛型的Callable对象代表的线程
public class MyCallable implements Callable{

	@Override
	public Object call() throws Exception {
		for(int i = 0; i < 100; i ++){
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
		return null;
	}

}

//ExecutorDemo.java

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorsDemo {
	public static void main(String[] args) {
		//创建一个线程对象,控制要创建几个线程对象
		ExecutorService pool = Executors.newFixedThreadPool(2);
		//执行Callable对象代表的线程
		pool.submit(new MyCallable());
		pool.submit(new MyCallable());
		//关闭线程池
		pool.shutdown();
	}
}

实例代码2(带泛型的Callable对象代表的多线程,求和案例)

//MyCallable2.java

import java.util.concurrent.Callable;
//带泛型的Callable对象代表的多线程
public class MyCallable2 implements Callable<Integer>{

	private int number;
	public MyCallable(int number){
		this.number = number;
	}
	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for(int x = 1; x <= number; x ++){
			sum += x;
		}
		return sum;
	}
}

// ExecutorsDemo2

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorsDemo2 {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//创建一个线程对象,控制要创建几个线程对象
		ExecutorService pool = Executors.newFixedThreadPool(2);
		//执行Callable对象代表的线程
		Future<Integer> f1 =  pool.submit(new MyCallable(100));
		Future<Integer> f2 = pool.submit(new MyCallable(200));
		
		Integer i1 = f1.get();
		Integer i2 = f2.get();
		
		System.out.println(i1);
		System.out.println(i2);
		//关闭线程池
		pool.shutdown();
	}
}

3.线程调度

概述:假如我们的计算机只有一个CPU,那么,CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令

线程的两种调度模型:

  • 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
  • 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么,会随机选择一个,优先级高的线程获得的CPU时间片多一些,java就是使用这种模型

那么,java中程序的优先级是怎么样的,可以设置优先级么?我们接下来就来看看java中的优先级是什么样的

设置和获取线程对象优先级的方法:

  • public final void setPriority(int newPriority):更改线程的优先级
  • public final int getPriority():返回线程对象的优先级

注意:

  • 线程默认优先级是5
  • 线程优先级的范围是1-10
  • 线程优先级高的仅仅表示线程获取的CPU时间片的几率高,要在次数比较多,或者多次运行的时候,才能看到明显的效果(优先级高的先执行)

线程控制

我们已经知道了线程的调度,接下来我们就可以使用如下方法对象来对线程进行控制

线程休眠

  • public static void sleep(long mills):休眠mills毫秒

线程加入

  • public static void join():使用了join方法的线程执行完后,才能执行其他线程

线程礼让

  • public static void yield():暂停当前正在执行的线程对象,并执行其他线程,它会让多个线程的执行更加和谐一些,但是不能靠他保证一人一次

后台线程(守护线程)

  • public final void setDaemon(boolean on):
    设置为true时,即设置为守护线程,当守护的线程结束了的话,它们再在cpu上跑一会就得结束线程,而不是完全执行完自己线程的内容再结束

中断线程:

  • public final void stop():该方法已经过时了,具有不安全性,不推荐使用
  • public void interrupt():中断线程,把线程的状态终止,并抛出一个InterruptException异常

4.线程生命周期

线程的生命周期:

  • 新建:创建线程对象
  • 就绪:有执行资格,没有执行权
  • 运行:有执行资格,有执行权
    • 阻塞:由于一些操作让线程处于了该状态,没有执行资格,没有执行权,而另一些操作却可以把它激活,激活后处于就绪状态
  • 死亡:线程对象变成垃圾,等待被回收

多线程的状态转换图如下:
java基础(6)

5.线程安全问题

再讲解我们的线程安全问题之前,我们先来看一个案例:

三个窗口同时出售一百张电影票的案例:

//MyRunnable2.java

public class MyRunnable2 implements Runnable{

	private int ticket = 100;
	@Override
	public void run() {
		while(true){
			if(ticket > 0){
				try {
					//在真实场景中,会有网络延迟等问题,所以添加了100ms的延迟
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "票");
			}else{
				break;
			}	
		}
	}

}

//Demo2.java

public class Demo2 {
	public static void main(String[] args) {
		MyRunnable2 my = new MyRunnable2();
		
		Thread t1 = new Thread(my, "窗口1");
		Thread t2 = new Thread(my, "窗口2");
		Thread t3 = new Thread(my, "窗口3");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

如果你复制这段代码并执行了几次,就会发现,执行结果居然有卖相同张票,第0张票甚至卖负数的票的情况,这是为什么呢?

出现相同票的原因:CPU的一次操作必须是原子性的,
由于我们的程序对ticket变量的操作不是原子性的,这就会导致当我们再卖出某一张票之后,还没等到对ticket变量--,下一个线程就接着卖这同一张票了

出现负票的原因:随机性和延迟导致的,因为有了延迟和随机性,导致三个线程都进去了if判断语句,但是要等100ms,然后再执行下一个,所以就会导致当卖出第1张票之后,还会卖出第0和负数票

通过上面这个案例,我们了解到了多线程的不安全性,那产生线程不安全的原因以及怎么解决,接下来就要正式来说说了

线程安全问题产生的原因(以后我们判断一个程序是否会有线程安全问题的标准):

  • 是否有多线程环境
  • 是否有共享数据
  • 是否有多条语句操作共享数据

怎么解决线程安全问题

通过看产生线程安全问题的原因,我们可以知道,前两种导致线程不安全的原因我们无法解决,只能解决第三个原因了

思想:把多条语句操作共享数据的代码给包起来,让某个线程在执行的时候,别人不能来执行,而java就给我们提供了这样的方法:同步进制

同步代码块:

synchronized(对象){
	需要同步的代码;
}

注意:同步可以解决安全问题的根本原因就在那个对象上,该对象如同锁的功能,而多个线程必须用的是用一把锁

对上面案例的改进:

//MyRunnable3.java

public class MyRunnable3 implements Runnable{

	private int ticket = 100;
	//创建锁对象
	private Object obj = new Object(); 
	@Override
	public void run() {
		while(true){
			synchronized(obj){
				if(ticket > 0){
					try {
						//在真实场景中,会有网络延迟等问题,所以添加了100ms的延迟
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "票");
				}else{
					break;
				}	
			}
			
		}
	}

}

//Demo3.java

public class Demo3 {
	public static void main(String[] args) {
		MyRunnable2 my = new MyRunnable2();
		
		Thread t1 = new Thread(my, "窗口1");
		Thread t2 = new Thread(my, "窗口2");
		Thread t3 = new Thread(my, "窗口3");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

同步的特点

前提:程序是多个线程的

同步的好处:同步的出现解决了多线程的安全问题

同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是非常耗费资源的,无形种会降低程序的运行效率,而且同步比较容易产生死锁

同步代码块以及把synchronized关键字加到方法上

1.同步代码块的锁对象是任意对象

2.同步方法的锁对象是this

3.静态方法的锁对象是类的字节码文件对象

6.JDK5的Lock锁

概述:虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并 没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,jdk5之后就提供了一个新的锁对象Lock

Lock:是一个接口,可以通过ReentrantLock具体类来实现

Lock获取锁和释放锁的方法:

  • void lock():获取锁
  • void unlock():释放锁

使用Lock锁售卖电影票的代码:

// MyRunnable4.java

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

public class MyRunnable4 implements Runnable{

	private int ticket = 100;
	private Lock lock = new ReentrantLock();
	@Override
	public void run() {
		while(true){
			//获取锁
			try{
				lock.lock();
				if(ticket > 0){
					try {
						//在真实场景中,会有网络延迟等问题,所以添加了100ms的延迟
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "票");
				}else{
					break;
				}
			}finally{

				//释放锁
				lock.unlock();
			}	
		}
	}

}

//Demo4.java

public class Demo2 {
	public static void main(String[] args) {
		MyRunnable2 my = new MyRunnable2();
		
		Thread t1 = new Thread(my, "窗口1");
		Thread t2 = new Thread(my, "窗口2");
		Thread t3 = new Thread(my, "窗口3");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

死锁问题

概述:有两个或两个以上的线程在争夺资源的过程中,发生的一种互相等待的现象

死锁的代码实现:

//MyLock.java

public class MyLock {
	public static final Object objA = new Object();
	public static final Object objB = new Object();
}

//DieLock

public class DieLock extends Thread{
	private boolean flag;
	
	public DieLock(boolean flag){
		this.flag = flag;
	}
	@Override
	public void run() {
		if(flag){
			synchronized (MyLock.objA) {
				System.out.println("if objA");
				synchronized (MyLock.objB) {
					System.out.println("if objB");
				}
			}
		}else{
			synchronized (MyLock.objB) {
				System.out.println("else objB");
				synchronized (MyLock.objA) {
					System.out.println("else objA");
				}
			}
		}
	}
}

//DieLockDemo

public class DieLockDemo {
	public static void main(String[] args) {
		DieLock dl1 = new DieLock(true);
		DieLock dl2 = new DieLock(false);
		
		dl1.start();
		dl2.start();
	}
}

线程间通信问题

概述:不同种类的线程间针对同一个资源的操作,如生产者消费者模式

生产者消费者模式的实现代码:

//Person.java

public class Person {
	private String name;
	private int age;
	boolean flag = false;//标记位,标记是否有人了,flase代表没人
	public Person(){};
	public Person(String name, int age){
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	
}

//Product.java

public class Product implements Runnable{
	//这里接受一个Person对象,因为要与消费者共享资源
	private Person p;
	private int x = 0;
	public Product(Person p){
		this.p = p;
	}
	@Override
	public void run() {
		while(true){
			//这里用p对象作为锁,因为生产者和消费者需要使用同一把锁对象来保证线程安全
			synchronized (p) {
				//假设有人了
				if(p.flag){
					try {
						p.wait();//有人的情况下,就不生产人了,阻塞住,并立即释放锁,将来醒来时,是从这里醒过来的
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				//没人的情况下,生产人出来
				if(x % 2 == 0){
					p.setName("卢一");
					p.setAge(18);
				}else{
					p.setName("黄伊");
					p.setAge(17);
				}
				x ++;
				////注意这里的修改标记值和唤醒线程必须放在锁里面,如果没有,将会报 java.lang.IllegalMonitorStateException异常
				//生产人出来了,就把标记值改为true
				p.flag = true;
				//唤醒线程
				p.notify();
			}
			
		}
		
	}
	
}

//Customer.java

public class Customer implements Runnable {
	//这里接受一个Person对象,因为要与生产者共享资源
	private Person p;
	public Customer(Person p){
		this.p = p;
	}
	@Override
	public void run() {
		while(true){
			//这里用p对象作为锁,因为生产者和消费者需要使用同一把锁对象来保证线程安全
			synchronized (p) {
				if(!p.flag){
					try {
						p.wait();//没人的情况下,就阻塞等待,将来醒来从这里醒来
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				//有人了就看看这个人是谁(消费掉)
				System.out.println(p.getName() + "-----" + p.getAge());
				//注意这里的修改标记值和唤醒线程必须放在锁里面,如果没有,将会报 java.lang.IllegalMonitorStateException异常
				//看腻了,想看下一个人(消费掉了),修改标记值为没人
				p.flag = false;
				//唤醒线程
				p.notify();
			}
			
		}
		
	}	
}

Test.java

public class Test {
	public static void main(String[] args) {
		//创建资源
		Person p = new Person();
		
		Product pro = new Product(p); 
		Customer c = new Customer(p);
		//创建两条不同种类线程
		Thread product = new Thread(pro);
		Thread customer = new Thread(c);
		//启动线程
		product.start();
		customer.start();
	}
}

//结果:

卢一-----18
黄伊-----17
卢一-----18
黄伊-----17
卢一-----18
黄伊-----17
卢一-----18
黄伊-----17
卢一-----18
黄伊-----17
...

从以上的例子中,我们可以知道,即使是不同种类的线程,也需要相同的锁对象来保证数据的安全性;而且为了更符合生成者消费者模式(生产一个就消费一个),我们需要引入等待唤醒机制

等待唤醒的三个方法(是Object类中提供的方法):

  • wait():等待
  • notify():唤醒单个线程
  • notifyAll():唤醒所有线程

为什么这些方法定义不定义在Thread类中,而是Object类中呢?

答:这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象就是任意锁对象,所以,这些方法必须定义在Object类中

7.线程组

概述:把多个线程组合在一起,它可以对一批线程进行分类管理,java运行程序直接对线程进行控制

那么,如果我们想要知道某一个线程所在的线程组,怎么办?

我们可以通过以下方法得到:

  • public final ThreadGroup getThreadGroup():获取线程所在的线程组对象,在通过getName()方法即可获得线程组名称

ThreadGroup类的使用:

  • ThreadGroup(String name):构造方法,创建一个线程组对象

    • 如:ThreadGroup tg = new ThreadGroup("线程组名");
  • 通过对线程组对象进行线程控制,达到对这个线程组里的所有线程进行线程控制的效果

    • 如:tg.setDaemon(true):设置了该线程组对象下的所有线程为守护线程

8.线程池

概述:程序启动一个新线程成本是比较高的,因为他涉及到要与操作系统进行交互,而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期比较短的线程时,更应该考虑使用线程池,线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用,在jdk5之前,我们必须手动实现自己的线程池,从jdk5之后,java内置支持线程池

使用线程池的步骤:

1.创建一个线程池对象,控制要创建几个线程对象

  • public static ExecutorService newCacheThreadPool():创建一个具有缓冲功能的线程池
  • public static ExecutorService newFixedThreadPool(int nThreads):创建有多个线程对象的线程池
  • public static ExecutorService:造一个线程池,相当于第二个方法里面的nThread值为1 newSingleThreadExecutor():

2.这种线程池的可以执行:

  • 实现Runnable接口的对象的线程
  • 实现Callable即可的对象的线程

3.调用如下方法即可:

  • Feture<?> submit(Runnable task)
  • Future submit(Callable task)
    • 返回值Future是一个接口,它的get方法返回计算结果

4.线程池开启之后不会自动关闭,如果想关闭,需要执行以下方法

  • public void shutdown():关闭线程池

示例代码:

//MyRunnable.java

public class MyRunnable implements Runnable {

	@Override
	public void run() {
		for(int i = 0; i < 100; i ++){
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}

}

//ExecutorsDemo.java

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorsDemo {
	public static void main(String[] args) {
		//创建一个线程对象,控制要创建几个线程对象
		ExecutorService pool = Executors.newFixedThreadPool(2);
		//执行Runnable对象代表的线程
		pool.submit(new MyRunnable());
		pool.submit(new MyRunnable());
		//关闭线程池
		pool.shutdown();
	}
}

8.匿名内部类实现多线程程序

示例代码:

public class Demo {
	public static void main(String[] args) {
		//继承Thread类实现多线程
		new Thread(){
			@Override
			public void run() {
				for(int i = 0; i < 100; i ++){
					System.out.println(Thread.currentThread().getName() + ":" + i);
				}
			};
		}.start();
		
		//实现Runnable接口实现多线程
		new Thread(new Runnable(){

			@Override
			public void run() {
				for(int i = 0; i < 100; i ++){
					System.out.println(Thread.currentThread().getName() + ":" + i);
				}
			}
			
		}){}.start();
		
		//走的是Thread
		new Thread(new Runnable(){

			@Override
			public void run() {
				for(int i = 0; i < 100; i ++){
					System.out.println("走的是Runnable:" + i);
				}
			}
			
		}){
			@Override
			public void run() {
				for(int i = 0; i < 100; i ++){
					System.out.println("走的是Thread:" + i);
				}
			}
		}.start();
		
	}
}

9.定时器

概述:定时器可以让我们在指定的时间做某件事情,还可以重复做某件事,依赖Timer类和TimeTask这两个类实现

Time:定时

  • public Timer():构造方法
  • public void schedule(TimeTask task, long delay):延迟一段时间后执行task任务,只执行一次
  • public void schedule(TimeTask task, long delay, long period):延迟一段时间后执行task任务,每隔一段时间后再重复执行task任务
  • public void schedule(TimeTask task, Date time):安排在指定的时间执行指定的任务
  • public void schedule(TimeTask task, Date firstTime, long period):安排指定的任务在指定的时间进行,每隔一段时间再重复执行任务
  • public void cancel():取消定时器

代码示例:

import java.util.Timer;
import java.util.TimerTask;

public class TimeDemo {
	public static void main(String[] args) {
		Timer t = new Timer();
		//设置三秒延迟后爆炸,并每隔两秒爆炸一次
		t.schedule(new MyTask(), 3000, 2000);
	}
}
class MyTask extends TimerTask{

	@Override
	public void run() {
		System.out.println("beng,爆炸了");
	}
	
}

10.学完多线程须知

1.同步有几种方式,分别是什么?

答:两种。同步代码块和同步方法

2.启动一个线程是run()还是start()?他们的区别?

答:run()方法封装了被线程执行的代码,直接调用仅仅是普通方法的调用;start()方法启动线程,并由jvm自动调用run()方法

3.sleep()方法和wait()方法的区别

sleep()方法必须指定时间,不释放锁;wait()方法可以不知道时间,也可以指定时间,释放锁

4.为什么wait(),notify(),notifyAll()等方法定义在Object类中

答:因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁,而Object代表任意的对象,所以,定义在这里面