一 设计模式之 单例设计模式
1.解释
保证类在内存中只有一个对象
2.三种创建方式
(1)饿汉式
//饿汉式,一上来就创建本类对象
class Singleton{
//1.私有构造方法,其他的类不能访问构造器了
private Singleton() {}
//2.创建本类对象,私有成员变量
private static Singleton s = new Singleton();
//3.提供公共的get访问方法
public static Singleton getInstance() {
return s;
}
}
(2)懒汉式(单例的延迟加载模式)
class Singleton1{
//1.私有构造方法,其他类无法访问构造器
private Singleton1() {}
//2.声明一个本类成员变量,不创建实例,私有成员变量
private static Singleton1 s1;
//3.提供公共访问方法,如果在内存中有本类的实例,直接返回,没有就创建一个
public static Singleton1 getInstance() {
if (s1 == null) {
s1 = new Singleton1();
}
return s1;
}
}
弊端
多线程的时候可能会创建多个实例对象
(3)第三种方式
class Singleton2{
private Singleton2() {}
private static final Singleton2 s = new Singleton2();
}
3.案例:使用了单例设计模式的类:Runtime
Runtime类使用了饿汉式单例设计模式
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
二 计时器:Timer类
1.Constructor
Timer();
2.Method
schedule(TimeTask task,Date time); //传入任务子类,触发的时间
schedule(TimerTask task, Date firstTime, long period); //传入任务子类,首次触发时间,触发间隔毫秒
3. TimeTask类
(1)功能
创建一个TimeTask的子类,然后重写run()方法
(2)e.g
class MyTimeTask extends TimerTask{
@Override
public void run() {
System.out.println("起床背单词!!!");
}
}
4. e.g
public static void main(String[] args) throws InterruptedException {
Timer t = new Timer();
t.schedule(new MyTimeTask(), new Date(119, 2, 14, 20, 9, 0));
while (true) { //每隔1秒钟打印一次时间
Thread.sleep(1000);
System.out.println(new Date());
}
}
三 线程间的通信
1.什么时候需要通信
需要让多个线程间进行有规律的执行的时候
等待唤醒 机制
1.Object类中的方法
//Object类的方法
void notify(); //唤醒在此对象监视器上等待的单个线程
void notifyAll(); //唤醒在此对象监视器上等待的所有线程
void wait(); //在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待。
//throws InterruptedException
2. e.g 两个线程的 唤醒 等待
public class Demo1_Notify {
public static void main(String[] args) {
Printer p = new Printer();
new Thread() {
public void run() {
while (true) {
try {
p.print1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while (true) {
try {
p.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
class Printer {
int flag = 1;
public void print1() throws InterruptedException {
synchronized (this) {
if (flag != 1) {
this.wait();
}
System.out.print("钱");
System.out.print("惊");
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("黄");
System.out.print("麒");
System.out.print("宁");
System.out.print("\r\n");
flag = 1;
this.notify();
}
}
}
3. e.g 三个及以上线程的 唤醒 等待
(1)分析
- wait()方法是从哪里停止,再从哪里开始,所以当线程被唤醒的时候,执行的语句是从wait()的后面开始的,并不会重新执行本线程的语句;
- 若要让等待的线程被唤醒时重新判断flag的值,将if()语句改为while(),只有当flag==当前线程要求的flag时才会跳出循环;
- 但是当notify()随机唤醒的线程均不是flag能通过的线程时(特殊情况),所有线程都处于等待状态,程序无法继续运行了;
- 所以将notify()方法改为notifyAll()方法,唤醒所有的线程,进行flag的比较。
(2)代码
public class Demo2_NotifyAll {
public static void main(String[] args) {
Printer2 p = new Printer2();
//这里省略启动三个线程分别执行print1、2、3方法的过程
}
class Printer2 {
int flag = 1;
public void print1() throws InterruptedException {
synchronized (this) {
while(flag != 1) {
this.wait(); //当前线程等待
}
System.out.print("钱");
System.out.print("惊");
System.out.print("涛");
System.out.print("\r\n");
flag = 2;
this.notify(); //随机唤醒单个等待的线程
}
}
public void print2() throws InterruptedException {
synchronized (this) {
while (flag != 2) {
this.wait();
}
System.out.print("黄");
System.out.print("麒");
System.out.print("宁");
System.out.print("\r\n");
flag = 3;
this.notify();
}
}
public void print3() throws InterruptedException {
synchronized (this) {
while (flag != 3) {
this.wait();
}
System.out.print("爱");
System.out.print("的");
System.out.print("魔");
System.out.print("丽");
System.out.print("转");
System.out.print("圈");
System.out.print("圈");
System.out.print("\r\n");
flag = 1;
this.notify();
}
}
}
4. 线程间通信的注意事项
- 同步代码块中的同步锁对象用来调用wait()和notify()方法。(用this当作同步锁,就用this来调用线程等待和唤醒的方法);
- wait()和notify()方法定义在Object中,因为锁对象可以是任意对象;
- sleep()&wait()方法的区别:
- sleep()方法必须传入参数,参数就是时间,时间到了,自动醒来;
- wait()方法可以传入参数,也可以不传入参数,传入参数就是再参数的时间结束后等待,不传入就是直接等待。
- sleep()方法在同步函数函数或同步代码块中不释放锁;
- wait()方法在同步函数或同步代码块中释放锁。
5. JDK1.5新特性:互斥锁(ReentrantLock)
(1)同步
使用ReentrantLock类中的lock()&unlock()方法进行同步
(2)Method
void lock();
void unlock();
Condition newCondition();
(3)Condition接口
Method
void await();
void signal();
void signalAll();
(4)改写之前的方法
class Printer3 {
private ReentrantLock r = new ReentrantLock();
private Condition c1 = r.newCondition();
private Condition c2 = r.newCondition();
private Condition c3 = r.newCondition();
int flag = 1;
public void print1() throws InterruptedException {
r.lock();
if(flag != 1) {
c1.await(); //当前线程等待
}
System.out.print("钱");
System.out.print("惊");
System.out.print("涛");
System.out.print("\r\n");
flag = 2;
c2.signal(); //随机唤醒单个等待的线程
r.unlock();
}
public void print2() throws InterruptedException {
r.lock();
if (flag != 2) {
c2.await();
}
System.out.print("黄");
System.out.print("麒");
System.out.print("宁");
System.out.print("\r\n");
flag = 3;
c3.signal();
r.unlock();
}
public void print3() throws InterruptedException {
r.lock();
if (flag != 3) {
c3.await();
}
System.out.print("爱");
System.out.print("的");
System.out.print("魔");
System.out.print("力");
System.out.print("转");
System.out.print("圈");
System.out.print("圈");
System.out.print("\r\n");
flag = 1;
c1.signal();
r.unlock();
}
}
四 线程组(ThreadGroup类)
(1)概述
- ThreadGroup表示线程组,可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制
- 默认情况下,所有的线程都属于主线程组(main)
(2)Constructor&Method
在Thread的构造中,有添加线程组的构造器
//target为Thread的运行对象,name为Thread的名称,group是该线程所在的线程组
public Thread(ThreadGroup group, Runnable target,String name);
public Thread(ThreadGroup group, Runnable target);
public Thread(ThreadpGroup group, String name);
在Thread的方法中,获取线程组的方法
public ThreadGroup getThreadGroup();
ThreadGroup中的方法
void setDaemon(boolean daemon); //设置线程组是否为守护线程
String getName();
五 线程的5中状态
- 新建,Thread t1 = new Thread(xxx);
- 就绪,t1.start();notify();
- 运行,得到cpu执行权,调用t1.run();
- 阻塞, sleep(),wait()等,唤醒后重新进入就绪状态
- 死亡,run()结束
六 线程池(Executors类)
(1)概述
线程的启动成本很高,涉及到操作系统的交互。当程序中需要创建大量的生存周期很小的线程时,应使用线程池。线程池中的线程在结束后不会立刻死亡,而是返回线程池中处于空闲状态,等待再次被调用。
(2)Method
public static ExecutorService newFixedThreadPool(int nThreads); //创建线程池服务器,池中放nThread条线程
public static ExcutorService newSingleThreadExecutor(); //创建一个只放一条线程的线程池
(3)ExecutorService
Method
public Future<T> submit(Runnable task); //提交一个Runnable任务用于执行,返回一个该任务的Future。
public viud shutDown(); //结束线程池
七 多线程实现的第三种方式(Callable)
除了extends Thread和implements Runnable之外。
e.g
public class Demo06_Callable {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(1000));
System.out.println(f1.get());
System.out.println(f2.get());
pool.shutdown();
}
}
class MyCallable implements Callable<Integer>{
private int num;
public MyCallable(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 1; i <= num ; i++) {
sum += i;
}
return sum;
}
}
八 设计模式 工厂模式
(1)(静态工厂方法模式) 概述
定义一个具体的工厂类来负责创建一些类的实例,需要创建实例时直接调用工厂类的静态方法创建即可。
- 优点:客户端不需要再负责对象的创建,从而明确了各个类的职责;
- 弊端:这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护。
(2)(工厂方法模式)概述
① 方法
- 创建工厂接口 interface Factory,定义抽象方法
- 创建猫类狗类
- 创建猫狗工厂类实现工厂接口,重写抽象方法
- 在测试类中先实现猫狗工厂对象,通过工厂对象创建猫狗实例对象
② 优缺点
- 优点
- 客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性
- 缺点
- 需要额外的编写代码,增加了工作量