1、线程进程概念
1)进程:
进程是受操作系统管理的基本单元。任何多道程序设计系统中,CPU由一个进程快速切换到另一个进程,严格说,在某一个瞬间CPU(指单核)只能运行一个进程。但在1s内可以切换多个进程运行,这样就产生并行错觉。这种快速切换称为多道程序设计。进程有三种状态:运行、阻塞、就绪。
运行状态:进程占用CPU正常运行;
就绪状态:可以运行,只是因为CPU被其他进程占用,等待CPU空闲;
阻塞状态:除非发生某些外部事件,否则不能运行
2)线程:
一个进程中可以有多个线程,共享进程的内存空间,与进程类似,相当于进程下的子任务。
需要使用线程的原因:
1)进程中有些任务很耗时,就像IO操作,很费时间,操作系统给进程分配着CPU资源,但IO操作费时,使CPU实际上在空闲,并没有充分利用起CPU资源,效率低,这时就可以使用多线程,使用一个线程处理IO操作,使用另外的线程处理其他的工作,是这些活动彼此重叠进行,实现CPU资源的充分利用,提高效率从而加快程序执行速度。
2)由于线程比进程更加轻量级,所以他比进程更加容易创建,也更加容易撤销。
3)许多应用中同时发生着多种活动,这些应用就可以分解为多个可以并行运行的多个程序,程序设计也会简单化。
2、java多线程的实现方式
java实现多线程有两种方式
1)继承Thread类
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
// 启动一个线程
myThread.start();
}
}
// 创建一个线程
class MyThread extends Thread{
@Override
public void run() {
System.out.println("线程业务操作!");
}
}
2)、实现Runnable接口
public class Main {
public static void main(String[] args) {
// 实现接口的启动方式
MyThreadB b = new MyThreadB();
Thread t = new Thread(b);
t.start();
}
}
// 实现接口的多线程类
class MyThreadB implements Runnable{
@Override
public void run() {
System.out.println("线程的业务操作");
}
}
3、多线程的方法以及线程状态变化
1)、currentThread()方法
这是Thread类中一个静态方法
Thread.currentThread(); // 返回当前执行的线程
Thread.currentThread().getName(); // 返回当前执行线程的名字
2)isAlive()
Thread类中的方法,检测线程是否处于活动状态,wait()和sleep()中的线程也是true。
thread.isAlive();
// 在线程子类中调用
this.isAlive();
3)sleep()
Thread类中的静态方法,让当前正在执行的线程休眠,会抛出一个InterruptedException异常。
Thread.sleep(1000);
4)getId()
Thread类的方法,取得线程的唯一标识
5)停止线程
线程停止三种情况:正常退出停止,使用stop方法停止,使用interrupt方法停止。
- stop():Thread的方法,直接暴力停止线程,是一个已经过期的方法,执行后直接释放锁,停止线程,容易使一些数据得不到同步处理出现问题。不建议使用。使用stop方法会抛出一个java.lang.ThreadDeath异常,此异常通常也不需要显式捕获
- interrupt():并不会直接停止线程,是通过更改一个标志位,sleep或wait()中的程序可以被直接中断,抛出InterruptedException,并且清除停止状态标志。
t.interrupt(); // Thread中普通方法,t是一个线程,中断t线程
t.interrupted(); // Thread中静态方法,返回当前线程是否中断,执行后会将状态标志清除为false
t.isInterrupted(); // Thread中普通方法,返回t线程是否中断,不清除标志位
通常建议使用抛出异常方式老实现线程的停止。
6)暂停线程
suspend()方法暂停线程,resume()方法恢复线程,都是Thread类下的方法,属于过期的方法。
过期原因:suspend方法暂停线程时,并不会释放锁,所以对于同步代码中暂停的话,容易造成独占同步资源,而且如果是异步代码的话使用suspend和resume也容易造成线程暂停,使数据不同步。
例如:如果一个线程中有调用其他的同步代码,如println打印函数,当线程暂停时,恰好正在执行这个方法,由于这个方法是同步的,所以无法释放锁,其他线程也无法调用打印方法。
threadA.suspend(); // 暂停线程threadA
threadA.resume(); // 恢复threadA线程
7)yield
yield方法时Thread中的静态方法,使用该方法会使当前线程放弃当前CPU资源,重新竞争CPU资源,有可能刚放弃就又竞争上。
Thread.yield(); // 设置当前线程放弃CPU资源
8)线程的优先级
设置线程优先级使用setPriority(),是Thread类中的方法,线程优先级分为1~10个等级,小于1或大于10会抛出异常。
线程优先级具有继承性,线程A启动线程B,则线程B的优先级与A是一样的。
优先级高的线程先执行的可能性大,但并不是肯定先执行,因为线程优先级具有随机性。
thread.setPriority(5); // 设置线程thread优先级为5
9)守护线程
线程分为两种:用户线程、守护线程
守护线程是一种特殊的线程,当进程中不存在非守护线程了,守护线程就会自动销毁。
典型的守护线程就是垃圾回收线程。
thread.setDaemon(true); // 设置thread线程为守护线程
4、同步
为什么要是用同步:由于多个线程访问同一个共享的变量,会出现处理混乱,有时候一个线程正在修改这个变量值,另一个线程却在读取,这样出现读出的值没有修改,称为脏读,这个问题称为线程安全问题。
同步代码的出现就是为了解决线程安全问题。
1)synchronized同步
synchronized具有互斥性和可见性:
- 被同一把锁锁住的代码同一时间只能有一个线程访问,这就是互斥性;
- 可以保证进入同步方法或者同步代码的每个想成,都可以看到又听一个锁保护之前所有的修改效果,这就是可见性。
synchronized可以修饰方法,那么整个方法的代码就是同步代码,同一时间只有一个线程可以访问整个同步方法,使用的是当前this对象对方法上锁,同一锁锁住的同步代码同一时间只能有一个线程进入执行。如果使用synchronized(){}
同步代码块,则需要在括号中指定锁对象。
synchronized锁重入:当一个线程得到一个对象锁后,再次请求此对象锁时可以再次得到,即就是在一个同步代码中可以调用同一锁的另外的同步代码可以立即获得锁执行。当存在父子类继承关系时,完全可以使用子类的同步方法调用父类的同步方法。
出现异常,锁自动释放
synchronized(this):锁住当前对象
synchronized使用在静态方法上时,是对当前*.java文件对应的Class类进行持锁,与synchronized(class)代码块一样。注意和普通的synchronized修饰的方法区别,synchronized关键字加到非静态方法上是给对象上锁。Class锁会对类的所有对象实例起作用。注意:Class锁和具体类对象锁不一样,所以分别被这两个锁锁住的代码所持的不是同一锁,所以不会同步。
注意String常量锁
使用synchronize(String)
string类型作为锁时,有时传入的string是相等的常量,则常量会造成锁是同一对象,一般情况尽量避免使用字符串作为锁对象
2)、多线程死锁问题
使用两把锁,A线程在Lock1锁的同步代码中调用Lock2锁住的同步代码,然后B线程在Lock2锁住的同步代码中调用Lock1锁住的同步代码。这样就会造成A拿到Lock1锁请求Lock2锁,同时Lock2的锁被B拿着,A无法拿到Lock2锁只有一直拿着Lock1锁等待着B线程执行完毕释放Lock2锁,但B线程请求Lock1的锁,B线程无法拿到Lock1的锁,所以B线程也一直拿着Lock2锁在等待Lock1锁,这样就是死锁。
3)volatile关键字
volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得值。
下面代码就示范了线程启动后isRunning
变量取值就从线程私有数据栈中取得值,而是用setIsRunning(boolean isRunning)
方法修改的是公共堆栈中的isRunning
值,线程值取值就不同步,所以该程序运行起来后就无法停止。
public class Main {
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start();
Thread.sleep(5000);
t.setIsRunning(false);
System.out.println("已经赋值false");
}
}
class MyThread extends Thread{
private boolean isRunning = true; // 注意这里变量
public boolean isRunning() {
return isRunning;
}
public void setIsRunning(boolean isRunning) {
this.isRunning = isRunning;
}
@Override
public void run() {
System.out.println("进入run");
while(isRunning) {
}
System.out.println("线程停止了");
}
}
需要修改private volatile boolean isRunning = true;
是用volatile
关键字强制每次取isRunning
变量值时必须从公共堆栈中取值,这样就不会有线程堆栈值和公共堆栈值不同步问题。
volatile操作不具有原子性,volatile只是强制每次线程取值从公共内存中取值。
注意区分volatile和synchronized的区别:
- 关键字volatile是线程同步的轻量级实现,所以性能比synchronized好,并且volatile只能用于修饰变量;
- 多线程访问volatile不会发生阻塞,而synchronized会发生阻塞;
- volatile只能保证数据的可见性,并不能保证原子性,而synchronized既能保证原则性,又能保证可见性,他会将私有内存和公共内存中的数据做同步;
- volatile主要解决变量在多个线程之间的可见性,而synchronized解决的是多个线程之间访问资源的同步性,所以synchronized功能比volatile强大,但同时效率低于volatile;
4)、原子类
i++操作并不是原子操作,他可以分为以下几步:首先取得i值、计算i+1,赋值i = i+1;所以多线程环境下会出现错误。
除了使用同步synchronized办法,还可以使用AtomicInteger原子类进行操作。注意原子类的每个方法保证原子性,但是各个方法之间没有原子性。
5、线程间通信
1)、使用wait/notify实现线程间通信
wait()方法:是Object类的方法,作用是使当前执行代码进行等待,直到接收到通知或者中断停止。在调用wait前,线程必须获取到该对象的对象级别锁,即只能在同步方法或同步代码块中调用wait方法。在执行wait方法后,当前线程释放锁。在从wait方法返回之前,线程与其他线程重新竞争锁。
notify()方法:是Object类的方法,必须在同步方法或同步代码块中使用,使用前必须获取到该对象级别的锁,只能唤醒一个使用同样锁的等待线程,如果有多个则只会唤醒其中一个;而且在执行完notify()方法后并不会立即释放锁,一直要等到同步代码执行完,当前线程才会释放锁。
注意上边两个方法使用前都必须获取到该对象级别的锁,即调用wait/notify必须使用的是锁对象调用。
如下程序使用的锁对象是线程对象实例ThreadA ta
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadA ta = new ThreadA();
ta.start();
Thread.sleep(2000);
synchronized (ta) {
ta.notify();
}
}
}
class ThreadA extends Thread{
@Override
synchronized public void run() {
try {
System.out.println("进入wait");
this.wait();
System.out.println("退出wait");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
interrupt()对wait影响:线程wait状态时,调用线程对象的interrupt()方法时,会出现InterruptedException异常。
notifyAll():notify方法每次只能随机选择一个线程通知唤醒,notifyAll可以唤醒所有的等待线程。
wait(long):等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。
3)、生产者与消费者
生产者消费者模型代码
/**
* 生产者与消费者
* 1个生产者 --- 1个消费者
* 因为只有1个生产者和一个消费者,所以不存在生产者唤醒生产者,消费者唤醒消费者,出现假死状况,或者越界状况
* @author wsk
*
*/
public class Main {
public static void main(String[] args) {
MyStack myStack = new MyStack();
P p = new P(myStack);
C c = new C(myStack);
p.start();
c.start();
}
}
// 生产者线程
class P extends Thread{
private MyInterface myStack;
public P(MyInterface myStack) {
this.myStack = myStack;
}
@Override
public void run() {
while(true) {
myStack.push();
}
}
}
// 消费者线程
class C extends Thread{
private MyInterface myStack;
public C(MyInterface myStack) {
this.myStack = myStack;
}
@Override
public void run() {
while(true) {
myStack.pop();
}
}
}
interface MyInterface{
public void push();
public void pop();
}
class MyStack implements MyInterface{
private List list = new ArrayList<String>();
// 生产操作
synchronized public void push() {
if(list.size()==1) {
System.out.println(Thread.currentThread().getName()+":生产者WAIT");
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
list.add(Math.random());
System.out.println(Thread.currentThread().getName()+":生产者生产了:"+list.size());
this.notify();
}
// 消费操作
synchronized public void pop() {
if(list.size()==0) {
System.out.println(Thread.currentThread().getName()+":消费者WAIT");
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
list.remove(0);
System.out.println(Thread.currentThread().getName()+":消费者消费了:"+list.size());
this.notify();
}
}
当有多个生产者和消费者时,有可能出现消费者唤醒消费者list.remove(0);
出现越界,或生产者唤醒生产者list中存放多个;
当把生产方法if(list.size()==1)
和消费方法if(list.size()==0)
改为while时,多个生产者和多个消费者会出现生产者叫醒生产者,则会进行一次while判断,然后继续进入等待,会使所有生产者进入等待造成假死状态。
多个生产者对多个消费者正确代码应该把叫醒操作改为notifyAll(),一次叫醒所有,并且使用while判断。
/**
* 多个生产者和消费者
* 使用1对1 的代码的话就会产生假死或者越界错误:原因就是生产者一直唤醒生产者,或者消费者一直唤醒消费者
* 改进:使用notifyAll进行唤醒,使用while()替换if()判断
* @author wsk
*
*/
public class Main2 {
public static void main(String[] args) {
MyStack2 myStack = new MyStack2();
P p0 = new P(myStack);
P p1 = new P(myStack);
P p2 = new P(myStack);
C c0 = new C(myStack);
C c1 = new C(myStack);
p0.start();
p1.start();
p2.start();
c0.start();
c1.start();
}
}
class MyStack2 implements MyInterface{
private List list = new ArrayList<String>();
synchronized public void push() {
while(list.size()==1) { // 修改为while
System.out.println(Thread.currentThread().getName()+":生产者WAIT");
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
list.add(Math.random());
System.out.println(Thread.currentThread().getName()+":生产者生产了:"+list.size());
this.notifyAll(); // 修改这里改为notifyAll
}
synchronized public void pop() {
while(list.size()==0) {
System.out.println(Thread.currentThread().getName()+":消费者WAIT");
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
list.remove(0);
System.out.println(Thread.currentThread().getName()+":消费者消费了:"+list.size());
this.notifyAll();
}
}
4)、通过管道进行线程间通信
pipeStream管道流,用于在不同线程之间传送数据,一个线程发送数据到管道,另一个线程从管道中获取数据,实现不同线程之间的通信。
JDK提供了4个类来使线程间可以通信
- PipedInputStream和PipedOutputStream 字节流
- PipedReader和PipedWriter 字符流
字节流
/**
* 线程通信:字节流
* @author wsk
*
*/
public class Main2 {
public static void main(String[] args) throws Exception {
WriteDate write = new WriteDate();
ReadData read = new ReadData();
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
in.connect(out);// 让两个管道产生通信连接
// 写数据线程
Thread tWrite = new Thread(new Runnable() {
@Override
public void run() {
write.writeMethod(out);
}
});
// 读数据线程
Thread tRead = new Thread(new Runnable() {
@Override
public void run() {
read.ReadMethod(in);
}
});
tWrite.start();
Thread.sleep(2000);
tRead.start();
}
}
// 写数据
class WriteDate{
public void writeMethod(PipedOutputStream out) {
try {
System.out.println("write:");
for(int i=0;i<100;i++) {
String str = ""+i;
out.write(str.getBytes());
System.out.print(str);
}
System.out.println();
out.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
// 读数据
class ReadData{
public void ReadMethod(PipedInputStream in) {
try {
System.out.println("Read:");
byte[] b = new byte[1024];
int length=0;
while((length = in.read(b))!=-1) {
String str = new String(b, 0, length);
System.out.print(str);
}
in.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
字符流与字节流类似
5)join方法
join方法是Thread类中方法,作用是使所属的线程x正常运行run()中任务,而使当前线程无限期阻塞,直到线程x销毁后再继续执行。使线程具有排队运行的效果,内部是调用wait方法实现。所以当join方法遇到interrupt方法时,会抛出InterruptedException,注意是等待线程调用interrupt方法时等待线程会抛出异常,调用join的线程正常执行。
join(long):等待指定时间,内部调用wait(long).
public class Main3 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" start:");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end:");
}
});
t.start();
t.join();
System.out.println("Main");
}
}
输出结果:
Thread-0 start:
Thread-0 end:
Main
主线程Main停下来等待线程t执行完毕,然后才执行
6)ThreadLocal的使用
变量值共享使用public static形式变量,但这个变量是所有线程都共享的,如果要实现单个线程共享的变量JDK提供ThreadLocal类实现。
ThreadLocal主要解决的是每个线程绑定自己的值,不同线程的值可以放入ThreadLocal中保存,并且只有自己取出来,实现了变量在不同线程之间的隔离性。
ThreadLocal提供get()和set()方法来存取数据,当没有存放数据时,调用get返回null,如果想要修改初始化get返回值时,需要覆盖ThradLocal方法的initialValue方法,return需要返回的值。
InheritableThreadLocal类:可以在子线程获取父线程的值
/**
* InheritableThreadLocal类
* 可以在子线程获取父线程的值
* @author wsk
*
*/
public class Main3 {
public static InheritableThreadLocal<String> t = new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
t.set("Main");
ThreadSubA sub = new ThreadSubA();
sub.start();
Thread.sleep(2000);
System.out.println(t.get());
}
}
class ThreadSubA extends Thread{
@Override
public void run() {
System.out.println(Main3.t.get());
Main3.t.set("sub");
}
}
6、Lock对象实现synchronized的功能
ReentrantLock类可以实现与synchronized关键字一样的功能,而且扩展功能更加强大。
1)、简单的lock对象锁
/**
* Lock对象产生死锁
* 同一个Lock对象持有同一把锁
* @author wsk
*
*/
public class Main {
public static void main(String[] args) {
MyServer server = new MyServer();
ThreadA ta1 = new ThreadA(server);
ThreadA ta2 = new ThreadA(server);
ta1.start();
ta2.start();
}
}
class ThreadA extends Thread{
MyServer server;
public ThreadA(MyServer server) {
this.server = server;
}
@Override
public void run() {
server.method();
}
}
class MyServer{
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
public void method() {
try {
lock1.lock();
System.out.println(Thread.currentThread().getName()+"第一次进入lock1");
Thread.sleep(1000);
lock2.lock();
System.out.println(Thread.currentThread().getName()+"第一次进入lock2");
Thread.sleep(1000);
lock1.unlock();
System.out.println(Thread.currentThread().getName()+"第一次退出lock1");
Thread.sleep(1000);
lock1.lock();
System.out.println(Thread.currentThread().getName()+"第二次进入lock1");
Thread.sleep(1000);
lock2.unlock();
System.out.println(Thread.currentThread().getName()+"第一次退出lock2");
Thread.sleep(1000);
lock1.unlock();
System.out.println(Thread.currentThread().getName()+"第二次退出lock1");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
2)、Condition实现等待/通知
在使用wait/notify方法进行通知时,被通知线程是由JVM随机选择,但使用ReentrantLock结合Condition类是可以实现选择性通知,单独唤醒指定的await需要使用多个condition。
/**
* condition等待通知类
* 注意调用condition.await();前必须调用lock.lock();
* 同样对于condition.signal();也是一样
* 对于通知指定的等待线程,需要使用多个condition
* @author wsk
*
*/
public class Main {
public static void main(String[] args) throws InterruptedException {
MyServer server = new MyServer();
ThreadA ta = new ThreadA(server);
ThreadB tb = new ThreadB(server);
ta.start();
tb.start();
Thread.sleep(2000);
server.signalA();
}
}
class ThreadA extends Thread{
MyServer server;
public ThreadA(MyServer server) {
this.server = server;
}
@Override
public void run() {
server.waitA();
}
}
class ThreadB extends Thread{
MyServer server;
public ThreadB(MyServer server) {
this.server = server;
}
@Override
public void run() {
server.waitB();
}
}
class MyServer{
Lock lock = new ReentrantLock();
Condition conditionA = lock.newCondition();// 多个condition
Condition conditionB = lock.newCondition();
public void waitA() {
lock.lock();
try {
System.out.println("等待");
conditionA.await(); // 等待
System.out.println("唤醒");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void signalA() {
lock.lock();
conditionA.signal();// 唤醒A
System.out.println("signal");
lock.unlock();
}
public void waitB() {
lock.lock();
try {
System.out.println("等待");
conditionB.await(); // 等待
System.out.println("唤醒");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void signalB() {
lock.lock();
conditionB.signal();// 唤醒B
System.out.println("signal");
lock.unlock();
}
}
wait()方法相当于condition的await()
wait(long timeout)相当于await(long time,TimeUntil until)
notify()相当于signal()
notifyAll相当于signalAll
3)、公平锁与非公平锁
锁Lock分为“公平锁”和“非公平锁”,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先到FIFO先进先出顺序。而非公平锁就是一种锁的抢占机制,即随机获取锁,先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁,结果不公平。
公平锁:基本上先start()启动的线程就会先获得锁,但不是绝对的
非公平锁:结果基本乱序,先start的线程不一定先获得锁。默认情况就为非公平锁
lock = new ReentrantLock(isFair);// isFair为true则表示创建公平锁,false为非公平锁
4)、ReentrantLock对象的相关方法
int getHoldCount()查询当前线程保持锁的个数,即调用多少次lock()方法
int getQueueLength()返回正等待获取此锁定的线程估计数
int getWaitQueueLength(Condition condition)返回等待与此锁定相关的给定条件Condition的线程估计数
boolean hasQueuedThread(Thread thread)查询指定的线程是否正在等待获取此锁定
boolean hasQueuedThreads()查询是否有线程正在等待获取此锁定
boolean hasWaiters(Condition condition)查询是否有线程正在等待与此锁定有关的condition条件
boolean isFair()判断是不是公平锁
boolean isHeldByCurrentThread()判断当前线程是否保持此锁定
boolean isLocked()查询此锁定是否由任意线程保持
5)、ReentrantReadWriteLock类
为了提升ReentrantLock的效率而出现的类,有两个锁,一个读相关的锁,称为共享锁;另一个写相关的锁,称为排它锁。
可以同时又多个线程进入读锁定的代码,但是同一时间只有一个线程可以进入写锁的代码,即:读读可以同步,读写,写读,写写都是互斥的。
ReentrantReadWriteLock lock0 = new ReentrantReadWriteLock();
lock0.readLock().lock();// 读锁
lock0.writeLock().lock();//写锁
7、定时器Timer
JDK类库中,Timer类主要负责计划任务的功能,也就是在指定时间开始执行一个任务。
方法schedule(TimerTask task,Date time)
在指定的时间time开始任务task,任务task一般要继承TimerTask 抽象类,重写run方法,执行的业务代码需要放入run方法中。
/**
* Timer类计划任务
* @author wsk
*
*/
public class Main {
public static void main(String[] args) {
System.out.println("当前时间:"+System.currentTimeMillis());
Calendar cal = Calendar.getInstance();
cal.add(Calendar.SECOND, 10);// 当前时间十秒后
Date runData = cal.getTime();
MyTask task = new MyTask();
Timer timer = new Timer();
timer.schedule(task, runData);
}
}
class MyTask extends TimerTask{
@Override
public void run() {
System.out.println("计划任务执行了:"+System.currentTimeMillis());
}
}
执行任务代码后,程序并没有停止
原因:创建一个Timer类就启动一个TimerThread 新线程,这个线程并不是守护线程,会一直运行。
//JDK源码Timer
public Timer() {
this("Timer-" + serialNumber());
}
public Timer(String name) {
thread.setName(name);
thread.start();
}
/**
* The timer thread.
*/
private final TimerThread thread = new TimerThread(queue);
需要将此线程改为守护线程,则运行完任务代码就会停止。
Timer timer = new Timer(true);// 守护线程
对于计划时间有几种情况
- 当计划时间早于当前时间,立即执行任务代码
- TimerTask是以队列的方式一个一个的顺序执行,所以执行时间可能和预期时间不一样。比如
timer.schedule(task1,runDate1);
和timer.schedule(task2,runDate2);
当task1是一个比较耗时的操作,执行完后时间超过runDate2的时间,则task1执行完立即执行task2.
2)、schedule(TimerTask task,Date firstTime,long period)
该方法的通是在指定的日期之后按指定的时间周期,无限循环的执行某一任务。
3)、cancel()方法
TimerTask类的cancel()方法:将自身从任务队列中清除
Timer类的cancel()方法:将任务队列中全部任务清空。有时并不能清空,因为cancel没有抢到队列Queue锁。
8、单例模式与多线程
单例模式即就是对象只能存在一个实例,不能随意穿件对象实例,也就是需要将对象的构造器设置成private,不允许外部调用。
单例模式有两种实现:立即加载/饿汉模式、延迟加载/懒汉模式。
1)立即加载/饿汉模式
指使用类的时候已经将对象创建好了,如下
class MyObject{
private MyObject() {}
private static MyObject myObject = new MyObject();
public static MyObject getInstance() {
// 此版本代码的缺点是不能有其他的实例变量
// 而且getInstance方法没有同步,会有线程安全问题
return myObject;
}
}
可以实现多线程的单例模式
2)延迟加载/懒汉模式
class MyObject2{
private static MyObject2 m2;
private MyObject2() {}
public static MyObject2 getInstance() {
try {
if(m2!=null) {
}else {
// 模拟创建对象前进行的准备工作
Thread.sleep(2000);
m2 = new MyObject2();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return m2;
}
}
这个延迟加载的单例模式在多线程环境下会造成错误,因为线程安全不是单例。
解决办法:对getInstance方法使用同步,缺点执行效率太低。
最佳办法:DCL双检查锁机制
// 使用DCL双重检测
class MyObject3{
private volatile static MyObject3 m3;
private MyObject3() {}
public static MyObject3 getInstance() {
try {
if(m3!=null) {
}else {
// 模拟创建对象前进行的准备工作
Thread.sleep(2000);
synchronized (MyObject3.class) {
if(m3==null) // 双重检测
m3 = new MyObject3();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return m3;
}
}
3)static代码块实现单例模式
4)静态内部类实现单例模式
9、总结多线程状态
线程可以调用getState()
返回线程的状态
线程状态分为:
- NEW:是指线程实例化后还未执行start()状态
- RUNBABLE:线程进入运行状态
- TERMINATED:线程被销毁时状态
- TIMED_WAITING:代表线程执行了Thread.sleep()方法,等待时间到达继续向下运行(wait(timeout)/join(timeout)都处于这个状态)
- WAITING:执行wait()/join()方法后线程所处的状态(join方法内部调用的就是wait方法)
- BLOCKED:线程等待锁的状态