24.01 JDK5之后的Lock锁的概述和使用
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
public interface Lock:Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
例:
1 public class SellTicket implements Runnable 2 { 3 // 定义票 4 private int tickets = 100; 5 6 // 定义锁对象 7 private Lock lock = new ReentrantLock(); 8 9 @Override 10 public void run() 11 { 12 while (true) 13 { 14 try 15 { 16 // 加锁 17 lock.lock(); 18 if (tickets > 0) 19 { 20 try 21 { 22 Thread.sleep(100); 23 } 24 catch (InterruptedException e) 25 { 26 e.printStackTrace(); 27 } 28 System.out.println(Thread.currentThread().getName() 29 + "正在出售第" + (tickets--) + "张票"); 30 } 31 } 32 finally 33 { 34 // 确保锁一定释放 35 lock.unlock(); 36 } 37 } 38 } 39 }
24.02 死锁问题概述和使用
同步弊端:效率低,如果出现了同步嵌套,就容易产生死锁问题
死锁问题:是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
同步代码块的嵌套案例
1 public class DieLock extends Thread 2 { 3 private boolean flag; 4 5 // 创建两把锁对象 6 public static final Object objA = new Object(); 7 public static final Object objB = new Object(); 8 9 public DieLock(boolean flag) 10 { 11 this.flag = flag; 12 } 13 14 @Override 15 public void run() 16 { 17 if (flag) 18 { 19 synchronized (objA) 20 { 21 System.out.println("if objA"); 22 synchronized (objB) 23 { 24 System.out.println("if objB"); 25 } 26 } 27 } 28 else 29 { 30 synchronized (objB) 31 { 32 System.out.println("else objB"); 33 synchronized (objA) 34 { 35 System.out.println("else objA"); 36 } 37 } 38 } 39 } 40 }
理想状态下运行结果:
if objA if objB else objB else objA
死锁运行结果:
if objA else objB
24.03 生产者消费者问题代码1
线程间通讯:多个线程在处理同一资源,但是任务却不同
例:
1 public class Practice 2 { 3 public static void main(String[] args) 4 { 5 //创建资源 6 Student s = new Student(); 7 8 //设置和获取的类 9 SetThread st = new SetThread(s); 10 GetThread gt = new GetThread(s); 11 12 //线程类 13 Thread t1 = new Thread(st); 14 Thread t2 = new Thread(gt); 15 16 //启动线程 17 t2.start(); 18 t1.start(); 19 } 20 }
问题:t1线程是用来设置学生的信息,而t2线程是用来获取学生的信息,理想状态下t1先运行t2后运行没有问题,但如果t2先运行则获取的结果是null---0
24.04 生产者消费者题代码2并解决线程安全问题
问题:加入了循环和判断,给出不同的值,这个时候产生了新的问题
1:同一个数据出现多次
2:姓名和年龄不匹配
原因:
1:同一个数据出现多次,因为CPU的一点点时间片的执行权,就足够执行很多次
2:姓名和年龄不匹配,线程运行的随机性
解决方案:加锁
1:不同种类的线程都要加锁
2:不同种类的线程加的锁必须是同一把
例:
1 public class SetThread implements Runnable 2 { 3 private Student s; 4 private int i = 0; 5 public SetThread(Student s) 6 { 7 this.s = s; 8 } 9 10 @Override 11 public void run() 12 { 13 while(true) 14 { 15 synchronized (s) 16 { 17 if(i % 2 == 0) 18 { 19 s.name = "旺财"; 20 s.age = 7; 21 } 22 else 23 { 24 s.name = "小强强强"; 25 s.age = 3; 26 } 27 i++; 28 } 29 } 30 } 31 32 } 33 34 35 public class GetThread implements Runnable 36 { 37 private Student s; 38 39 public GetThread(Student s) 40 { 41 this.s = s; 42 } 43 44 @Override 45 public void run() 46 { 47 while(true) 48 { 49 synchronized (s) 50 { 51 System.out.println(s.name + "---" + s.age); 52 } 53 } 54 } 55 }
24.05 生产者消费者之等待唤醒机制思路
思路:
生产者:先看是否有数据,有就等待,没有就生产,生产完之后通知消费者消费
消费者:先看是否有数据,有就消费,没有就等待,等待时通知生产者生产
Java提供了等待唤醒机制
24.06 生产者消费者之等待唤醒机制代码实现
通过Java提供的等待唤醒机制来实现一次一个输出
等待唤醒:
Object类中提供了三个方法:
wait():等待
notify():唤醒单个线程
notifyAll():唤醒所有线程
为什么操作线程的方法wait notify notifyAll定义在Object类中?
因为这些方法是监视器的方法,监视器其实就是锁。锁可以是任意的对象,任意的对象调用的方法一定定义在Object类中。
1 public class SetThread implements Runnable 2 { 3 private Student s; 4 private int x = 0; 5 6 public SetThread(Student s) 7 { 8 this.s = s; 9 } 10 11 @Override 12 public void run() 13 { 14 while (true) 15 { 16 synchronized (s) 17 { 18 //判断有没有 19 if(s.flag) 20 { 21 try 22 { 23 //有就等待 24 s.wait();//等待立即释放锁 25 } catch (InterruptedException e) 26 { 27 e.printStackTrace(); 28 } 29 } 30 31 if (x % 2 == 0) 32 { 33 s.name = "小明"; 34 s.age = 17; 35 } 36 else 37 { 38 s.name = "旺财"; 39 s.age = 3; 40 } 41 x++; 42 43 //修改标记 44 s.flag = true; 45 //唤醒线程 46 s.notify(); 47 } 48 } 49 } 50 } 51 52 53 public class GetThread implements Runnable 54 { 55 private Student s; 56 57 public GetThread(Student s) 58 { 59 this.s = s; 60 } 61 62 @Override 63 public void run() 64 { 65 while (true) 66 { 67 synchronized (s) 68 { 69 if(!s.flag) 70 { 71 try 72 { 73 s.wait(); //等待立即释放锁 74 } 75 catch (InterruptedException e) 76 { 77 e.printStackTrace(); 78 } 79 } 80 81 System.out.println(s.name + "---" + s.age); 82 //修改标记 83 s.flag = false; 84 //唤醒线程 85 s.notify(); 86 } 87 } 88 } 89 }
24.07 线程的状态转换图及常见执行情况
24.08 线程组的概述和使用
Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
方法:public final ThreadGroup getThreadGroup()
返回该线程所属的线程组。如果该线程已经终止(停止运行),该方法则返回 null。
构造方法:public Thread(ThreadGroup group,Runnable target,String name)
分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,并作为 group 所引用的线程组的一员。
默认情况下,所有的线程都属于主线程组。
例:
1 public class Practice 2 { 3 public static void main(String[] args) 4 { 5 method2(); 6 } 7 private static void method1() 8 { 9 MyRunnable my = new MyRunnable(); 10 Thread t1 = new Thread(my); 11 Thread t2 = new Thread(my); 12 // 线程类里面的方法:public final ThreadGroup getThreadGroup() 13 ThreadGroup tg1 = t1.getThreadGroup(); 14 ThreadGroup tg2 = t2.getThreadGroup(); 15 // 线程组类里面的方法:public final String getName() 16 //获取线程组名称 17 String name1 = tg1.getName();//main 18 String name2 = tg2.getName();//main 19 System.out.println(name1); 20 System.out.println(name2); 21 // 线程默认情况下属于main线程组,所有的线程都属于同一个组 22 System.out.println(Thread.currentThread().getThreadGroup().getName());//main 23 } 24 private static void method2() 25 { 26 // ThreadGroup(String name) 27 ThreadGroup tg = new ThreadGroup("新的线程组"); 28 29 MyRunnable my = new MyRunnable(); 30 // Thread(ThreadGroup group, Runnable target, String name) 31 Thread t1 = new Thread(tg, my); 32 Thread t2 = new Thread(tg, my); 33 34 System.out.println(t1.getThreadGroup().getName());//新的线程组 35 System.out.println(t2.getThreadGroup().getName());//新的线程组 36 37 //通过组名称设置后台线程,表示该组的线程都是后台线程 38 tg.setDaemon(true); 39 } 40 }
24.09 生产者消费者之等待唤醒机制代码优化
1 public class Student 2 { 3 private String name; 4 private int age; 5 // 默认情况是没有数据,如果是true,说明有数据 6 private boolean flag; 7 8 public synchronized void set(String name, int age) 9 { 10 // 如果有数据,就等待 11 if (this.flag) 12 { 13 try 14 { 15 this.wait(); 16 } 17 catch (InterruptedException e) 18 { 19 e.printStackTrace(); 20 } 21 } 22 23 // 设置数据 24 this.name = name; 25 this.age = age; 26 27 // 修改标记 28 this.flag = true; 29 this.notify(); 30 } 31 32 public synchronized void get() 33 { 34 // 如果没有数据,就等待 35 if (!this.flag) 36 { 37 try 38 { 39 this.wait(); 40 } 41 catch (InterruptedException e) 42 { 43 e.printStackTrace(); 44 } 45 } 46 47 // 获取数据 48 System.out.println(this.name + "---" + this.age); 49 50 // 修改标记 51 this.flag = false; 52 this.notify(); 53 } 54 } 55 -------------------------------------------------------------------- 56 public class SetThread implements Runnable 57 { 58 private Student s; 59 private int x = 0; 60 61 public SetThread(Student s) 62 { 63 this.s = s; 64 } 65 66 @Override 67 public void run() 68 { 69 while (true) 70 { 71 if (x % 2 == 0) 72 { 73 s.set("xiaoming", 17); 74 } 75 else 76 { 77 s.set("旺财", 7); 78 } 79 x++; 80 } 81 } 82 } 83 -------------------------------------------------------------------- 84 public class GetThread implements Runnable 85 { 86 private Student s; 87 88 public GetThread(Student s) 89 { 90 this.s = s; 91 } 92 93 @Override 94 public void run() 95 { 96 while (true) 97 { 98 s.get(); 99 } 100 } 101 } 102 -------------------------------------------------------------------- 103 public class Practice 104 { 105 public static void main(String[] args) 106 { 107 //创建资源 108 Student s = new Student(); 109 110 //设置和获取的类 111 SetThread st = new SetThread(s); 112 GetThread gt = new GetThread(s); 113 114 //线程类 115 Thread t1 = new Thread(st); 116 Thread t2 = new Thread(gt); 117 118 //启动线程 119 t1.start(); 120 t2.start(); 121 } 122 }
24.10 线程池的概述和使用
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
1.public static ExecutorService newCachedThreadPool():创建一个具有缓存功能的线程池
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
2.public static ExecutorService newFixedThreadPool(int nThreads):创建一个可重用的,具有固定线程数的线程池
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
3.public static ExecutorService newSingleThreadExecutor():创建一个只有单线程的线程池,相当于上个方法的参数是1
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。
接口 ExecutorService提供了如下方法
1.Future> submit(Runnable task)
提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。该 Future 的 get 方法在成功完成时将会返回 null。
2.
提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
例:
1 public class Practice 2 { 3 public static void main(String[] args) 4 { 5 // 创建一个线程池对象,控制要创建几个线程对象。 6 // public static ExecutorService newFixedThreadPool(int nThreads) 7 ExecutorService pool = Executors.newFixedThreadPool(2); 8 9 // 可以执行Runnable对象或者Callable对象代表的线程 10 pool.submit(new MyRunnable()); 11 pool.submit(new MyRunnable()); 12 13 //结束线程池 14 pool.shutdown(); 15 } 16 }
24.11 多线程方式3的代码实现
1 //这里指定的泛型其实是call()方法的返回值类型 2 public class MyCallable implements Callable 3 { 4 @Override 5 public Object call() throws Exception 6 { 7 for (int x = 0; x < 100; x++) 8 { 9 System.out.println(Thread.currentThread().getName() +":"+ x); 10 } 11 return null; 12 } 13 } 14 15 16 public class Practice 17 { 18 public static void main(String[] args) 19 { 20 //创建线程池对象 21 ExecutorService pool = Executors.newFixedThreadPool(2); 22 23 //可以执行Runnable对象或者Callable对象代表的线程 24 pool.submit(new MyCallable()); 25 pool.submit(new MyCallable()); 26 27 //结束 28 pool.shutdown(); 29 } 30 }
好处:可以有返回值、可以抛出异常
弊端:代码比较复杂,所以一般不用
24.12 多线程方式3的求和案例
1 public class MyCallable implements Callable2 { 3 private int number; 4 public MyCallable(int number) 5 { 6 this.number = number; 7 } 8 @Override 9 public Integer call() throws Exception 10 { 11 int sum = 0; 12 for (int x = 1; x <= number; x++) 13 { 14 sum += x; 15 } 16 return sum; 17 } 18 } 19 20 public class Practice 21 { 22 public static void main(String[] args) throws InterruptedException, ExecutionException 23 { 24 //创建线程池对象 25 ExecutorService pool = Executors.newFixedThreadPool(2); 26 27 //可以执行Runnable对象或者Callable对象代表的线程 28 Future f1 = pool.submit(new MyCallable(6)); 29 Future f2 = pool.submit(new MyCallable(100)); 30 31 Integer i1 = f1.get(); 32 Integer i2 = f2.get(); 33 System.out.println(i1); 34 System.out.println(i2); 35 //结束 36 pool.shutdown(); 37 } 38 }
24.13 匿名内部类的方式实现多线程程序
1 public class Practice 2 { 3 public static void main(String[] args) 4 { 5 // 继承Thread类来实现多线程 6 new Thread() 7 { 8 public void run() 9 { 10 for (int x = 0; x < 100; x++) 11 { 12 System.out.println(Thread.currentThread().getName() + ":"+ x); 13 } 14 } 15 }.start(); 16 17 // 实现Runnable接口来实现多线程 18 new Thread(new Runnable() 19 { 20 @Override 21 public void run() 22 { 23 for (int x = 0; x < 100; x++) 24 { 25 System.out.println(Thread.currentThread().getName() + ":"+ x); 26 } 27 } 28 }) 29 {}.start(); 30 31 // 更有难度的 32 new Thread(new Runnable() 33 { 34 @Override 35 public void run() 36 { 37 for (int x = 0; x < 100; x++) 38 { 39 System.out.println("hello" + ":" + x); 40 } 41 } 42 }) 43 { 44 public void run() 45 { 46 for (int x = 0; x < 100; x++) 47 { 48 //子类对象运行 49 System.out.println("world" + ":" + x); 50 } 51 } 52 }.start(); 53 } 54 }
24.14 定时器的概述和使用
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能
类 Timer:一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。
构造方法:
public Timer()
创建一个新计时器。相关的线程不作为守护程序运行。
方法:
1.public void schedule(TimerTask task,long delay)
安排在指定延迟后执行指定的任务。
2.public void schedule(TimerTask task,long delay,long period)
安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。
类 TimerTask:由 Timer 安排为一次执行或重复执行的任务。
1.public abstract void run()
此计时器任务要执行的操作。
2.public boolean cancel()
取消此计时器任务。如果任务安排为一次执行且还未运行,或者尚未安排,则永远不会运行。
开发中:Quartz是一个完全由java编写的开源调度框架。
例:
1 public class Practice 2 { 3 public static void main(String[] args) 4 { 5 // 创建定时器对象 6 Timer t = new Timer(); 7 //结束任务 8 t.schedule(new MyTask(t), 3000); 9 } 10 } 11 //做一个任务 12 class MyTask extends TimerTask 13 { 14 15 private Timer t; 16 17 public MyTask(){} 18 19 public MyTask(Timer t) 20 { 21 this.t = t; 22 } 23 24 @Override 25 public void run() 26 { 27 System.out.println("任务执行"); 28 t.cancel(); 29 } 30 }
24.15 定时任务的多次执行代码体现
1 public class Practice 2 { 3 public static void main(String[] args) 4 { 5 // 创建定时器对象 6 Timer t = new Timer(); 7 t.schedule(new MyTask(), 3000, 2000); 8 } 9 } 10 //做一个任务 11 class MyTask extends TimerTask 12 { 13 @Override 14 public void run() 15 { 16 System.out.println("任务执行"); 17 } 18 }
24.16 定时删除指定的带内容目录
1 public class Practice 2 { 3 public static void main(String[] args) throws ParseException 4 { 5 // 创建定时器对象 6 Timer t = new Timer(); 7 8 String s = "2014-11-27 15:45:00"; 9 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 10 Date d = sdf.parse(s); 11 12 t.schedule(new DeleteFolder(), d); 13 } 14 } 15 //做一个任务 16 class DeleteFolder extends TimerTask 17 { 18 @Override 19 public void run() 20 { 21 File srcFolder = new File("demo"); 22 deleteFolder(srcFolder); 23 } 24 25 // 递归删除目录 26 public void deleteFolder(File srcFolder) 27 { 28 File[] fileArray = srcFolder.listFiles(); 29 if (fileArray != null) 30 { 31 for (File file : fileArray) 32 { 33 if (file.isDirectory()) 34 { 35 deleteFolder(file); 36 } 37 else 38 { 39 System.out.println(file.getName() + ":" + file.delete()); 40 } 41 } 42 System.out.println(srcFolder.getName() + ":" + srcFolder.delete()); 43 } 44 } 45 }
24.17 多线程常见的面试题
1:多线程有几种实现方案,分别是哪几种?
两种。继承Thread类、实现Runnable接口
扩展一种:实现Callable接口。这个得和线程池结合。
2:同步有几种方式,分别是什么?
两种。同步代码块、同步方法
3:启动一个线程是run()还是start()?它们的区别?
start();
run():封装了被线程执行的代码,直接调用仅仅是普通方法的调用
start():启动线程,并由JVM自动调用run()方法
4:sleep()和wait()方法的区别
sleep():必须指时间;不释放锁。
wait():可以不指定时间,也可以指定时间;释放锁。
5:为什么wait(),notify(),notifyAll()等方法都定义在Object类中
因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。而Object代表任意的对象,所以,定义在这里面。
6:线程的生命周期图
新建 -- 就绪 -- 运行 -- 死亡
新建 -- 就绪 -- 运行 -- 阻塞 -- 就绪 -- 运行 -- 死亡
画图解释
24.18 面向对象的常见设计原则
面向对象思想的设计原则:
1.单一职责原则:
其实就是开发人员经常说的”高内聚,低耦合”
也就是说,每个类应该只有一个职责,对外只能提供一种功能,而引起类变化的原因应该只有一个。在设计模式中,所有的设计模式都遵循这一原则。
2.开闭原则
核心思想是:一个对象对扩展开放,对修改关闭。
其实开闭原则的意思就是:对类的改动是通过增加代码进行的,而不是修改现有代码。
3.里氏替换原则
核心思想:在任何父类出现的地方都可以用它的子类来替代。
其实就是说:同一个继承体系中的对象应该有共同的行为特征。
4.依赖注入原则
核心思想:要依赖于抽象,不要依赖于具体实现。
其实就是说:在应用程序中,所有的类如果使用或依赖于其他的类,则应该依赖这些其他类的抽象类,而不是这些其他类的具体类。为了实现这一原则,就要求我们在编程的时候针对抽象类或者接口编程,而不是针对具体实现编程。
5.接口分离原则
核心思想:不应该强迫程序依赖它们不需要使用的方法。
其实就是说:一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一个接口中。
6.迪米特原则
核心思想:一个对象应当对其他对象尽可能少的了解
其实就是说:降低各个对象之间的耦合,提高系统的可维护性。在模块之间应该只通过接口编程,而不理会模块的内部工作原理,它可以使各个模块耦合度降到最低,促进软件的复用
24.19 设计模式的概述和分类
设计模式概述:设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
设计模式不是一种方法和技术,而是一种思想
设计模式和具体的语言无关,学习设计模式就是要建立面向对象的思想,尽可能的面向接口编程,低耦合,高内聚,使设计的程序可复用,学习设计模式能够促进对面向对象思想的理解,反之亦然。它们相辅相成
设计模式的几个要素:
名字必须有一个简单,有意义的名字
问题描述在何时使用模式
解决方案描述设计的组成部分以及如何解决问题
效果描述模式的效果以及优缺点
设计模式的分类
创建型模式对象的创建
结构型模式对象的组成(结构)
行为型模式对象的行为
创建型模式:简单工厂模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式、单例模式。(6个)
结构型模式:外观模式、适配器模式、代理模式、装饰模式、桥接模式、组合模式、享元模式。(7个)
行为型模式:模版方法模式、观察者模式、状态模式、职责链模式、命令模式、访问者模式、策略模式、备忘录模式、迭代器模式、解释器模式。(10个)
24.20 简单工厂模式概述和使用
简单工厂模式概述:又叫静态工厂方法模式,它定义一个具体的工厂类负责创建一些类的实例
优点:客户端不需要在负责对象的创建,从而明确了各个类的职责
缺点:这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护
例:
1 public class Practice 2 { 3 public static void main(String[] args) 4 { 5 // // 具体类调用 6 // Dog d = new Dog(); 7 // d.eat(); 8 // Cat c = new Cat(); 9 // c.eat(); 10 // System.out.println("------------"); 11 12 // //使用工厂创建对象 13 // Dog dd = AnimalFactory.createDog(); 14 // Cat cc = AnimalFactory.createCat(); 15 // dd.eat(); 16 // cc.eat(); 17 // System.out.println("------------"); 18 19 // 工厂改进后 20 Animal a = AnimalFactory.createAnimal("dog"); 21 a.eat(); 22 a = AnimalFactory.createAnimal("cat"); 23 a.eat(); 24 25 // NullPointerException 26 a = AnimalFactory.createAnimal("pig"); 27 if (a != null) 28 { 29 a.eat(); 30 } 31 else 32 { 33 System.out.println("没有这种动物"); 34 } 35 } 36 } 37 abstract class Animal 38 { 39 public abstract void eat(); 40 } 41 42 class Dog extends Animal 43 { 44 @Override 45 public void eat() 46 { 47 System.out.println("狗吃肉"); 48 } 49 } 50 class Cat extends Animal 51 { 52 53 @Override 54 public void eat() 55 { 56 System.out.println("猫吃鱼"); 57 } 58 } 59 60 //工厂类 61 class AnimalFactory 62 { 63 private AnimalFactory() 64 {} 65 66 // public static Dog createDog() { 67 // return new Dog(); 68 // } 69 // 70 // public static Cat createCat() { 71 // return new Cat(); 72 // } 73 74 public static Animal createAnimal(String type) 75 { 76 if ("dog".equals(type)) 77 { 78 return new Dog(); 79 } 80 else if ("cat".equals(type)) 81 { 82 return new Cat(); 83 } 84 else 85 { 86 return null; 87 } 88 } 89 }
24.21 工厂方法模式的概述和使用
工厂方法模式概述:工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
优点:客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性
缺点:需要额外的编写代码,增加了工作量
例:
1 public class Practice 2 { 3 public static void main(String[] args) 4 { 5 // 创建狗 6 Factory f = new DogFactory(); 7 Animal a = f.createAnimal(); 8 a.eat(); 9 System.out.println("-------"); 10 11 //创建猫 12 f = new CatFactory(); 13 a = f.createAnimal(); 14 a.eat(); 15 } 16 } 17 //动物类 18 abstract class Animal 19 { 20 public abstract void eat(); 21 } 22 //狗类 23 class Dog extends Animal 24 { 25 @Override 26 public void eat() 27 { 28 System.out.println("狗吃肉"); 29 } 30 } 31 //猫类 32 class Cat extends Animal 33 { 34 35 @Override 36 public void eat() 37 { 38 System.out.println("猫吃鱼"); 39 } 40 } 41 //工厂 42 interface Factory 43 { 44 public abstract Animal createAnimal(); 45 } 46 //创建Cat的工厂 47 class CatFactory implements Factory 48 { 49 @Override 50 public Animal createAnimal() 51 { 52 return new Cat(); 53 } 54 55 } 56 //创建Dog的工厂 57 class DogFactory implements Factory 58 { 59 @Override 60 public Animal createAnimal() 61 { 62 return new Dog(); 63 } 64 }
24.22 单例模式之饿汉式
单例设计模式概述:单例模式就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供。
优点:在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
缺点:没有抽象层,因此扩展很难。职责过重,在一定程序上违背了单一职责
保证对象唯一性的条件:
1.不允许其他程序用new创建该类对象
2.在该类创建一个本类实例
3.对外提供一个方法让其他程序可以获取该对象
保证对象唯一性的步骤:
1.私有化该类的构造函数
2.通过new在本类中创建一个本类的对象
3.定义一个公有的方法,将创建的对象返回
例:
1 public class Student 2 { 3 // 构造私有 4 private Student() 5 {} 6 7 // 静态方法只能访问静态成员变量,加静态 8 // 为了不让外界直接访问修改这个值,加private 9 private static Student s = new Student(); 10 11 // 提供公共的访问方式 12 // 为了保证外界能够直接使用该方法,加静态 13 public static Student getStudent() 14 { 15 return s; 16 } 17 }
24.23 单例模式之懒汉式
例:
1 class Teacher 2 { 3 private Teacher() 4 {} 5 6 private static Teacher t = null; 7 8 public static Teacher getTeacher() 9 { 10 if (t == null) //该语句在被多线程操作时存在安全隐患 11 { 12 t = new Teacher(); 13 } 14 return t; 15 } 16 }
上面的单例模式在被多线程操作时是有问题的
解决单例模式延迟加载涉及的多线程安全问题
方案1:使用同步方法(多线程操作时每次都需要判断锁,效率低)
1 class Teacher 2 { 3 private Teacher() 4 {} 5 6 private static Teacher t = null; 7 //同步方法 8 public synchronized static Teacher getTeacher() 9 { 10 if (t == null) 11 { 12 t = new Teacher(); 13 } 14 return t; 15 } 16 }
方案2:使用同步代码块
1 class Teacher 2 { 3 private Teacher() 4 {} 5 6 private static Teacher t = null; 7 8 public static Teacher getTeacher() 9 { 10 if (t == null) //提高效率 11 { 12 //同步代码块 13 synchronized (t) //解决安全问题 14 { 15 if(t == null) 16 t = new Teacher(); 17 } 18 } 19 return t; 20 } 21 }
24.24 单例模式的Java代码体现Runtime类
Runtime类概述:每个Java应用程序都有一个Runtime类实例,使应用程序能够与其运行的环境相连接。
可以通过 getRuntime 方法获取当前运行时。应用程序不能创建自己的 Runtime 类实例。(单例设计模式饿汉式)
Runtime类使用
public Process exec(String command)throws IOException
在单独的进程中执行指定的字符串命令。
例:
Runtime r = Runtime.getRuntime(); r.exec("notepad");//打开记事本