目录
单例设计模式
单例设计模式:
保证类在内存中只有一个对象。
如何保证类在内存中只有一个对象呢?
(1)控制类的创建,不让其他类来创建本类的对象。private
(2)在本类中定义一个本类的对象。Singleton s;
(3)提供公共的访问方式。 public static Singleton getInstance(){return s}
单例写法两种:
第一种格式 饿汉式 推荐开发用这种方式
演示:
/* * 饿汉式 更好,在开发的时候用 * 上来就想吃(上来就想new 对象) */ class Singleton{ //1.私有构造方法 ,其他类不能访问该构造方法了,即不能创建此类对象 private Singleton() {} //2.创建本类对象 private static Singleton s = new Singleton(); //s是成员变量,必须用对象去调用他,但现在没有对象,我们就只能用类名.调用 所以加上静态 //3.对外提供公共的访问方法 public static Singleton getInstance() { //getInstance 获取实例(对象) return s; } }
第二种格式 懒汉式 面试写这种方式
演示:
/* * 懒汉式 * 先声明引用,然后再做一个判断,啥时候需要再创建对象 * 为什么不好: * 如果是多线程访问,第一条线程进行判断s == null,条件满足进入if内部,刚准备执行内部代码块。 * 这时不小心线程的执行权被别的线程抢去了,结果线程一在s = new Singleton();语句前等待 ,这时线程二进入if内部(线程二 s == null) * 线程一突然抢回执行权,线程二在s = new Singleton();语句前等待,线程一创建一个对象,return s给返回了。线程二接着也抢到执行权,创建一个对象。 * 这样,一共创建两次对象,就不是单例设计模式了。因此,懒汉式在多线程访问的时候,他会有安全隐患————他有可能会创建多个对象出来。 * 所以,懒汉式只在面试的时候用。 面试时,他让你写一个单例的延迟加载模式,其实问的就是懒汉式。 */ class Singleton{ //1.私有构造方法 ,其他类不能访问该构造方法了,即不能创建此类对象 private Singleton() {} //2.声明一个引用 private static Singleton s ; //3.对外提供公共的访问方法 public static Singleton getInstance() { if(s == null) { //s == null 没有创建对象 s = new Singleton(); } return s; } }
第三种格式 没名字
演示:
class Singleton{ //1.私有构造方法 ,其他类不能访问该构造方法了,即不能创建此类对象 private Singleton() {} //2.声明一个引用 public static final Singleton s = new Singleton(); //加final,s不可改变 }
饿汉式和懒汉式的区别
1.饿汉式是空间换时间 上来就去创建一个对象,把空间给浪费了。但是节省了时间,因为你想要,我只接就返回给你。现在时间更重要
懒汉式是时间换空间 上来做个声明,不去创建,什么时候用什么时候给你创建。调用方法的时候做判断s == null,判断就浪费了我们的时间,每一次都要做判断,每一次都浪费时间,比较亏
2.在多线程访问时,饿汉式不会创建多个对象,而懒汉式有可能会创建多个对象。
Runtime类(懒汉式单例设计模式的应用场景)
Runtime类是一个单例类,概述:
public class Runtime extends Object 每个Java应用程序都有一个Runtime类实例,使应用程序能够与其运行的环境相连接。可以通过getRuntime方法获取当前运行时。
应用程序不能创建自己的Runtime类实例。
api中没有构造方法(一个类里没有构造方法,说明构造方法被私有了),但并不是所有方法都为静态。
Runtime类的成员方法:
public static Runtime getRuntime() 返回与当前Java应用程序相关的运行时对象。(说明这个类其实是私有构造。在本类创建对象,对外提供这么一个公共的访问方式。)
源代码:
private static Runtime currentRuntime = new Runtime(); //私有了一个本类对象 自己创建了一个本类对象 currentRuntime当前的一个运行时对象 public static Runtime getRuntime() { return currentRuntime; //返回这个currentRuntime } private Runtime() {} //私有构造方法
private Runtime() {} //私有构造方法
public Process exec(String command) 在单独的进程中执行指定的字符串命令。
演示:
public class Demo2_Runtime {
public static void main(String[] args) throws IOException {
Runtime r = Runtime.getRuntime(); //获取运行时对象
// r.exec("shutdown -s -t 300"); //设置系统在5分钟后关机 第一次把系统给修改了
r.exec("shutdown -a"); //取消注销计划(不会关机了) 第二次修改你修改后的结果,我们操作的是同一个对象 这就需要用到单例设计模式
}
}
Timer类
Timer类:计时器,概述:
public class Timer extends Object 一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。
与每个Timer对象相对应的是单个后台线程,用于顺序地执行所有计时器任务。计时器任务应该迅速完成。如果完成某个计时器任务的时间太长,那么它会“独占“计时器的任务执行线程。因此,这就可能延迟后续任务的执行,而这些任务就可能”堆在一起”,并且在上述不友好的任务最终完成时才能够被快速连续地执行。
对Timer对象最后的引用完成后,并且所有未处理的任务都已执行完成后,计时器的任务执行线程会正常终止(并且成为垃圾回收的对象)。但是这可能要很长时间后才发生。默认情况下,任务执行线程并不作为守护线程来运行,所以他能够阻止应用程序终止。如果调用者想要快速终止计时器的任务执行线程,那么调用者应该调用计时器的cancel方法。
如果意外终止了计时器的任务执行线程,例如调用了它的stop方法。那么所有以后对该计时器安排的尝试都将导致IllegalStateException,就好像调用了计时器的cancel方法一样。
此类是线程安全的:多个线程可以共享单个Timer对象而无需进行外部同步。
此类不提供实时保证:它使用Object.wait(long)方法来安排任务。
实现注意事项:此类可扩展到大量同时安排的任务(存在数千个都没有问题)。在内部,它使用二进制堆来表示其任务队列,所以安排任务的开销是O(log n),其中n是同时安排的任务数。
实现注意事项:所有构造方法都启动计时器线程。
Timer类的成员方法:
public void schedule(TimerTask task, Date time) 安排在指定的时间执行指定的任务。
public void schedule(TimerTask task, Date firstTime, long period) 安排指定的任务在指定的时间开始进行重复的固定延迟执行。以近似固定的时间间隔(由指定的周期分隔)进行后续执行。
在固定延迟执行中,根据前一次执行的实际执行时间来安排每次执行。如果由于任何原因(如垃圾回收或其他后台活动)而延迟了某次执行,则后续执行也将被延迟。在长期运行中,执行的频率一般要稍慢于指定周期的倒数(假定 Object.wait(long) 所依靠的系统时钟是准确的)。
固定延迟执行适用于那些需要“平稳”运行的重复执行活动。换句话说,它适用于在短期运行中保持频率准确要比在长期运行中更为重要的活动。这包括大多数动画任务,如以固定时间间隔闪烁的光标。这还包括为响应人类活动所执行的固定活动,如在按住键时自动重复输入字符。
TimerTask类的概述:
public abstract class TimerTask extends Object implements Runnable 由Timer安排为一次执行或重复执行的任务。
TimerTask类的成员方法:
public abstract void run() 此计时器任务要执行的操作。父类实现了Runnable接口,所以run()来自于Runnable接口。该方法抽象,所以任务是交给子类去完成的。
Date类(util包下的)的成员方法:
public Date(int year, int month, int date, int hrs, int min, int sec) 已过时。 从 JDK 1.1 开始,由 Calendar.set(year + 1900, month, date, hrs, min, sec) 或 GregorianCalendar(year + 1900, month, date, hrs, min, sec) 取代。
分配 Date 对象,并初始化此对象,以表示本地时区中由 year、month、date、hrs、min 和 sec 参数指定的秒的开始瞬间。
参数:
year - 减1900的年份。
month - 0-11之间的月份。
date - 一月中1-31之间的某一天。
hrs - 0-23之间的小时数。
min - 0-59之间的分钟数。
sec - 0-59之间的秒数。
演示:
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class Demo3_Timer {
public static void main(String[] args) throws InterruptedException {
//Timer计时器类
Timer t = new Timer();
//在指定时间安排指定任务,第一个参数是安排的任务,第二个参数是执行的时间,第三个参数是过多长时间再重复执行
t.schedule(new MyTimerTask(), new Date(118, 11, 23, 11, 49, 05), 3000); //3000为毫秒值,意思为过3秒使设定的任务再执行一次
//隔一秒钟打印一次时间
while(true) {
Thread.sleep(1000);
System.out.println(new Date());
}
}
}
class MyTimerTask extends TimerTask {
public void run() {
System.out.println("起床读英文报纸。");
}
}
两个线程间的通信
什么时候需要通信
多个线程并发执行时,在默认情况下CPU是随机切换线程的。
如果我们希望他们有规律的执行,就可以使用通信,例如每个线程执行一次打印。
怎么通信
如果希望线程等待,就调用wait()
如果希望唤醒等待的线程,就调用notify();
这两个方法必须在同步代码中执行,并且使用同步锁对象来调用。
Object类中的成员方法:
public final void wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。
public final void notify() 唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
一次只能有一个线程拥有对象的监视器。
演示:
/**
* 等待唤醒机制
*/
public class Demo1_Notify {
public static void main(String[] args) {
final Printer p = new Printer(); //局部内部类在使用局部变量的时候,必须用final修饰
new Thread() {
public void run() {
while(true) {
try {
p.print1();
} catch (InterruptedException e) { //run()中的异常只能自己处理,父类没有抛异常,子类当然不能抛异常
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
try {
p.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
/*如果想要两条线程交替打印,就需要等待唤醒机制
* 两条线程同时全都开启,比如先执行第一个方法,做判断1 == 1不满足条件,然后输出了一次Hello! flag改成2.现在没有等待的线程,但也可以随机唤醒一下。
* 如果当前线程还有执行权,再进入方法体做判断2 != 1满足条件,当前线程等待。执行权让出,让另一条线程去执行。
* 另一条线程跑起来,调用Print2方法,做判断flag为2 == 2不满足条件,执行了一次World! flag改为1,this.notify();把等待的线程叫醒了。
* 又有可能他还拥有执行权,再进入方法体做判断1 != 2满足条件,这条线程也去等待了。此时活着的线程是之前刚被叫醒的那个执行print1()的线程。
* 然后在最先执行的那条线程再重复先前的步骤 判断,不符合条件 - 打印 - flag改变值 - 唤醒一个等待中的线程 - 判断,符合条件 - 等待
* 等待之前,他们都唤醒了对方,这样就形成了一个间隔输出
*/
class Printer {
private int flag = 1;
public void print1() throws InterruptedException {
synchronized(this) {
if(flag != 1) {
this.wait(); //当前线程等待
}
System.out.print("H");
System.out.print("e");
System.out.print("l");
System.out.print("l");
System.out.print("o");
System.out.print("!");
System.out.print("\r\n");
flag = 2;
this.notify(); //随机唤醒单个等待的线程
}
}
public void print2() throws InterruptedException {
synchronized(this) {
if(flag != 2) {
this.wait();
}
System.out.print("W");
System.out.print("o");
System.out.print("r");
System.out.print("l");
System.out.print("d");
System.out.print("!");
System.out.print("\r\n");
flag = 1;
this.notify();
}
}
}
三个或三个以上间的线程通信
多个线程通信的问题
JDK5之前无法唤醒指定的一个线程。
如果多个线程之间通信,需要使用notifyAll()通知所有线程,用while来反复判断条件。
Object类中的成员方法:
public final void notifyAll() 唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
演示:
public class Demo2_NotifyAll {
public static void main(String[] args) {
final Print