Java基础总结篇---多线程实现
这篇文章只是对java多线程的应用级泛泛而谈,其实还是挺难的,就那几个状态转换就可以把你安排上了。实则要研究还是有挺多学问的我有一本《Java并发编程实战》至今还没有打开第一章。
在说多线程直接首先需要明确一点:同一时间,CPU只能处理1条线程,只有1条线程在工作(执行),多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。
多线程
假如一个程序有多条执行流程,那么,该程序就是多线程程序。
多线程有什么意义呢?
- 多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率。
- 程序的执行其实都是在抢CPU的资源,CPU的执行权。
- 多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权。
- 我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性。
但是:如果线程过多
CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源
每条线程被调度执行的频次会降低(线程的执行效率降低)
由此引出多线程的缺点:开启线程需要占用一定的内存空间,如果开启大量的线程,会占用大量的内存空间,降低程序的性能。线程越多,CPU在调度线程上的开销就越大。程序设计更加复杂:比如线程之间的通信、多线程的数据共享。
线程和进程有什么区别?
线程是依赖于进程存在的
-
进程:
- 正在运行的程序,是系统进行资源分配和调用的独立单位。
- 每一个进程都有它自己的内存空间和系统资源。
-
线程:
- 是进程中的单个顺序控制流,是一条执行路径
- 一个进程如果只有一条执行路径,则称为单线程程序。
- 一个进程如果有多条执行路径,则称为多线程程序。
并行和并发
- 前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。简单来说就是多个线程在某一时间段内同时运行。
- 后者是物理上同时发生,指在某一个时间点同时运行多个程序。简单来说就是同一时间访问同一服务器。在大数据应用里面高并发是常常需要考虑的一个问题。
其次,你需要明确1个线程中任务的执行是串行的。如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务。
Java程序运行原理
Java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
Jvm虚拟机的启动是单线程的还是多线程?
- Jvm虚拟机的启动是多线程的。
- 原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出。
- 现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。
进入正文----
如何实现多线程
public class Thread extends Object implements Runnable 线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。常用 实现Runnable接口方法实现多线程。
方式一:继承Thread类
自定义类继承Thread类
在自定义类里面重写run方法
创建对象
启动线程
代码例子:
public class MyThread extends Thread {
@Override
public void run() {
for (int x = 0; x < 200; x++) {
System.out.println(x);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
// MyThread my = new MyThread();
// my.start();
// IllegalThreadStateException:非法的线程状态异常
// 为什么呢?因为这个相当于是my线程被调用了两次。而不是两个线程启动。我都启动一次了你还启动的话就报错。
// my.start();
// 创建两个线程对象
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.start();
my2.start();
}
}
方式二:实现Runnable接口
自定义类MyRunnable实现Runnable接口
重写run()方法
创建MyRunnable类的对象
创建Thread类的对象,并把C步骤的对象作为构造参数传递
代码例子:
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
// 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
public class MyRunnableDemo {
public static void main(String[] args) {
// 创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
// 创建Thread类的对象,并把C步骤的对象作为构造参数传递,参数要的是接口实质上要的是实现类对象
// Thread(Runnable target)
// Thread t1 = new Thread(my);
// Thread t2 = new Thread(my);
// t1.setName("林青霞");
// t2.setName("刘意");
// Thread(Runnable target, String name)
Thread t1 = new Thread(my, "zjl");
Thread t2 = new Thread(my, "zyf");
t1.start();
t2.start();
}
}
实现接口方式的好处
- A:可以避免由于Java单继承带来的局限性。
- B:适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想,说白了你只创建了一个实现类对象,可以让多个线程去共享一个资源。
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
//多个窗口售卖,实现一个门票资源共享
@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "张票");
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
Thread类的基本获取和设置方法
- public final String getName()
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
- public final void setName(String name)
其实通过构造方法也可以给线程起名字,当然我觉得最方便的还是用set方法设置名字。
public class MyThreadDemo {
public static void main(String[] args) {
// 创建线程对象
//无参构造+setXxx()
// MyThread my1 = new MyThread();
// MyThread my2 = new MyThread();
// //调用方法设置名称
// my1.setName("林青霞");
// my2.setName("刘意");
// my1.start();
// my2.start();
//带参构造方法给线程起名字
// MyThread my1 = new MyThread("zjl");
// MyThread my2 = new MyThread("zrf");
// my1.start();
// my2.start();
}
}
如何获取main方法所在的线程名称呢?
- public static Thread currentThread()
//我要获取main方法所在的线程对象的名称,该怎么办呢?
System.out.println(Thread.currentThread().getName());
这样就可以获取任意方法所在的线程名称。
问题一:为什么需要重写run方法?
不是类中的所有代码都需要被线程执行的。而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。
问题二:调用run()方法为什么是单线程的呢?
因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果。
如何实现线程调度?
假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?
Java使用的是抢占式调度模型。 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
如何设置和获取线程优先级
-
public final int getPriority()
默认优先级的值是5
-
public final void setPriority(int newPriority)
参数范围 1-10
如何实现线程控制?
-
线程休眠
public static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
public class ThreadSleep extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x + ",日期:" + new Date());
// 睡眠
// 困了,我稍微休息1秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
-
线程加入
public final void join()
等待该线程终止。
public class ThreadJoin extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
public class ThreadJoinDemo {
public static void main(String[] args) {
ThreadJoin tj1 = new ThreadJoin();
ThreadJoin tj2 = new ThreadJoin();
ThreadJoin tj3 = new ThreadJoin();
tj1.setName("李渊");
tj2.setName("李世民");
tj3.setName("李元霸");
tj1.start();
try {
tj1.join(); // 等待该线程终止后其他线程开始抢占才能执行,保证线程一可以最先执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
tj2.start();
tj3.start();
}
}
- 线程礼让
public static void yield()
暂停当前正在执行的线程对象,并执行其他线程。让多个线程的执行更和谐,近似于一人执行一次,但是不能靠它保证一人一次。
public class ThreadYield extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
Thread.yield();
}
}
}
public class ThreadYieldDemo {
public static void main(String[] args) {
ThreadYield ty1 = new ThreadYield();
ThreadYield ty2 = new ThreadYield();
ty1.setName("zjl");
ty2.setName("zyf");
ty1.start();
ty2.start();
}
}
- 后台线程
public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。
守护线程图解:
代码例子:
public class ThreadDaemon extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
//这程序里面,刘备作为主线程,当刘备死了以后,此时正在运行的线程中只有他的两个守护线程张飞关羽,所以Java 虚拟机退出,这两个线程挂掉。
public class ThreadDaemonDemo {
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();
td1.setName("关羽");
td2.setName("张飞");
// 设置守护线程
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
Thread.currentThread().setName("刘备"); //设置主线程名字为刘备
for (int x = 0; x < 5; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
- 中断线程
public final void stop()
让线程停止,过时了,但是还可以使用。只是不太安全,太暴力了。
public void interrupt():InterruptedException
中断线程。 把线程的状态终止,并抛出一个异常,一定会走catch。
public class ThreadStop extends Thread {
@Override
public void run() {
System.out.println("开始执行:" + new Date());
// 我要休息10秒钟,亲,不要打扰我哦
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("线程被终止了");
}
System.out.println("结束执行:" + new Date());
}
}
public class ThreadStopDemo {
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();
// 你超过三秒不醒过来,我就干死你
try {
Thread.sleep(3000);
// ts.stop();
ts.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程的生命周期
线程的生命周期和进程的生命周期高度一致。由此也可以引出进程的生命周期。
注意:运行的时候可能回到就绪状态,因为被别的线程抢到执行权,其次运行的线程被阻塞后不能直接回到运行状态,他回到的是就绪状态,此时再和其他线程进行一个CPU执行权的抢夺。带run方法执行结束后进入死亡状态,等待GC回收。
线程安全问题
前面买票那个例子就存在典型的线程安全问题,卖同一张票,卖负票。
首先想为什么出现安全问题?
- 是否是多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据
在所如何解决多线程安全问题呢?
基本思想:让程序没有安全问题的环境。
那么怎么实现呢?
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可—> synchronized
同步代码块格式
同步可以解决安全问题的根本原因就在那个锁对象上。该对象如同锁的功能。当线程开始执行同步代码块前,必须先获得对同步代码块的锁定。并且任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。
synchronized(对象)
{
需要同步的代码;
}
//常见的三种写法,实质上同步代码块才是核心
public class Demo {
//就是同步代码块,同步锁对象是类的class对象(Demo类里的静态方法锁对象就是Demo.class)
public static synchronized void fun1(){
}
//就是同步代码块,同步锁对象是this
public synchronized void fun2(){
}
public static void main(String args[]) throws Exception{
//就是锁住这个对象,表示这个对象正在为我服务,其他人不能用.通常推荐使用可能被并发访问的共享资源充当同步监视器。
synchronized(xxx) {
}
}
}
三种同步类型
代码例子:
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
//创建锁对象,得是同一个锁对象,这个对象我们可以认为是任意一个对象,同步方法锁对象是 this
private Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票");
}
}
}
}
}
// 同步方法
private static synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
}
同步的前提
- 程序中需要多个线程
- 多个线程使用的是同一个锁对象
同步的好处
- 同步的出现解决了多线程的安全问题。
同步的弊端
- 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
是否线程安全
前面的文章介绍过有些类是线程不安全的,那么我们如何判断呢?简单明了看源码,看有没有synchronized关键字…
public class ThreadDemo {
public static void main(String[] args) {
// 线程安全的类
StringBuffer sb = new StringBuffer();
Vector<String> v = new Vector<String>();
Hashtable<String, String> h = new Hashtable<String, String>();
// Vector是线程安全的时候才去考虑使用的,但是我还说过即使要安全,我也不用你
// 那么到底用谁呢?
// public static <T> List<T> synchronizedList(List<T> list)
List<String> list1 = new ArrayList<String>();// 线程不安全
List<String> list2 = Collections.synchronizedList(new ArrayList<String>()); // 线程安全
}
}
JDK5中Lock锁的使用
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。
public interface Lock ,Lock是一个接口 Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
public class SellTicket implements Runnable {
// 定义票
private int tickets = 100;
// 定义锁对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
// 加锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票");
}
} finally {
// 释放锁
lock.unlock();
}
}
}
}
简单了解一下就行了,毕竟你看到的线程安全里面的各种类的源码使用的仍然是synchronized关键字.
死锁问题
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
死锁代码例子,其中创建了A,B两把锁:
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");
}
}
}
}
}
线程间通信
不同种类的线程操作同一资源,由此引出一个线程间通信问题。
代码例子:
public class Student {
String name;
int age;
}
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if (x % 2 == 0) {
s.name = "zjl";//刚走到这里,就被别人抢到了执行权
s.age = 27;
} else {
s.name = "zyf"; //刚走到这里,就被别人抢到了执行权
s.age = 30;
}
x++;
}
}
}
}
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
System.out.println(s.name + "---" + s.age);
}
}
}
}
public class StudentDemo {
public static void main(String[] args) {
//创建资源
Student s = new Student();
//设置和获取的类
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//线程类
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//启动线程
t1.start();
t2.start();
}
}
等待唤醒机制
wait():等待
notify():唤醒单个线程
notifyAll():唤醒所有线程
代码例子:
/ * 等待唤醒:
*/
public class StudentDemo {
public static void main(String[] args) {
//创建资源,统一学生资源
Student s = new Student();
//设置和获取的类
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//创建两类线程,一个获取一个设置
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//启动线程
t1.start();
t2.start();
}
}
public class Student {
String name;
int age;
boolean flag; // 默认情况是没有数据,如果是true,说明有数据
}
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if(!s.flag){
try {
s.wait(); //t2就等待了。立即释放锁。将来醒过来的时候,是从这里醒过来的时候
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "---" + s.age);
//修改标记
s.flag = false;
//唤醒线程
s.notify(); //唤醒t1
}
}
}
}
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
//判断有没有
if(s.flag){
try {
s.wait(); //t1等着,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (x % 2 == 0) {
s.name = "zjl";
s.age = 27;
} else {
s.name = "zyf";
s.age = 30;
}
x++; //x=1
//修改标记
s.flag = true;
//唤醒线程
s.notify(); //唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。
}
//t1有,或者t2有
}
}
}
线程状态转换图
线程组
Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
- 默认情况下,所有的线程都属于主线程组。
public final ThreadGroup getThreadGroup()
- 我们也可以给线程设置分组
Thread(ThreadGroup group, Runnable target, String name)
代码例子:
public class ThreadGroupDemo {
public static void main(String[] args) {
// method1();
// 我们如何修改线程所在的组呢?
// 创建一个线程组
// 创建其他线程的时候,把其他线程的组指定为我们自己新建线程组
method2();
// t1.start();
// t2.start();
}
private static void method2() {
// ThreadGroup(String name)
ThreadGroup tg = new ThreadGroup("这是一个新的组");
MyRunnable my = new MyRunnable();
// Thread(ThreadGroup group, Runnable target, String name)
Thread t1 = new Thread(tg, my, "zjl");
Thread t2 = new Thread(tg, my, "zyf");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
//通过组名称设置后台线程,表示该组的线程都是后台线程
tg.setDaemon(true);
}
private static void method1() {
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my, "zjl");
Thread t2 = new Thread(my, "zyf");
// 我不知道他们属于那个线程组,我想知道,怎么办
// 线程类里面的方法:public final ThreadGroup getThreadGroup()
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
// 线程组里面的方法:public final String getName()
String name1 = tg1.getName();
String name2 = tg2.getName();
System.out.println(name1);
System.out.println(name2);
// 通过结果我们知道了:线程默认情况下属于main线程组
// 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组
System.out.println(Thread.currentThread().getThreadGroup().getName());
}
}
线程池
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
- 线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
- 在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
public static ExecutorService newCachedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
代码例子:
public class ExecutorsDemo {
public static void main(String[] args) {
// 创建一个线程池对象,控制要创建几个线程对象。
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//可以手动结束线程池
pool.shutdown();
}
}
匿名内部类实现多线程
- new Thread(){代码…}.start();
- new Thread(new Runnable(){代码…}).start();
/*
* 匿名内部类的格式:
* new 类名或者接口名() {
* 重写方法;
* };
* 本质:是该类或者接口的子类对象。
*/
public class ThreadDemo {
public static void main(String[] args) {
// 继承Thread类来实现多线程
new Thread() {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":"
+ x);
}
}
}.start();
// 实现Runnable接口来实现多线程
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":"
+ x);
}
}
}) {
}.start();
// 子类对象一个run Runnable一个对象 **实质上走的是Runnable的run,可以这样写但是没什么鸟用。
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println("hello" + ":" + x);
}
}
}) {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println("world" + ":" + x);
}
}
}.start();
}
}
定时器
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能。开发中常用Quartz,它是一个完全由java编写的开源调度框架。
-
Timer:一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。
- public Timer()
创建一个新计时器。
- public void schedule(TimerTask task, long delay)
安排在指定延迟后执行指定的任务。
-
public void schedule(TimerTask task,long delay,long period)
安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。
-
TimerTask:(抽象类)由 Timer 安排为一次执行或重复执行的任务。
- public abstract void run()
此计时器任务要执行的操作。
-
public boolean cancel()
取消此计时器任务。
代码例子:
public class TimerDemo {
public static void main(String[] args) {
// 创建定时器对象
Timer t = new Timer();
// 3秒后执行爆炸任务
// t.schedule(new MyTask(), 3000);
//结束任务
t.schedule(new MyTask(t), 3000);
}
}
// 做一个任务
class MyTask extends TimerTask {
private Timer t;
public MyTask(){}
public MyTask(Timer t){
this.t = t;
}
@Override
public void run() {
System.out.println("beng,爆炸了");
t.cancel();
}
}
总结
-
多线程有几种实现方案,分别是哪几种?
三种,常用的是两种,一种是继承Thread类一种是实现Runnable接口。
-
同步有几种方式,分别是什么?
两种:同步代码块,同步方法。
-
启动一个线程是run()还是start()?它们的区别?
启动线程是用start()
run():封装了被线程执行的代码,直接调用仅仅是普通方法的调用
start():启动线程,并由JVM自动调用run()方法
-
sleep()和wait()方法的区别
sleep():必须指时间;不释放锁,即使有人想进来也不行。
wait():可以不指定时间,也可以指定时间;释放锁,运行别的线程执行。
-
为什么wait(),notify(),notifyAll()等方法都定义在Object类中
因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。
而Object代码任意的对象,所以,定义在这里面。 -
线程的生命周期图