java基础学习_多线程02_多线程、设计模式_day24总结
============================================================================= ============================================================================= 涉及到的知识点有: 1:多线程(理解) (1)JDK5中Lock锁的使用 (2)Lock接口的方法 (3)死锁问题的描述和代码体现 (4)生产者和消费者多线程体现(线程间通信问题) (5)线程的状态转换图及常见的线程执行情况 (6)线程组 (7)线程池 (8)多线程实现的第三种方案:依赖于线程池而存在的 (9)匿名内部类方式使用多线程 (10)定时器 (11)多线程常见的面试题 2:设计模式(理解) (1)面向对象思想的设计原则的概述 (2)设计模式 (3)常见的设计模式 (4)Runtime类的概述和应用 ============================================================================= ============================================================================= 1:多线程(理解) (1)JDK5中Lock锁的使用 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁, 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock接口。 即:JDK5以后的针对线程的锁定操作和释放操作。 Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。 (2)Lock接口的方法 void lock() 获取锁(加锁) void unlock() 释放锁 ReentrantLock类是Lock接口的实现类。 示例代码如下:
1 package cn.itcast_01; 2 /* 3 * 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁, 4 * 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock接口。 5 * 6 * Lock接口的方法: 7 * void lock() 获取锁(加锁) 8 * void unlock() 释放锁 9 * 10 * ReentrantLock类是Lock接口的实现类。 11 */ 12 public class SellTicketDemo { 13 public static void main(String[] args) { 14 // 创建资源对象 15 SellTicket st = new SellTicket(); 16 17 // 创建三个线程对象 18 Thread t1 = new Thread(st, "窗口1"); 19 Thread t2 = new Thread(st, "窗口2"); 20 Thread t3 = new Thread(st, "窗口3"); 21 22 // 启动线程 23 t1.start(); 24 t2.start(); 25 t3.start(); 26 } 27 }
1 package cn.itcast_01; 2 3 import java.util.concurrent.locks.Lock; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 public class SellTicket implements Runnable { 7 // 定义票 8 private int tickets = 100; 9 10 // 定义锁对象 11 private Lock lock = new ReentrantLock(); // 多态 12 13 @Override 14 public void run() { 15 while (true) { 16 // 如果锁里面有异常发生,释放锁lock.unlock();就不会被执行, 17 // 为了保证释放锁lock.unlock();被执行,使用try...finally... 18 // 将lock.unlock();放在finally里面。 19 try { 20 // 获取锁(加锁) 21 lock.lock(); 22 if (tickets > 0) { 23 try { 24 Thread.sleep(100); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); 29 } 30 } finally { 31 // 释放锁 32 lock.unlock(); 33 } 34 } 35 } 36 37 }
-------------------------------------- (3)死锁问题的描述和代码体现 同步弊端:效率低;如果出现了同步嵌套,就容易产生死锁问题。 死锁问题:是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。 同步代码块的嵌套案例代码如下:
1 package cn.itcast_02; 2 3 public class MyLock { 4 // 创建两把锁对象 5 public static final Object objA = new Object(); 6 public static final Object objB = new Object(); 7 }
1 package cn.itcast_02; 2 3 public class DieLock extends Thread { 4 5 private boolean flag; 6 7 public DieLock(boolean flag) { 8 this.flag = flag; 9 } 10 11 @Override 12 public void run() { 13 if (flag) { 14 synchronized (MyLock.objA) { 15 System.out.println("if objA"); 16 synchronized (MyLock.objB) { 17 System.out.println("if objB"); 18 } 19 } 20 } else { 21 synchronized (MyLock.objB) { 22 System.out.println("else objB"); 23 synchronized (MyLock.objA) { 24 System.out.println("else objA"); 25 } 26 } 27 } 28 } 29 30 }
1 package cn.itcast_02; 2 3 /* 4 * 同步的弊端: 5 * A:效率低 6 * B:如果出现了同步嵌套,就容易产生死锁问题 7 * 8 * 死锁: 9 * 是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。 10 * 11 * 举例: 12 * 中国人和美国人一起吃饭案例。 13 * 正常情况: 14 * 中国人:筷子两支 15 * 美国人:刀和叉 16 * 现在: 17 * 中国人:筷子1支,刀一把 18 * 美国人:筷子1支,叉一把 19 */ 20 public class DieLockDemo { 21 public static void main(String[] args) { 22 DieLock dl1 = new DieLock(true); 23 DieLock dl2 = new DieLock(false); 24 25 dl1.start(); 26 dl2.start(); 27 } 28 }
-------------------------------------- (4)生产者和消费者多线程体现(线程间通信问题) 线程间通信:针对同一个资源的操作有不同种类的线程。 举例:卖票有进的,也有出的。 通过设置线程(生产者)和获取线程(消费者)针对同一个学生对象进行操作。
以学生作为资源来实现的。 学生资源类:Student 设置学生数据线程类:SetThread(生产者) 获取学生数据线程类:GetThread(消费者) 测试类:StudentDemo 代码: A:最基本的版本,只有一个数据。 B:改进版本,给出了不同的数据,并加入了同步机制。 C:等待唤醒机制改进该程序,让数据能够实现依次的出现。 wait() notify() (用在单个生产线程和单个消费线程) notifyAll() (用在多个生产线程和多个消费线程) D:等待唤醒机制的代码优化。把数据和操作都写在了学生资源类中。
A版本代码如下:
1 package cn.itcast_03; 2 3 public class Student { 4 String name; 5 int age; 6 }
1 package cn.itcast_03; 2 3 public class SetThread implements Runnable { 4 5 private Student s; 6 7 // 带参构造 8 public SetThread(Student s) { 9 this.s = s; 10 } 11 12 @Override 13 public void run() { 14 // Student s = new Student(); 15 s.name = "林青霞"; 16 s.age = 27; 17 } 18 19 }
1 package cn.itcast_03; 2 3 public class GetThread implements Runnable { 4 5 private Student s; 6 7 // 带参构造 8 public GetThread(Student s) { 9 this.s = s; 10 } 11 12 @Override 13 public void run() { 14 // Student s = new Student(); 15 System.out.println(s.name + "---" + s.age); 16 } 17 18 }
1 package cn.itcast_03; 2 3 /* 4 * 分析: 5 * 学生资源类:Student 6 * 设置学生数据线程类:SetThread(生产者) 7 * 获取学生数据线程类:GetThread(消费者) 8 * 测试类:StudentDemo 9 * 10 * 问题1:按照思路写代码,发现数据每次都是:null---0 11 * 原因: 12 * 我们在每个线程中都创建了新的资源:Student s = new Student(); 13 * 而我们要求的是:设置和获取线程的资源应该是同一个。 14 * 如何解决呢? 15 * 如何把一个数据在多个类中共享使用呢? 16 * 思路1:在外界把这个数据创建出来,通过构造方法传递给其他的类。 17 * 18 */ 19 public class StudentDemo { 20 public static void main(String[] args) { 21 // 创建资源对象 22 Student s = new Student(); 23 24 // 创建设置和获取对象,并把资源对象作为构造方法的参数进行传递 25 SetThread st = new SetThread(s); 26 GetThread gt = new GetThread(s); 27 28 // 创建线程对象 29 Thread t1 = new Thread(st); 30 Thread t2 = new Thread(gt); 31 32 // 启动线程 33 t1.start(); 34 t2.start(); 35 } 36 }
B版本代码如下:
1 package cn.itcast_04; 2 3 public class Student { 4 String name; 5 int age; 6 }
1 package cn.itcast_04; 2 3 public class SetThread implements Runnable { 4 private int x = 0; 5 6 private Student s; 7 8 // 带参构造 9 public SetThread(Student s) { 10 this.s = s; 11 } 12 13 @Override 14 public void run() { 15 while (true) { 16 synchronized (s) { 17 if (x % 2 == 0) { 18 s.name = "林青霞"; // 刚走到这里,就被别人抢到了执行权 19 s.age = 27; 20 } else { 21 s.name = "刘意"; // 刚走到这里,就被别人抢到了执行权 22 s.age = 30; 23 } 24 x++; 25 } 26 } 27 } 28 29 }
1 package cn.itcast_04; 2 3 public class GetThread implements Runnable { 4 5 private Student s; 6 7 // 带参构造 8 public GetThread(Student s) { 9 this.s = s; 10 } 11 12 @Override 13 public void run() { 14 while (true) { 15 synchronized (s) { 16 System.out.println(s.name + "---" + s.age); 17 } 18 } 19 } 20 21 }
1 package cn.itcast_04; 2 3 /* 4 * 分析: 5 * 学生资源类:Student 6 * 设置学生数据线程类:SetThread(生产者) 7 * 获取学生数据线程类:GetThread(消费者) 8 * 测试类:StudentDemo 9 * 10 * 问题1:按照思路写代码,发现数据每次都是:null---0 11 * 原因: 12 * 我们在每个线程中都创建了新的资源:Student s = new Student(); 13 * 而我们要求的是:设置和获取线程的资源应该是同一个。 14 * 如何解决呢? 15 * 如何把一个数据在多个类中共享使用呢? 16 * 思路1:在外界把这个数据创建出来,通过构造方法传递给其他的类。 17 * 18 * 19 * 问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题 20 * A:同一个数据出现多次 21 * B:姓名和年龄不匹配 22 * 原因: 23 * A:同一个数据出现多次 24 * CPU的一点点时间片的执行权,就足够你执行很多次。 25 * B:姓名和年龄不匹配 26 * 线程运行的随机性 27 * 线程安全问题: 28 * A:是否是多线程环境 是 29 * B:是否有共享数据 是 30 * C:是否有多条语句操作共享数据 是 31 * 解决方案: 32 * 加锁。 33 * 注意: 34 * A:不同种类的线程都要加锁。 35 * B:不同种类的线程加的锁必须是同一把。 36 */ 37 public class StudentDemo { 38 public static void main(String[] args) { 39 // 创建资源对象 40 Student s = new Student(); 41 42 // 创建设置和获取对象,并把资源对象作为构造方法的参数进行传递 43 SetThread st = new SetThread(s); 44 GetThread gt = new GetThread(s); 45 46 // 创建线程对象 47 Thread t1 = new Thread(st); 48 Thread t2 = new Thread(gt); 49 50 // 启动线程 51 t1.start(); 52 t2.start(); 53 } 54 }
C版本代码如下:
1 package cn.itcast_05; 2 3 public class Student { 4 String name; 5 int age; 6 boolean flag; // flag默认值是false,我们标记为没有数据;如果flag是true,我们标记为有数据 7 }
1 package cn.itcast_05; 2 3 public class SetThread implements Runnable { 4 5 private int x = 0; 6 7 private Student s; 8 9 // 带参构造 10 public SetThread(Student s) { 11 this.s = s; 12 } 13 14 @Override 15 public void run() { 16 while (true) { 17 synchronized (s) { 18 // 生产者判断有没有数据 19 20 // 有数据就等着消费者先消费数据 21 if (s.flag) { 22 try { 23 s.wait(); // t1等待,wait()方法的特点:1.线程等待并立即释放锁。2.线程将来醒过来的时候,是从这里醒过来的。 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 } 28 29 // 没有数据就生产数据 30 if (x % 2 == 0) { 31 s.name = "林青霞"; 32 s.age = 27; 33 } else { 34 s.name = "刘意"; 35 s.age = 30; 36 } 37 x++; 38 39 // 程序能执行到这里,说明数据生产出来了 40 s.flag = true; // 修改标记,通知消费者消费数据 41 42 // 唤醒线程(唤醒消费者) 43 s.notify(); // 唤醒t2,唤醒并不表示立马就可以执行,必须还得抢CPU的执行权 44 } 45 } 46 } 47 48 }
1 package cn.itcast_05; 2 3 public class GetThread implements Runnable { 4 5 private Student s; 6 7 // 带参构造 8 public GetThread(Student s) { 9 this.s = s; 10 } 11 12 @Override 13 public void run() { 14 while (true) { 15 synchronized (s) { 16 // 消费者判断有没有数据 17 18 // 没有数据就等着生产者先生产数据 19 if (!s.flag) { 20 try { 21 s.wait(); // t2等待,wait()方法的特点:1.线程等待并立即释放锁。2.线程将来醒过来的时候,是从这里醒过来的。 22 } catch (InterruptedException e) { 23 e.printStackTrace(); 24 } 25 } 26 27 // 有数据就消费 28 System.out.println(s.name + "---" + s.age); 29 30 // 程序能执行到这里,说明数据消费完了 31 s.flag = false; // 修改标记,通知生产者生产数据 32 33 // 唤醒线程(唤醒生产者) 34 s.notify(); // 唤醒t1,唤醒并不表示立马就可以执行,必须还得抢CPU的执行权 35 } 36 } 37 } 38 39 }
1 package cn.itcast_05; 2 3 /* 4 * 分析: 5 * 学生资源类:Student 6 * 设置学生数据线程类:SetThread(生产者) 7 * 获取学生数据线程类:GetThread(消费者) 8 * 测试类:StudentDemo 9 * 10 * 问题1:按照思路写代码,发现数据每次都是:null---0 11 * 原因: 12 * 我们在每个线程中都创建了新的资源:Student s = new Student(); 13 * 而我们要求的是:设置和获取线程的资源应该是同一个。 14 * 如何解决呢? 15 * 如何把一个数据在多个类中共享使用呢? 16 * 思路1:在外界把这个数据创建出来,通过构造方法传递给其他的类。 17 * 18 * 19 * 问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题 20 * A:同一个数据出现多次 21 * B:姓名和年龄不匹配 22 * 原因: 23 * A:同一个数据出现多次 24 * CPU的一点点时间片的执行权,就足够你执行很多次。 25 * B:姓名和年龄不匹配 26 * 线程运行的随机性 27 * 线程安全问题: 28 * A:是否是多线程环境 是 29 * B:是否有共享数据 是 30 * C:是否有多条语句操作共享数据 是 31 * 解决方案: 32 * 加锁。 33 * 注意: 34 * A:不同种类的线程都要加锁。 35 * B:不同种类的线程加的锁必须是同一把。 36 * 37 * 38 * 问题3:虽然数据安全了,但是呢,一次输出一大片不好看,我就想依次的一次一个输出。 39 * 如何解决呢? 40 * 通过Java提供的等待唤醒机制解决。 41 * 等待唤醒: 42 * Object类中提供了三个方法: 43 * public final wait() 等待 44 * public final notify() 唤醒单个线程(用在单个生产线程和单个消费线程) 45 * public final notifyAll() 唤醒所有线程(用在多个生产线程和多个消费线程) 46 * 为什么这些方法不定义在Thread类中呢? 47 * 这些方法的调用必须通过锁对象调用,而我们刚才使用的是同步代码块,其锁对象是任意对象。 48 * 所以,这些方法定义在Object类中。 49 */ 50 public class StudentDemo { 51 public static void main(String[] args) { 52 // 创建资源对象 53 Student s = new Student(); 54 55 // 创建设置和获取对象,并把资源对象作为构造方法的参数进行传递 56 SetThread st = new SetThread(s); 57 GetThread gt = new GetThread(s); 58 59 // 创建线程对象 60 Thread t1 = new Thread(st); 61 Thread t2 = new Thread(gt); 62 63 // 启动线程 64 t1.start(); 65 t2.start(); 66 } 67 }
D版本代码如下:
1 package cn.itcast_07; 2 3 public class Student { 4 private String name; 5 private int age; 6 private boolean flag; // flag默认值是false,我们标记为没有数据;如果flag是true,我们标记为有数据 7 8 public synchronized void set(String name, int age) { 9 // 生产者判断有没有数据 10 // 有数据就等着消费者先消费数据 11 if (this.flag) { 12 try { 13 this.wait(); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 } 18 19 // 没有数据就生产数据 20 this.name = name; 21 this.age = age; 22 23 // 程序能执行到这里,说明数据生产出来了 24 this.flag = true; // 修改标记,通知消费者消费数据 25 this.notify(); // 唤醒t2,唤醒并不表示立马就可以执行,必须还得抢CPU的执行权 26 } 27 28 public synchronized void get() { 29 // 消费者判断有没有数据 30 // 没有数据就等着生产者先生产数据 31 if (!this.flag) { 32 try { 33 this.wait(); 34 } catch (InterruptedException e) { 35 e.printStackTrace(); 36 } 37 } 38 39 // 有数据就消费 40 System.out.println(this.name + "---" + this.age); 41 42 // 程序能执行到这里,说明数据消费完了 43 this.flag = false; // 修改标记,通知生产者生产数据 44 this.notify(); // 唤醒t1,唤醒并不表示立马就可以执行,必须还得抢CPU的执行权 45 } 46 }
1 package cn.itcast_07; 2 3 public class SetThread implements Runnable { 4 5 private int x = 0; 6 7 private Student s; 8 9 // 带参构造 10 public SetThread(Student s) { 11 this.s = s; 12 } 13 14 @Override 15 public void run() { 16 while (true) { 17 if (x % 2 == 0) { 18 s.set("林青霞", 27); 19 } else { 20 s.set("刘意", 30); 21 } 22 x++; 23 } 24 } 25 26 } 27
1 package cn.itcast_07; 2 3 public class GetThread implements Runnable { 4 5 private Student s; 6 7 // 带参构造 8 public GetThread(Student s) { 9 this.s = s; 10 } 11 12 @Override 13 public void run() { 14 while (true) { 15 s.get(); 16 } 17 } 18 19 }
1 package cn.itcast_07; 2 3 /* 4 * 分析: 5 * 学生资源类:Student 6 * 设置学生数据线程类:SetThread(生产者) 7 * 获取学生数据线程类:GetThread(消费者) 8 * 测试类:StudentDemo 9 * 10 * 问题1:按照思路写代码,发现数据每次都是:null---0 11 * 原因: 12 * 我们在每个线程中都创建了新的资源:Student s = new Student(); 13 * 而我们要求的是:设置和获取线程的资源应该是同一个。 14 * 如何解决呢? 15 * 如何把一个数据在多个类中共享使用呢? 16 * 思路1:在外界把这个数据创建出来,通过构造方法传递给其他的类。 17 * 18 * 19 * 问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题 20 * A:同一个数据出现多次 21 * B:姓名和年龄不匹配 22 * 原因: 23 * A:同一个数据出现多次 24 * CPU的一点点时间片的执行权,就足够你执行很多次。 25 * B:姓名和年龄不匹配 26 * 线程运行的随机性 27 * 线程安全问题: 28 * A:是否是多线程环境 是 29 * B:是否有共享数据 是 30 * C:是否有多条语句操作共享数据 是 31 * 解决方案: 32 * 加锁。 33 * 注意: 34 * A:不同种类的线程都要加锁。 35 * B:不同种类的线程加的锁必须是同一把。 36 * 37 * 38 * 问题3:虽然数据安全了,但是呢,一次输出一大片不好看,我就想依次的一次一个输出。 39 * 如何解决呢? 40 * 通过Java提供的等待唤醒机制解决。 41 * 等待唤醒: 42 * Object类中提供了三个方法: 43 * public final wait() 等待 44 * public final notify() 唤醒单个线程 45 * public final notifyAll() 唤醒所有线程 46 * 为什么这些方法不定义在Thread类中呢? 47 * 这些方法的调用必须通过锁对象调用,而我们刚才使用的是同步代码块,其锁对象是任意对象。 48 * 所以,这些方法定义在Object类中。 49 * 50 * 51 * 最终版代码中: 52 * 把Student的成员变量给私有了。 53 * 把设置和获取的操作给封装成了功能,并加了同步(同步方法)。 54 * 设置或获取线程类里面只需要调用方法即可。 55 */ 56 public class StudentDemo { 57 public static void main(String[] args) { 58 // 创建资源对象 59 Student s = new Student(); 60 61 // 创建设置和获取对象,并把资源对象作为构造方法的参数进行传递 62 SetThread st = new SetThread(s); 63 GetThread gt = new GetThread(s); 64 65 // 创建线程对象 66 Thread t1 = new Thread(st); 67 Thread t2 = new Thread(gt); 68 69 // 启动线程 70 t1.start(); 71 t2.start(); 72 } 73 }
-------------------------------------- (5)线程的状态转换图及常见的线程执行情况
(6)线程组 Java中使用ThreadGroup类来表示线程组类,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。 默认情况下,所有的线程都属于主线程组。
1 package cn.itcast_06; 2 3 public class MyRunnable implements Runnable { 4 5 @Override 6 public void run() { 7 for (int x = 0; x < 100; x++) { 8 System.out.println(Thread.currentThread().getName() + ":" + x); 9 } 10 } 11 12 }
1 package cn.itcast_06; 2 3 /* 4 * 线程组: 把多个线程组合到一起。 5 * 6 * 它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。 7 */ 8 public class ThreadGroupDemo { 9 public static void main(String[] args) { 10 // method1(); 11 12 // 我们如何修改线程所在的组呢? 13 // 创建一个线程组,在创建其他线程的时候,把其他线程的组指定为我们自己新建线程组 14 method2(); 15 } 16 17 private static void method2() { 18 // 线程组类ThreadGroup的构造方法:public ThreadGroup(String name) 19 ThreadGroup tg = new ThreadGroup("新的线程组"); 20 MyRunnable my = new MyRunnable(); 21 22 // 线程类Thread的构造方法:public Thread(ThreadGroup group, Runnable target, String name) 23 Thread t1 = new Thread(tg, my, "林青霞"); 24 Thread t2 = new Thread(tg, my, "刘意"); 25 26 System.out.println(t1.getThreadGroup().getName()); // 新的线程组 27 System.out.println(t2.getThreadGroup().getName()); // 新的线程组 28 29 // 通过线程组组名去设置后台线程,表示该组的线程都是后台线程(守护线程),不用一个一个线程去设置了,多简洁 30 tg.setDaemon(true); 31 } 32 33 private static void method1() { 34 MyRunnable my = new MyRunnable(); 35 Thread t1 = new Thread(my, "林青霞"); 36 Thread t2 = new Thread(my, "刘意"); 37 // 我不知道上面的线程属于那个线程组,我想知道,肿么办? 38 39 // 线程类Thread里面的方法:public final ThreadGroup getThreadGroup() 返回该线程所属的线程组 40 ThreadGroup tg1 = t1.getThreadGroup(); 41 ThreadGroup tg2 = t2.getThreadGroup(); 42 43 // 线程组类ThreadGroup里面的方法:public final String getName() 返回此线程组的名称 44 String name1 = tg1.getName(); 45 String name2 = tg2.getName(); 46 System.out.println(name1); // main 47 System.out.println(name2); // main 48 // 通过结果我们知道了:线程默认情况下属于main线程组 49 50 // 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组 51 System.out.println(Thread.currentThread().getThreadGroup().getName()); // 链式编程 52 } 53 }
(7)线程池 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。 而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。 线程池的特点:线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。 问题:一开始线程池里面设计多少个线程才合适呢? 答:需要我们进行压力测试、并发访问测试等。 在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池。
1 package cn.itcast_08; 2 3 public class MyRunnable implements Runnable { 4 5 @Override 6 public void run() { 7 for (int x = 0; x < 100; x++) { 8 System.out.println(Thread.currentThread().getName() + ":" + x); 9 } 10 } 11 12 }
1 package cn.itcast_08; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 6 /* 7 * 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。 8 * 而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。 9 * 10 * 线程池的好处:线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。 11 * 12 * 在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池。 13 * JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法 14 * public static ExecutorService newCachedThreadPool() 创建一个具有缓存功能的线程池 15 * public static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用的,具有固定线程数的线程池 16 * public static ExecutorService newSingleThreadExecutor() 创建一个只有单线程的线程池,相当于上个方法的参数是1 17 * 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。 18 * 19 * 如何实现线程池呢? 20 * A:创建一个线程池对象,明确在线程池中要创建几个线程对象。 21 * Executors类的方法: 22 * public static ExecutorService newFixedThreadPool(int nThreads) 23 * 该方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。 24 * B:这种线程池的线程可以执行Runnable对象或者Callable对象代表的线程。 25 * 即:做一个类实现Runnable接口。 26 * C:调用如下方法即可 27 * ExecutorService类的方法: 28 * Future<?> submit(Runnable task) 29 * <T> Future<T> submit(Callable<T> task) 30 * D:线程池中的线程使用完毕后,会自动回到线程池中成为空闲状态,等待下一个对象来使用。 31 * 但是呢,我就要结束线程池,可以吗? 32 * 可以。 33 */ 34 public class ExecutorsDemo { 35 public static void main(String[] args) { 36 // 创建一个线程池对象,明确在线程池中要创建几个线程对象 37 // Executors类的方法:public static ExecutorService newFixedThreadPool(int nThreads) 38 ExecutorService pool = Executors.newFixedThreadPool(2); 39 40 // 这种线程池的线程可以执行Runnable对象或者Callable对象代表的线程 41 pool.submit(new MyRunnable()); 42 pool.submit(new MyRunnable()); 43 44 // 结束线程池 45 pool.shutdown(); 46 } 47 }
-------------------------------------- (8)多线程实现的第三种方案:依赖于线程池而存在的 如何实现线程池呢? A:创建一个线程池对象,确定在线程池中要创建几个线程对象。 Executors类的方法: public static ExecutorService newFixedThreadPool(int nThreads) 该方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。 B:这种线程池的线程可以执行Runnable对象或者Callable对象代表的线程。 即:做一个类实现Runnable接口。 C:调用如下方法即可 ExecutorService类的方法: Future<?> submit(Runnable task) <T> Future<T> submit(Callable<T> task) D:线程池中的线程使用完毕后,会自动回到线程池中成为空闲状态,等待下一个对象来使用。 但是呢,我就要结束线程池,可以吗? 可以。 好处: 可以有返回值。 可以抛出异常。 弊端: 代码比较复杂,而且依赖于线程池而存在的,所以一般不用它。
1 package cn.itcast_09; 2 3 import java.util.concurrent.Callable; 4 5 // Callable:是带泛型的接口。 6 // 这里指定的泛型其实是call()方法的返回值类型。 7 public class MyCallable implements Callable { 8 9 @Override 10 public Object call() throws Exception { 11 for (int x = 0; x < 100; x++) { 12 System.out.println(Thread.currentThread().getName() + ":" + x); 13 } 14 return null; 15 } 16 17 }
1 package cn.itcast_09; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 6 /* 7 * 多线程实现的方式3:依赖于线程池而存在的 8 * 9 * 如何实现线程池呢? 10 * A:创建一个线程池对象,明确在线程池中要创建几个线程对象。 11 * Executors类的方法: 12 * public static ExecutorService newFixedThreadPool(int nThreads) 13 * 该方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。 14 * B:这种线程池的线程可以执行Runnable对象或者Callable对象代表的线程。 15 * 即:做一个类实现Runnable接口。 16 * C:调用如下方法即可 17 * ExecutorService类的方法: 18 * Future<?> submit(Runnable task) 19 * <T> Future<T> submit(Callable<T> task) 20 * D:线程池中的线程使用完毕后,会自动回到线程池中成为空闲状态,等待下一个对象来使用。 21 * 但是呢,我就要结束线程池,可以吗? 22 * 可以。 23 */ 24 public class CallableDemo { 25 public static void main(String[] args) { 26 // // 创建一个线程池对象,明确在线程池中要创建几个线程对象 27 ExecutorService pool = Executors.newFixedThreadPool(2); 28 29 // 这种线程池的线程可以执行Runnable对象或者Callable对象代表的线程 30 pool.submit(new MyCallable()); 31 pool.submit(new MyCallable()); 32 33 // 结束线程池 34 pool.shutdown(); 35 } 36 }
线程求和案例:用多线程实现的方式3
1 package cn.itcast_10; 2 3 import java.util.concurrent.Callable; 4 5 /* 6 * 线程求和案例 7 */ 8 public class MyCallable implements Callable<Integer> { 9 10 private int number; 11 12 public MyCallable(int number) { 13 this.number = number; 14 } 15 16 @Override 17 public Integer call() throws Exception { 18 int sum = 0; 19 for (int x = 1; x <= number; x++) { 20 sum += x; 21 } 22 return sum; 23 } 24 25 }
1 package cn.itcast_10; 2 3 import java.util.concurrent.ExecutionException; 4 import java.util.concurrent.ExecutorService; 5 import java.util.concurrent.Executors; 6 import java.util.concurrent.Future; 7 8 /* 9 * 多线程实现的方式3:依赖于线程池而存在的 10 * 11 * 如何实现线程池呢? 12 * A:创建一个线程池对象,明确在线程池中要创建几个线程对象。 13 * Executors类的方法: 14 * public static ExecutorService newFixedThreadPool(int nThreads) 15 * 该方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。 16 * B:这种线程池的线程可以执行Runnable对象或者Callable对象代表的线程。 17 * 即:做一个类实现Runnable接口。 18 * C:调用如下方法即可 19 * ExecutorService类的方法: 20 * Future<?> submit(Runnable task) 21 * <T> Future<T> submit(Callable<T> task) 22 * D:线程池中的线程使用完毕后,会自动回到线程池中成为空闲状态,等待下一个对象来使用。 23 * 但是呢,我就要结束线程池,可以吗? 24 * 可以。 25 */ 26 public class CallableDemo { 27 public static void main(String[] args) throws InterruptedException, ExecutionException { 28 // 创建一个线程池对象,明确在线程池中要创建几个线程对象 29 ExecutorService pool = Executors.newFixedThreadPool(2); 30 31 // 这种线程池的线程可以执行Runnable对象或者Callable对象代表的线程 32 Future<Integer> f1 = pool.submit(new MyCallable(100)); 33 Future<Integer> f2 = pool.submit(new MyCallable(200)); 34 35 // Future接口的方法:V get() 36 Integer i1 = f1.get(); 37 Integer i2 = f2.get(); 38 39 System.out.println(i1); 40 System.out.println(i2); 41 42 // 结束线程池 43 pool.shutdown(); 44 } 45 }
-------------------------------------- (9)匿名内部类方式使用多线程 在开发中,为了方便使用线程,需要随手开线程,最简单的做法就是采用匿名内部类方式使用多线程。 匿名内部类的格式: new 类名或者接口名() { 重写方法; } 本质:是该类的子类对象或者该接口的实现类对象。 new Thread() {代码...}.start(); new Thread(new Runnable() {代码...}) {}.start(); 示例代码如下:
1 package cn.itcast_11; 2 3 /* 4 * 匿名内部类的格式: 5 * new 类名或者接口名() { 6 * 重写方法; 7 * } 8 * 9 * 本质:是该类的子类对象或者该接口的实现类对象。 10 */ 11 public class ThreadDemo { 12 public static void main(String[] args) { 13 // 继承Thread类来实现多线程 14 new Thread() { 15 @Override 16 public void run() { 17 for (int x = 0; x < 100; x++) { 18 System.out.println(Thread.currentThread().getName() + ":" + x); 19 } 20 } 21 }.start(); 22 23 // 实现Runnable接口来实现多线程 24 new Thread(new Runnable() { 25 @Override 26 public void run() { 27 for (int x = 0; x < 100; x++) { 28 System.out.println(Thread.currentThread().getName() + ":" + x); 29 } 30 } 31 }) { 32 }.start(); 33 34 // 面试题 35 // 到底执行的是Thread类的子类对象的run(),还是执行的是Runnable接口的实现类对象的run()呢? 答:是Thread类的子类对象的run() world 36 new Thread(new Runnable() { 37 @Override 38 public void run() { 39 for (int x = 0; x < 100; x++) { 40 System.out.println("hello" + ":" + x); 41 } 42 } 43 }) { 44 @Override 45 public void run() { 46 for (int x = 0; x < 100; x++) { 47 System.out.println("world" + ":" + x); 48 } 49 } 50 }.start(); 51 52 } 53 }
(10)定时器 定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能。(在java.util包下) A:Timer(定时器类) public Timer() public void schedule(TimerTask?task, long?delay) public void schedule(TimerTask task, long delay, long period) B:TimerTask(任务抽象类) public abstract void run() public boolean cancel() C:在实际开发中 Quartz是一个完全由java编写的开源调度框架。 D:需求:在指定的时间删除我们的指定目录(演示:使用项目路径下的demo)
1 package cn.itcast_12; 2 3 import java.util.Timer; 4 import java.util.TimerTask; 5 6 /* 7 * 定时器:可以让我们在指定的时间做某件事情,还可以重复的做某件事情。 8 * 9 * 依赖Timer和TimerTask这两个类: 10 * Timer(定时器类) 11 * public Timer() 12 * public void schedule(TimerTask task, long delay) 在指定延迟后执行指定的任务 13 * public void schedule(TimerTask task, long delay, long period) 14 * public void cancel() 15 * 16 * TimerTask(任务抽象类) 17 */ 18 public class TimerDemo { 19 public static void main(String[] args) { 20 // 创建定时器对象 21 Timer t = new Timer(); 22 23 // 3秒后执行爆炸任务 24 // t.schedule(new MyTask(), 3000); 25 26 // 3秒后执行爆炸任务,并结束任务 27 t.schedule(new MyTask(t), 3000); 28 } 29 } 30 31 // 做一个任务 32 class MyTask extends TimerTask { 33 34 private Timer t; 35 36 public MyTask(){} 37 38 public MyTask(Timer t){ 39 this.t = t; 40 } 41 42 @Override 43 public void run() { 44 System.out.println("beng,爆炸了"); 45 t.cancel(); // 结束任务 46 } 47 48 }
1 package cn.itcast_12; 2 3 import java.util.Timer; 4 import java.util.TimerTask; 5 6 /* 7 * 定时器:可以让我们在指定的时间做某件事情,还可以重复的做某件事情。 8 * 9 * 依赖Timer和TimerTask这两个类: 10 * Timer(定时器类) 11 * public Timer() 12 * public void schedule(TimerTask task, long delay) 在指定延迟后执行指定的任务 13 * public void schedule(TimerTask task, long delay, long period) 14 * public void cancel() 15 * 16 * TimerTask(任务抽象类) 17 */ 18 public class TimerDemo2 { 19 public static void main(String[] args) { 20 // 创建定时器对象 21 Timer t = new Timer(); 22 23 // 3秒后执行爆炸任务 24 // t.schedule(new MyTask(), 3000); 25 26 // 3秒后执行爆炸任务第一次,如果不成功,每隔2秒再继续炸 27 t.schedule(new MyTask2(), 3000, 2000); 28 } 29 } 30 31 // 做一个任务 32 class MyTask2 extends TimerTask { 33 34 @Override 35 public void run() { 36 System.out.println("beng,爆炸了"); 37 } 38 39 }
1 package cn.itcast_12; 2 3 import java.io.File; 4 import java.text.ParseException; 5 import java.text.SimpleDateFormat; 6 import java.util.Date; 7 import java.util.Timer; 8 import java.util.TimerTask; 9 10 /* 11 * 需求:在指定的时间删除我们的指定目录(演示:使用项目路径下的demo) 12 */ 13 public class TimerTest { 14 public static void main(String[] args) throws ParseException { 15 Timer t = new Timer(); 16 17 String s = "2014-11-27 15:45:00"; 18 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 19 Date d = sdf.parse(s); 20 21 t.schedule(new DeleteFolder(), d); 22 } 23 } 24 25 26 class DeleteFolder extends TimerTask { 27 28 @Override 29 public void run() { 30 File srcFolder = new File("demo"); 31 deleteFolder(srcFolder); 32 } 33 34 // 递归删除目录 35 public void deleteFolder(File srcFolder) { 36 File[] fileArray = srcFolder.listFiles(); 37 if (fileArray != null) { 38 for (File file : fileArray) { 39 if (file.isDirectory()) { 40 deleteFolder(file); 41 } else { 42 System.out.println(file.getName() + ":" + file.delete()); 43 } 44 } 45 System.out.println(srcFolder.getName() + ":" + srcFolder.delete()); 46 } 47 } 48 49 }
-------------------------------------- (11)多线程常见的面试题 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:线程的生命周期。 新建-->就绪-->运行-->死亡 新建-->就绪-->运行-->阻塞-->就绪-->运行-->死亡 建议:画图解释。 ----------------------------------------------------------------------------- 2:设计模式(理解) (1)面向对象思想的设计原则的概述 在实际的开发中,我们要想更深入的了解面向对象思想,就必须熟悉前人总结过的面向对象思想的设计原则。 单一职责原则 核心思想:其实就是开发人员经常说的“高内聚”(自己能做的,不麻烦别人)。 也就是说:每个类应该只有一个职责,对外只能提供一种功能,而引起类变化的原因应该只有一个。 在设计模式中,所有的设计模式都遵循这一原则。 开闭原则 核心思想:一个对象对扩展开放,对修改关闭。 其实开闭原则的意思就是:对类的改动是通过增加代码进行的,而不是修改现有代码。 也就是说:软件开发人员一旦写出了可以运行的代码,就不应该去改动它,而是要保证它能一直运行下去,如何能够做到这一点呢? 这就需要借助于抽象和多态,即把可能变化的内容抽象出来,从而使抽象的部分是相对稳定的,而具体的实现则是可以改变和扩展的。 里氏替换原则 核心思想:在任何父类出现的地方都可以用它的子类来替代。 也就是说:同一个继承体系中的对象应该有共同的行为特征。 依赖注入原则(Spring框架) 核心思想:要依赖于抽象,不要依赖于具体实现。 也就是说:在应用程序中,所有的类如果使用或依赖于其他的类,则应该依赖这些其他类的抽象类,而不是这些其他类的具体类。 为了实现这一原则,就要求我们在编程的时候针对抽象类或者接口编程,而不是针对具体实现编程。 接口分离原则 核心思想:不应该强迫程序依赖它们不需要使用的方法。 也就是说:一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一个接口中。 迪米特原则 核心思想:一个对象应当对其他对象尽可能少的了解。即“低耦合”(不要牵一发而动全身) 也就是说:降低各个对象之间的耦合,提高系统的可维护性。在模块之间应该只通过接口编程,而不理会模块的内部工作原理,它可以使各个模块耦合度降到最低,促进软件的复用。 所有的原则都是为了提高程序的可维护性、可扩展性、可复用性。 -------------------------------------- (2)设计模式 A:设计模式的概述(设计模式是经验的总结) 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 设计模式不是一种方法和技术,而是一种思想。 设计模式和具体的语言无关,学习设计模式就是要建立面向对象的思想,尽可能的面向接口编程,低耦合,高内聚,使设计的程序可复用。 学习设计模式能够促进对面向对象思想的理解,反之亦然,它们相辅相成。 B:设计模式的几个要素 名字:必须有一个简单、有意义的名字。 问题:描述在何时使用模式。 解决方案:描述设计的组成部分以及如何解决问题。 效果:描述模式的效果以及优缺点。 C:设计模式的分类 创建型模式 对象的创建 结构型模式 对象的组成(结构) 行为型模式 对象的行为 创建型模式:简单工厂模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式、单例模式。(6个) 结构型模式:外观模式、适配器模式、代理模式、装饰模式、桥接模式、组合模式、享元模式。(7个) 行为型模式:模版方法模式、观察者模式、状态模式、职责链模式、命令模式、访问者模式、策略模式、备忘录模式、迭代器模式、解释器模式。(10个) -------------------------------------- (3)常见的设计模式 A:简单工厂模式(单接口中用的多) 简单工厂模式的概述 又叫静态工厂方法模式,它定义一个具体的工厂类负责创建一些类的实例。 优点 创建对象的工作其实是比较麻烦的,有了简单工厂后,客户端不需要再负责对象的创建,从而明确了各个类的职责。 缺点 这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护。 示例代码如下:
1 package cn.itcast_01; 2 3 public abstract class Animal { 4 public abstract void eat(); 5 }
1 package cn.itcast_01; 2 3 public class Dog extends Animal { 4 5 @Override 6 public void eat() { 7 System.out.println("狗吃肉"); 8 } 9 10 }
1 package cn.itcast_01; 2 3 public class Cat extends Animal { 4 5 @Override 6 public void eat() { 7 System.out.println("猫吃鱼"); 8 } 9 10 }
1 package cn.itcast_01; 2 3 public class AnimalFactory { 4 5 private AnimalFactory() { 6 } 7 8 public static Animal createAnimal(String type) { 9 if ("dog".equals(type)) { 10 return new Dog(); 11 } else if ("cat".equals(type)) { 12 return new Cat(); 13 } else { 14 return null; 15 } 16 } 17 18 /* 19 public static Dog createDog() { 20 return new Dog(); 21 } 22 23 public static Cat createCat() { 24 return new Cat(); 25 } 26 */ 27 }
1 package cn.itcast_01; 2 3 public class AnimalDemo { 4 public static void main(String[] args) { 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 // 我要造只狗 21 Animal a = AnimalFactory.createAnimal("dog"); 22 // 记住:用任何一个对象之前先判断该对象是否为空 23 if (a != null) { 24 a.eat(); 25 } else { 26 System.out.println("对不起,暂时不提供这种动物"); 27 } 28 29 // 我要造只猫 30 a = AnimalFactory.createAnimal("cat"); 31 // 记住:用任何一个对象之前先判断该对象是否为空 32 if (a != null) { 33 a.eat(); 34 } else { 35 System.out.println("对不起,暂时不提供这种动物"); 36 } 37 38 // NullPointerException 39 a = AnimalFactory.createAnimal("pig"); 40 // 记住:用任何一个对象之前先判断该对象是否为空 41 if (a != null) { 42 a.eat(); 43 } else { 44 System.out.println("对不起,暂时不提供这种动物"); 45 } 46 } 47 }
B:工厂方法模式(多接口中用的多)
工厂方法模式的概述
工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
优点
客户端不需要再负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性。
缺点
需要额外的编写代码,增加了工作量。
示例代码如下:
1 package cn.itcast_02; 2 3 public abstract class Animal { 4 public abstract void eat(); 5 }
1 package cn.itcast_02; 2 3 public class Dog extends Animal { 4 5 @Override 6 public void eat() { 7 System.out.println("狗吃肉"); 8 } 9 10 }
1 package cn.itcast_02; 2 3 public class Cat extends Animal { 4 5 @Override 6 public void eat() { 7 System.out.println("猫吃鱼"); 8 } 9 10 }
1 package cn.itcast_02; 2 3 public interface AnimalFactory { 4 public abstract Animal createAnimal(); 5 }
1 package cn.itcast_02; 2 3 public class DogFactory implements AnimalFactory { 4 5 @Override 6 public Animal createAnimal() { 7 return new Dog(); 8 } 9 10 }
1 package cn.itcast_02; 2 3 public class CatFactory implements AnimalFactory { 4 5 @Override 6 public Animal createAnimal() { 7 return new Cat(); 8 } 9 10 }
1 package cn.itcast_02; 2 3 public class AnimalDemo { 4 public static void main(String[] args) { 5 // 需求:我要造只狗 6 AnimalFactory 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 } 19 }
C:单例设计模式(多线程中用的多)
单例设计模式的概述
单例模式就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供。
优点
在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
缺点
没有抽象层,因此扩展很难。职责过重,在一定程序上违背了单一职责。
a:饿汉式:类一加载就创建对象了。
b:懒汉式:用的时候,才去创建对象。
开发中:饿汉式(是不会出现问题的单例模式)
面试中:懒汉式(可能会出现问题的单例模式)
出现什么问题呢?
A:懒加载思想(延迟加载思想)
B:出现线程安全问题
a:是否是多线程环境 是
b:是否有共享数据 是
c:是否有多条语句操作共享数据 是
示例代码如下:
饿汉式示例代码:
1 package cn.itcast_03; 2 3 public class Student { 4 5 // 构造私有,外界就不能造对象了 6 private Student() { 7 } 8 9 // 在成员变量位置自己创建一个对象 10 // 静态方法只能访问静态成员变量,所以成员变量加静态修饰 11 // 为了不让外界直接访问修改这个成员变量的值,所以该成员变量加private修饰 12 private static Student s = new Student(); 13 14 // 提供公共的访问方式,返回该对象。为了保证外界能够直接访问该方法,所以方法加静态修饰 15 public static Student getStudent() { 16 return s; 17 } 18 }
1 package cn.itcast_03; 2 3 /* 4 * 单例模式:就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供。 5 * 6 * 如何保证该类在内存中只有一个对象呢? 7 * A:把构造方法私有,外界就不能造对象了。 8 * 9 * 如何保证该类的对象的自动创建,并且对外提供呢? 10 * A:在成员变量位置自己创建一个对象。静态方法只能访问静态成员变量,所以成员变量加静态修饰。 11 * 为了不让外界直接访问修改这个成员变量的值,所以该成员变量加private修饰。 12 * B:通过一个公共的方法提供访问,返回该对象。为了保证外界能够直接访问该方法,所以方法加静态修饰。 13 */ 14 public class StudentDemo { 15 public static void main(String[] args) { 16 // Student s1 = new Student(); 17 // Student s2 = new Student(); 18 // System.out.println(s1 == s2); // false 19 20 // 通过单例如何得到对象呢?先注意一个问题:外界能直接访问修改这个成员变量的值 21 // Student.s = null; 22 // 23 // Student s1 = Student.getStudent(); 24 // Student s2 = Student.getStudent(); 25 // System.out.println(s1 == s2); // true 26 // 27 // System.out.println(s1); // null 28 // System.out.println(s2); // null 29 30 // 为了不让外界直接访问修改这个成员变量的值,所以该成员变量加private修饰。 31 // 通过单例如何得到对象呢? 32 Student s1 = Student.getStudent(); 33 Student s2 = Student.getStudent(); 34 System.out.println(s1 == s2); // true 35 36 System.out.println(s1); // cn.itcast_03.Student@3c679bde 37 System.out.println(s2); // cn.itcast_03.Student@3c679bde 38 39 } 40 }
懒汉式示例代码:
1 package cn.itcast_03; 2 3 /* 4 * 单例模式: 5 * 饿汉式:类一加载就创建对象了。 6 * 懒汉式:用的时候,才去创建对象。 7 * 8 * 面试题: 9 * 单例模式的思想是什么? 答:确保类在内存中只有一个对象。 10 * 请写一个代码体现。 11 * 12 * 开发中:饿汉式(是不会出现问题的单例模式) 13 * 面试中:懒汉式(可能会出现问题的单例模式) 14 * 15 * 出现什么问题呢? 16 * A:懒加载思想(延迟加载思想) 17 * B:出现线程安全问题 18 * a:是否是多线程环境 是 19 * b:是否有共享数据 是 20 * c:是否有多条语句操作共享数据 是 21 */ 22 public class Teacher { 23 private Teacher() { 24 } 25 26 private static Teacher t = null; 27 28 public static synchronized Teacher getTeacher() { 29 if (t == null) { 30 t = new Teacher(); 31 } 32 return t; 33 } 34 35 }
1 package cn.itcast_03; 2 3 public class TeacherDemo { 4 public static void main(String[] args) { 5 Teacher t1 = Teacher.getTeacher(); 6 Teacher t2 = Teacher.getTeacher(); 7 System.out.println(t1 == t2); // true 8 System.out.println(t1); // cn.itcast_03.Teacher@3c679bde 9 System.out.println(t2); // cn.itcast_03.Teacher@3c679bde 10 } 11 }
-------------------------------------- (4)Runtime类的概述和应用 A:Runtime类的概述 每个Java应用程序都有一个Runtime类实例,使应用程序能够与其运行的环境相连接。可以通过getRuntime()方法获取当前运行时对象。 应用程序不能创建自己的Runtime类实例。 B:Runtime类使用 public Process exec(String command) 在单独的进程中执行指定的字符串命令(该方法会抛出异常) JDK提供的一个单例模式应用的类Runtime类。 该类的对象可以调用dos命令。
示例代码如下:
1 package cn.itcast_04; 2 3 import java.io.IOException; 4 5 /* 6 * Runtime类:每 Java应用程序都有一个 Runtime类实例,使应用程序能够与其运行的环境相连接。可以通过getRuntime()方法获取当前运行时对象。 7 * public Process exec(String command) 在单独的进程中执行指定的字符串命令 8 * 该类的对象可以调用dos命令。 9 * 10 * JDK提供的一个单例模式应用的类Runtime类。 11 */ 12 public class RuntimeDemo { 13 public static void main(String[] args) throws IOException { 14 Runtime r = Runtime.getRuntime(); 15 // r.exec("winmine"); // 打开扫雷游戏 16 // r.exec("notepad"); 17 r.exec("calc"); 18 // r.exec("shutdown -s -t 10000"); // 定时10000s秒后关机 19 // r.exec("shutdown -a"); // 取消关机命令 20 } 21 } 22 23 /* 24 * Runtime类的源码小解析: 25 * 26 * class Runtime { 27 * private Runtime() {} 28 * private static Runtime currentRuntime = new Runtime(); // 单例设计模式之饿汉式 29 * public static Runtime getRuntime() { 30 * return currentRuntime; 31 * } 32 * } 33 */
Runtime类的源码小解析: class Runtime { private Runtime() {} private static Runtime currentRuntime = new Runtime(); // 单例设计模式之饿汉式 public static Runtime getRuntime() { return currentRuntime; } } =============================================================================