多线程、同步代码块、Lock锁、线程池
(十)多线程程序实现的方式2
A:实现Runnable接口 这种方式扩展性强 实现一个接口 还可以再去继承其他类
a:如何获取线程名称
b:如何给线程设置名称
c:实现接口方式的好处
可以避免由于Java单继承带来的局限性。
(1)创建线程的另一种方法是声明实现 Runnable 接口的类。
该类然后实现 run 方法。然后可以分配该类的实例,
在创建 Thread 时作为一个参数来传递并启动
MyRunable myRunable = new MyRunable();
//Thread(Runnable target)
//分配新的 Thread 对象。
//Thread(Runnable target, String name)
//分配新的 Thread 对象。
Thread th = new Thread(myRunable,"王菲");
th.start();
Thread th2 = new Thread(myRunable,"谢霆锋");
th2.start();
public class MyRunable implements Runnable { //Runable 任务
@Override
public void run() {
for (int i = 0; i < 100; i++) {
// System.out.println(this.getName+"==+i");
System.out.println(Thread.currentThread().getName() + "==" + i);
}
}
}
(十一)多线程程序实现的方式3
A:实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
B:执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
C:实现步骤
1.创建一个类实现Callable 接口
2.创建一个FutureTask类将Callable接口的子类对象作为参数传进去
3.创建Thread类,将FutureTask对象作为参数传进去
4.开启线程
方式2:
创建一个类实现Callable 接口
2. 创建一个FutureTask类将Callable接口的子类对象作为参数传进去
3. 创建Thread类, 将FutureTask对象作为参数传进去
4. 开启线程
Callable 返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。
//Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
MyCallable myCallable = new MyCallable();
FutureTask<Integer> integerFutureTask = new FutureTask<>(myCallable);
Thread th = new Thread(integerFutureTask);
th.start();
//获取线程执行完之后,返回的结果
Integer integer = integerFutureTask.get();
System.out.println(integer);
案例:继承Thread类的方式卖电影票
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
- a: 三个窗口其实就是3个线程
- b: 定义票的数量100张
- c: 创建线程对象,启动线程. 每卖一张这个票数应该-1
public class SellTickets extends Thread {
private static int tickets = 100 ;//定义成静态的 让所有对象共享
public void run() {
// 定义票数
* 把票数定义成局部变量,那么表示的意思是每一个窗口卖了各自的100张票,而我们的需求是
* 总共有100张票,然后被3个窗口出售,很显然,定义成局部变量不合适
// int tickets = 100 ;
// 卖票
while(true){
if( tickets > 0 ){
System.out.println(getName() + "正在出售" + (tickets--) + "张票");
}
}
}
案例:卖电影票
Lock锁的概述
虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock和ReentrantLock
void lock ()
void unlock ()*/
CellRunable cellRunable = new CellRunable();
Thread th1 = new Thread(cellRunable);
Thread th2 = new Thread(cellRunable);
Thread th3 = new Thread(cellRunable);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
public class CellRunable implements Runnable {
static int piao = 100;
static Object obj = new Object();
static Lock lock = new ReentrantLock();
public void run() {
while (true) {
lock.lock(); //加锁
if (piao > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售卖 " + (piao--) + " 张票");
}
lock.unlock(); //释放锁
}
}
}
(十二)线程安全问题的产生原因分析
(1)
A:首先想为什么出现问题?(也是我们判断是否有问题的标准)
是否是多线程环境
是否有共享数据
是否有多条语句操作共享数据
B:如何解决多线程安全问题呢?
基本思想:让程序没有安全问题的环境。
怎么实现呢?
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
- 判断一个多线程应用程序是否有问题的标准:
- a: 是否是多线程环境
- b: 是否存在共享数据
- c: 是否存在多条语句同时操作共享数据
(2)同步代码块的方式解决线程安全问题及解释以及同步的特点及好处和弊端
A:同步代码块的格式
格式:
synchronized(对象){
需要同步的代码;
}
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能
B:案例演示: 同步代码块的方式解决线程安全问题
C:案例解释: 再次给学生解释一遍如何解决了线程安全问题
D:同步的好处: 同步的出现解决了多线程的安全问题。
E:同步的弊端: 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
二、Lock锁
(一)JDK5之后的Lock锁的概述和使用
A:Lock锁的概述
虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
B:Lock和ReentrantLock
void lock()
void unlock()
(二)死锁问题概述和使用
三、线程池
(一)线程池的概述和使用
A:线程池概述
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。
而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
B:内置线程池的使用概述
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
//public static ExecutorService newCachedThreadPool ():根据任务的数量来创建线程对应的线程个数
//public static ExecutorService newFixedThreadPool ( int nThreads):固定初始化几个线程
//public static ExecutorService newSingleThreadExecutor ():初始化一个线程的线程池
//这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者
//ExecutorService 线程池
//获取线程池对象
ExecutorService executorService = Executors.newCachedThreadPool();
//给线程池提交任务即可
executorService.submit(new MyRunable());
executorService.submit(new MyRunable());
executorService.submit(new MyRunable());
executorService.submit(new MyRunable());
executorService.submit(new MyRunable());
//关闭线程池
executorService.shutdown();
使用步骤:
创建线程池对象
创建Runnable实例
提交Runnable实例
关闭线程池
案例:匿名内部类的方式实现多线程
//匿名内部类来开启线程
//方式1
new Thread() {
public void run() {
System.out.println("线程执行了");
}
}.start();
//方式2
new Thread(new Runnable() {
public void run() {
System.out.println("线程执行了");
}
}).start();
四、定时器
(一)定时器的概述和使用
A:定时器概述
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。
在Java中,可以通过Timer和TimerTask类来实现定义调度的功能。
B:Timer和TimerTask
Timer:
void schedule (TimerTask task, Date time)
//安排在指定的时间执行指定的任务。
void schedule (TimerTask task, Date firstTime,long period)
//安排指定的任务在指定的时间开始进行重复的固定延迟执行。
void schedule (TimerTask task,long delay)
//安排在指定延迟后执行指定的任务。
void schedule (TimerTask task,long delay, long period)
//安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。
TimerTask由 Timer 安排为一次执行或重复执行的任务。
//boolean cancel ()
//取消此计时器任务。
MyTimerTask myTimerTask = new MyTimerTask(timer);
timer.schedule(myTimerTask, 3000); //2秒之后执行定时任务
timer.schedule(myTimerTask,2000,1000);//第一次等2秒执行,后面每隔一秒重复执行。
myTimerTask.cancel();取消定时任务
//在指定的日期来执行任务
String str = "2019-05-17 15:45:00";
Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(str);
timer.schedule(myTimerTask, date);
// timer.cancel();取消定时器
public class MyTimerTask extends TimerTask {
Timer timer;
public MyTimerTask(Timer timer) {
this.timer=timer;
}
public void run() {
System.out.println("砰~~~ 爆炸了");
//timer.cancel();
}
}
开发中
Quartz是一个完全由java编写的开源调度框架。
案例:在指定日期删除文件夹
Timer timer = new Timer();
MyTimerTask myTimerTask = new MyTimerTask(timer, "E:\\myclass");
Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2019-05-17 16:17:00");
timer.schedule(myTimerTask, date);
}
}
class MyTimerTask extends TimerTask {
Timer timer = null;
String s = null;
public MyTimerTask(Timer timer, String s) {
this.timer = timer;
this.s = s;
}
public void run() {
//删除一个文件夹
File file = new File(s);
if (file.exists()) {
deleteFolder(file);
}
timer.cancel();
}
private void deleteFolder(File file) {
File[] files = file.listFiles();
for (File f : files) {
if (f.isFile()) {
f.delete();
} else {
deleteFolder(f);
}
}
file.delete();
五、设计模式
(一)设计模式的概述和分类
A:设计模式概述
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性以及代码的结构更加清晰.
B:设计模式分类
创建型模式(创建对象的): 单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
行为型模式(对象的功能): 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
结构型模式(对象的组成): 模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。
(二)简单工厂模式概述和使用
A:简单工厂模式概述: 又叫静态工厂方法模式,它定义一个具体的工厂类负责创建一些类的实例
B:优点: 使用静态工厂模式的优点是实现责任的分割,该模式的核心是工厂类,工厂类含有必要的选择逻辑,可以决定什么时候创建哪一个产品的实例,
而客户端则免去直接创建产品的责任,而仅仅是消费产品。也就是说静态工厂模式在不改变客户端代码的情况可以动态的增加产品。
明确了类的职责
C:缺点
这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,
就需要不断的修改工厂类,不利于后期的维护
(三)工厂方法模式的概述和使用
A:工厂方法模式概述
工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
B:优点
客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,
只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性
C:缺点: 需要额外的编写代码,增加了工作量
(四)单例模式之懒汉式
单例设计模式之懒汉式
开发中 饿汉式
面试中 懒汉式
面试就是想面试你们的两种思想:
a: 线程安全思想
b: 延迟加载思想
Teacher teacher = Teacher.getTeacher();
Teacher teacher1 = Teacher.getTeacher();
System.out.println(teacher==teacher1);
private static Teacher teacher = null;
//1.私有化构造
private Teacher() {
}
//th1
//2.提供一个公共的静态方法,让外界通过这个方法,去获取一个该类的对象
public synchronized static Teacher getTeacher() {
if (teacher == null) {
teacher = new Teacher();
return teacher;
}
return teacher;
}