Day25
- 多线程(单例设计模式)
(1)单例设计模式:保证类在内存中只有一个对象。
(2)如何保证类在内存中只有一个对象呢?
控制类的创建,不让其他类来创建本类的对象。Private
在本类中定义一个本类的对象。Singleton s;
提供公共的访问方式。public static Singleton getInstance(){return s}
单例写法两种:
饿汉式和懒汉式的区别:
1:饿汉式是空间转换时间,懒汉式是时间换空间。
2:在多线程访问时,饿汉式不会创建多个对象,懒汉式有可能创建多个对象
(1) 饿汉式 开发用这种方式
public class thread_Demo1_Singleton {
/*
单例设计模式:保证类在内存中只有一个对象。
*/
public static void main(String[] args) {
// Singlenton s1 = new Singlenton();
/*Singlenton s1 = Singlenton.s; //成员变量被私有,不能通过类名.调用
Singlenton.s = null;
Singlenton s2 = Singlenton.s;
System.out.println(s1 == s2);*/
Singlenton s1 = Singlenton.getInstance();
Singlenton s2 = Singlenton.getInstance();
System.out.println(s1 == s2);
}
}
/*
饿汉式
*/
class Singlenton{
//1:私有构造方法,其他类不能访问该构造方法了
private Singlenton(){}
//2:创建本类对象
private static Singlenton s = new Singlenton();
//3:对外提供公共的访问方法
public static Singlenton getInstance(){ //获取实例
return s;
}
}
(2) 懒汉式 ,单例的延迟加载模式,面试写这种方式 多线程的问题?
class Singlenton {
//1:私有构造方法,其他类不能访问该构造方法了
private Singlenton(){}
//2:创建本类对象
private static Singlenton s = new Singlenton();
//3:对外提供公共的访问方法
public static Singlenton getInstance(){
if(s == null) {
s = new Singlenton();
}
return s;
}
}
(3) 第三种格式
class Singlenton {
//1:私有构造方法,其他类不能访问该构造方法了
private Singlenton() {
}
//2:创建本类对象
public static final Singlenton s = new Singlenton();
}
- 多线程(Runtime类)
Runtime类式一个单例类
public class Demo2_RunTime {
public static void main(String[] args) throws IOException {
Runtime r = Runtime.getRuntime(); //获取运行时对象
//r.exec("shutdown -s -t 300"); //命令电脑五分钟后关闭
r.exec("shutdown -a"); //取消关机命令
}
}
3. 多线程(Timer)
Timer类:计时器
public class Demo3_Timer {
public static void main(String[] args) throws InterruptedException {
Timer t = new Timer();
//在指定时间安排指定任务
//第一个参数时安排的任务
//第二个参数时执行的时间;第三个参数时过多长时间再重复运行
t.schedule(new MyTimerTask(),new Date(120,3,20,22,17),3000);
while (true){
Thread.sleep(1000);
System.out.println(new Date());
}
}
}
class MyTimerTask extends TimerTask {
@Override
public void run() {
System.out.println("起床背单词");
}
}
- 多线程(两个线程间的通信)
(1) 什么时候需要通信
多个线程并发执行时,在默认情况下cpu时随机切换线程的。
如果我们希望他们有规律的执行,就可以使用通信,例如每个线程执行一次打印。
(2) 怎么通信
如果希望线程等待,就调用wait()
如果希望唤醒等待的线程,就调用notify();
这两个方法必须在同步代码中执行,并且使用同步锁对象来调用。
public class Demo1_Notify {
//等待唤醒机制
public static void main(String[] args) {
final 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 {
private 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("序");
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("客");
System.out.print("\r\n");
flag = 1;
this.notify();
}
}
}
- 多线程(两个或三个以上间的线程通信)
多个线程通信的问题
(1) notify()方法是随机唤醒一个线程
(2) notifyAll()方法是唤醒所有线程
(3) JDK5之前无法唤醒指定的一个线程
(4) 如果多个线程之间通信,需要使用notifyAll通知所有线程,用while来反复判断条件。
(5) 注意事项:
1:在同步代码块中,用那个对象锁,就用那个对象调用wait方法
2:为什么wait方法和notify方法定义在Object类中?
因为锁对象可以是任意对象,Object是所有的类的基类,所以wait方法需要定义在Object这个类中
3:sleep方法和wait方法的区别?
1)sleep方法必须传入参数,参数就是时间,时间到了就自动醒来。
wait方法可以传入参数,也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待。
2)sleep方法在同步函数或同步代码块中,不释放锁,睡着了也抱着锁睡。
wait方法在同步函数或同步代码块中,释放锁。
public class Demo2_NotifyAll {
public static void main(String[] args) {
final Printer2 p = new Printer2();
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();
new Thread(){
public void run(){
while (true){
try {
p.print3();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
/*
1:在同步代码块中,用那个对象锁,就用那个对象调用wait方法
2:为什么wait方法和notify方法定义在Object类中?
因为锁对象可以是任意对象,Object是所有的类的基类,所以wait方法需要定义在Object这个类中
3:sleep方法和wait方法的区别?
1)sleep方法必须传入参数,参数就是时间,时间到了就自动醒来。
wait方法可以传入参数,也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待。
2)sleep方法在同步函数或同步代码块中,不释放锁,睡着了也抱着锁睡。
wait方法在同步函数或同步代码块中,释放锁。
*/
class Printer2 {
private 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("序");
System.out.print("员");
System.out.print("\r\n");
flag = 2;
// this.notify(); //随机唤醒单个等待线程
this.notifyAll();
}
}
public void print2() throws InterruptedException {
synchronized (this) {
while (flag != 2){
this.wait(); //线程2在此等待
}
System.out.print("传");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.print("\r\n");
flag = 3;
// this.notify(); 不适合
this.notifyAll();
}
}
public void print3() throws InterruptedException {
synchronized (this) {
while (flag != 3){
this.wait(); 线程3在此等待,if语句在哪里等待,就在那里起来
//while循环是循环判断,每次都会判断标记
}
System.out.print("i");
System.out.print("t");
System.out.print("c");
System.out.print("a");
System.out.print("s");
System.out.print("t");
System.out.print("\r\n");
flag = 1;
//this.notify();
this.notifyAll();
}
}
}
- 多线程(JDK1.5的新特性互斥锁)
(1) 同步
使用ReentrantLock类的lock()和unlock()方法进行同步
(2) 通信
使用ReentrantLock类的newCondition()方法可以获取Condition对象
需要等待的时候用Condition的await()方法,唤醒的时候用signal()方法
不同的线程使用不同的Condition,这样就能区分唤醒时候找那个线程了
public class Demo3_ReentratLock {
public static void main(String[] args) {
final Printer3 p = new Printer3();
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();
new Thread(){
public void run(){
while (true){
try {
p.print3();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
class Printer3 {
private ReentrantLock r = new ReentrantLock();
private Condition c1 = r.newCondition();
private Condition c2 = r.newCondition();
private Condition c3 = r.newCondition();
private 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("序");
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(); //线程2在此等待
}
System.out.print("传");
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(); 线程3在此等待,if语句在哪里等待,就在那里起来
//while循环是循环判断,每次都会判断标记
}
System.out.print("i");
System.out.print("t");
System.out.print("c");
System.out.print("a");
System.out.print("s");
System.out.print("t");
System.out.print("\r\n");
flag = 1;
c3.signal();
r.unlock();
}
}
- 多线程(线程组的概述和使用)
(1) 线程组概述
java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java运行程序直接对线程组进行控制。
默认情况下,所有的线程都属于主线程组:
public final ThreadGroup getThreadGroup()//通过线程对象获取他所属于的组
public final String getName()//通过线程组对象获取他组的名字
我们也可以给线程设置分组
ThreadGroup(String name)创建线程组对象并给其赋值名字。
创建线程对象。
Thread(ThreadGroup?group,Runnable?target,String?name)。
设置整组的优先级或者守护线程。
(2) 案例演示
线程组的使用,默认是主线程组
public class Demo4_ThreadGroup {
public static void main(String[] args) {
//demo1();
ThreadGroup tg = new ThreadGroup("我是一个新的线程组"); //创建新的线程组
MyRunnable mr = new MyRunnable(); //创建Runnable()的子类对象
Thread t1 = new Thread(tg,mr,"张三"); //将线程t1放在组中
Thread t2 = new Thread(tg,mr,"李四");//将线程t2放在组中
System.out.println(t1.getThreadGroup().getName()); //获取组名
System.out.println(t2.getThreadGroup().getName());
}
private static void demo1() {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr,"张三");
Thread t2 = new Thread(mr,"李四");
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
System.out.println(tg1.getName()); //默认是主线程
System.out.println(tg2.getName());
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for(int i = 0; i < 1000 ; i++){
System.out.println(Thread.currentThread().getName() + "..." + i);
}
}
}
- 多线程(线程的五种状态)
(1) 新建
(2) 就绪状态
(3) 阻塞
(4) 运行
(5) 死亡
- 多线程(线程池的概述和使用)
(1) 线程池的概述
程序启动一个新线程成本是比较高的,因为它涉及到要操作系统进行交互,而使用线程池也可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池,线程池里的每一个线性代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池。
(2) 内置线程池的使用概述
*JDK5新增了一个Executors工厂类产生线程池,有如下几个方法
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程,它提供了如下方法:
Future<?> submit(Runnable task)
Future submit(Callable task)
*使用步骤:
创建线程池对象
创建Runnable实例
提交Runnable实例
关闭线程池
(3) 案例演示
提交的是Runnable
public class Demo5_Executors {
/*
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
*/
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);//创建线程池
pool.submit(new MyRunnable()); //将线程放进池子里并执行
pool.submit(new MyRunnable());
pool.shutdown(); //关闭线程池
}
}
- 多线程(多线程程序实现的方式3)
提交的是Callable
public class Demo6_Callable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);//创建线程池
Future<Integer> f1 = pool.submit(new MyCallable(100)); //将线程放进池子里并执行
Future<Integer> f2 = pool.submit(new MyCallable(50));
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;
}
}
多线程程序实现的方式3的好处和弊端
好处:可以有返回值,可以抛出异常
弊端:代码比较复杂,所以一般不用
11. 设计模式(简单工厂模式概述和使用)
(1) 简单工厂模式概述
又叫静态工厂方法模式,它定义一个具体的工厂类负责创建一些类的实例
(2) 优点
客户端不需要在负责对象的创建,从而明确了各个类的职责
(3) 缺点
这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护。
(4) 案例
动物抽象类:public abstract Animal {public abstract void eat();}
具体狗类public class Dog extends Animal {}
具体猫类public class Cat extends Animal {}
开始,在测试类中每个具体的内容自己创建对象,但是,创建对象的工作如果比较麻烦,就需要有人专门做这个事情,所以就知道了一个专门的类来创建对象。
public class AnimalFactory {
/* public static Dog creatDog() {
return new Dog();
}
public static Cat createCat(){
return new Cat();
}*/
//发现方法需要定义很多,复用性太差
//改进
public static Animal creatAnimal(String name){
if("dog".equals(name)) {
return new Dog();
}else if("cat".equals(name)) {
return new Cat();
}else {
return null;
}
}
}
-
设计模式(工厂方法模式的概述和使用)
(1) 工厂方法模式概述
工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象创建工作由继承抽象工厂的具体类实现。
(2) 优点
客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期容易维护,增强系统的扩展性。
(3) 缺点
需要额外的编写代码,增加了工作量
(4) 案例演示 -
GUI(如何创建一个窗口并显示)
Graphical User Interface(图形用户接口)
public class Demo1_Frame {
public static void main(String[] args) {
Frame f = new Frame("我的第一个窗口");
f.setSize(400,600); //设置窗体大小
f.setLocation(300,50); //设置窗体位置
f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png"));
f.setVisible(true); //设置窗体可见
}
}
- GUI(布局管理器)
(1) FlowLayout(流式布局管理器)
从左到右的顺利排列。
Panel默认的布局管理器
(2) BorderLayout(边界布局管理器)
东、南、西、北、中
Frame默认布局管理器
(3) GridLayout(网格布局管理器)
规则的矩阵
(4) CardLayout(卡片布局管理器)
选项卡
(5) GridBagLayout(网络包布局管理器)
非规则的矩阵
public class Demo1_Frame {
public static void main(String[] args) {
Frame f = new Frame("我的第一个窗口");
f.setSize(400,600); //设置窗体大小
f.setLocation(300,50); //设置窗体位置
//f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png"));
Button b1 = new Button("按钮1");
f.add(b1);
f.setLayout(new FlowLayout()); //设置布局管理器
f.setVisible(true); //设置窗体可见
}
}
- GUI(窗体监听)
public class Demo1_Frame {
public static void main(String[] args) {
Frame f = new Frame("我的第一个窗口");
f.setSize(400,600); //设置窗体大小
f.setLocation(300,50); //设置窗体位置
//f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png"));
Button b1 = new Button("按钮1");
f.add(b1);
f.setLayout(new FlowLayout()); //设置布局管理器
f.addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
super.windowOpened(e);
}
});
f.setVisible(true); //设置窗体可见
}
}
- GUI(鼠标监听)
public class Demo1_Frame {
public static void main(String[] args) {
Frame f = new Frame("我的第一个窗口");
f.setSize(400,600); //设置窗体大小
f.setLocation(300,50); //设置窗体位置
//f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png"));
Button b1 = new Button("按钮1");
f.add(b1);
f.setLayout(new FlowLayout()); //设置布局管理器
f.addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
super.windowOpened(e);
}
}); //窗体监听
b1.addMouseListener(new MouseAdapter() {
/*@Override
public void mouseClicked(MouseEvent e) { //单击
System.exit(0);
}*/
public void mouseReleased(MouseEvent e) { //释放
System.exit(0);
}
});
f.setVisible(true); //设置窗体可见
}
}
- GUI(键盘监听和键盘事件)
public class Demo1_Frame {
public static void main(String[] args) {
Frame f = new Frame("我的第一个窗口");
f.setSize(400,600); //设置窗体大小
f.setLocation(300,50); //设置窗体位置
//f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png"));
Button b1 = new Button("按钮1");
f.add(b1);
f.setLayout(new FlowLayout()); //设置布局管理器
f.addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
super.windowOpened(e);
}
}); //窗体监听
b1.addMouseListener(new MouseAdapter() {
/*@Override
public void mouseClicked(MouseEvent e) { //单击
System.exit(0);
}*/
public void mouseReleased(MouseEvent e) { //释放
System.exit(0);
}
});
b1.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
// System.exit(0);
// System.out.println(e.getKeyCode());
// if (e.getKeyCode() == 32) {
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
System.exit(0);
}
}
});
f.setVisible(true); //设置窗体可见
}
}
- GUI(动作监听)
public class Demo1_Frame {
public static void main(String[] args) {
Frame f = new Frame("我的第一个窗口");
f.setSize(400,600); //设置窗体大小
f.setLocation(300,50); //设置窗体位置
//f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png"));
Button b1 = new Button("按钮1");
Button b2 = new Button("按钮2");
f.add(b1);
f.add(b2);
f.setLayout(new FlowLayout()); //设置布局管理器
f.addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
super.windowOpened(e);
}
}); //窗体监听
b1.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
// System.exit(0);
// System.out.println(e.getKeyCode());
// if (e.getKeyCode() == 32) {
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
System.exit(0);
}
}
});
b2.addActionListener(new ActionListener() { //添加动作监听,应用场景是暂停视频和播放视频
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
f.setVisible(true); //设置窗体可见
}
}
- 设计模式(适配器设计模式)
(1) 什么是适配器
在使用监听器的时候,需要定义一个类事件监听器接口。
通常接口中有多个方法,而程序中不一定所有的都用到,但又必须重写,这很繁琐。
适配器简化了这些操作,我们定义监听器时都要继承适配器,然后重写需要的方法即可。
(2) 适配器原理
适配器就是一个类,实现了监听器接口,所有抽象方法都重写了,但是方法全是空的。
适配器类需要定义成抽象的,因为创建该类对象,调用空方法没有意义。
目的就是为了简化程序员的操作,定义监听器时继承适配器,只重写需要的方法就可以了。
public static void main(String[] args) {
}
}
interface 和尚 {
public void 打坐();
public void 念经();
public void z撞钟();
public void 习武();
}
abstract class 天罡星 implements 和尚 { //声明成抽象的原因是不想让其他类创建本类对象,因为创建也没有意义
@Override
public void 打坐() {
}
@Override
public void 念经() {
}
@Override
public void z撞钟() {
}
@Override
public void 习武() {
}
}
class 鲁智深 extends 天罡星{
public void 习武(){
System.out.println("1");
System.out.println("2");
}
}
- GUI(需要知道的)
(1) 事件处理
事件:用户的一个操作
事件源:被操作的组件
监听器:一个自定义类的对象,实现了监听器接口,包含事件处理方法,把监听器添加在事件源上,当事件发生的时候虚拟机就会自动调用监听器中的事件处理方法。