9. 线程
1. 概念
一、程序 进程 线程
1、程序:指令集静态概念
2、进程:操作系统 调度程序 动态概念
3、线程:在进程内多条执行路径
4、了解多线程,必须先了解线程,而要想了解线程,必须先了解进程,因为线程是依赖于进程而存在。
5、什么是进程?
通过任务管理器我们就看到了进程的存在。而通过观察,我们发现只有运行的程序才会出现进程。
进程:就是正在运行的程序。进程是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
6、进程有什么意义呢?
单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。
举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。
也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。并且呢,可以提高CPU的使用率。
问题:
一边玩游戏,一边听音乐是同时进行的吗?
不是。因为单CPU在某一个时间点上只能做一件事情。
而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。
7:什么是线程呢?
在同一个进程内又可以执行多个任务,而这每一个任务我就可以看出是一个线程。
线程:是程序的执行单元,执行路径。是程序使用CPU的最基本单位。
单线程:如果程序只有一条执行路径。
多线程:如果程序有多条执行路径。
8:多线程有什么意义呢?
多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率。
程序的执行其实都是在抢CPU的资源,CPU的执行权。
多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权。
我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性。
/*
* 进程:
* 正在运行的程序,是系统进行资源分配和调用的独立单位。
* 每一个进程都有它自己的内存空间和系统资源。
* 线程:
* 是进程中的单个顺序控制流,是一条执行路径
* 一个进程如果只有一条执行路径,则称为单线程程序。
* 一个进程如果有多条执行路径,则称为多线程程序。
*
* 举例:
* 扫雷程序,迅雷下载
*
* 大家注意两个词汇的区别:并行和并发。
* 前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
* 后者是物理上同时发生,指在某一个时间点同时运行多个程序。
*
* Java程序的运行原理:
* 由java命令启动JVM,JVM启动就相当于启动了一个进程。
* 接着有该进程创建了一个主线程去调用main方法。
*
* 思考题:
* jvm虚拟机的启动是单线程的还是多线程的?
* 多线程的。
* 原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出。
* 现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。
*/
2. 创建
java如何实现多线程:(掌握一和二)
方法一:继承Thread +重写run()
启动: 创建子类对象 +对象.start()
龟兔赛跑
1、Rabbit.java
/**
* 模拟龟兔赛跑
1、创建多线程: 继承 Thread +重写run(线程体)
2、使用线程: 创建子类对象 + 对象.start() 线程启动
*/
public class Rabbit extends Thread {
/*
* 该类要重写run()方法,为什么呢?
* 不是类中的所有代码都需要被线程执行的。
* 而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。
*/
@Override
public void run() {
// 一般来说,被线程执行的代码肯定是比较耗时的。所以我们用循环改进
//线程体
for(int i=0;i<100;i++){
System.out.println("兔子跑了"+i+"步");
}
}
}
class Tortoise extends Thread {
@Override
public void run() {
//线程体
for(int i=0;i<100;i++){
System.out.println("乌龟跑了"+i+"步");
}
}
}
|
2、测试类
public class RabbitApp {
public static void main(String[] args) {
//创建子类对象
Rabbit rab = new Rabbit();
Tortoise tor =new Tortoise();
//调用start 方法
rab.start();
//不要调用run方法,run方法只是普通调用方法,真正作用是调用start方法,start方法是开启一个线程,并运行一个线程,看线程状态图
//rab.run();
tor.start();
}
}
|
方法二
实现Runnable +重写run()
启动:使用静态代理
1、创建真实角色 2、创建代理角色 Thread+引用 3、代理角色.start()
推荐使用接口:
1、避免单继承局限性 2、便于共享资源
1、实现Runnable +重写run()
public class Programmer implements Runnable {
@Override
public void run() {
for(int i=0;i<1000;i++){
System.out.println("一边敲helloworld....");
}
}
}
|
2、启动
public class ProgrammerApp {
public static void main(String[] args) {
//1)、创建真实角色
Programmer pro =new Programmer();
//2)、创建代理角色 +真实角色引用
Thread proxy =new Thread(pro);
//3)、调用 .start() 启动线程
proxy.start();
for(int i=0;i<1000;i++){
System.out.println("一边聊qq....");
}
}
}
|
方法三、通过Callable和FutureTask创建线程
a. 创建Callable接口的实现类,并实现call()方法;
b. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,
该FutureTask对象封装了该Callback对象的call()方法的返回值;
c. 使用FutureTask对象作为Thread对象的target(目标)创建并启动新线程;
d. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
|
public class CallableThreadTest implements Callable<Integer> {
@Override // a. 创建Callable接口的实现类,并实现call()方法;
public Integer call() throws Exception {
int i = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);// 输出有返回值的线程 i
}
return i;//返回值
}
public static void main(String[] args) throws Exception {
// b. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,
// 该FutureTask对象封装了该Callback对象的call()方法的返回值;
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<Integer>(ctt);
// c. 使用FutureTask对象作为Thread对象的target(目标)创建并启动新线程;
new Thread(ft, "有返回值的线程").start();
// d. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
System.out.println("子线程的返回值:" + ft.get());//子线程的返回值:100
}
}
|
方法四:通过线程池创建线程
public class ThreadPool {
private static int POOL_NUM = 10;
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < POOL_NUM; i++) {
RunnableThread thread = new RunnableThread();
executorService.execute(thread);
}
}
}
class RunnableThread implements Runnable {
private int THREAD_NUM = 10;
public void run() {
for (int i = 0; i < THREAD_NUM; i++) {
System.out.println("线程" + Thread.currentThread() + " " + i);
}
}
}
3. 静态代理
静态代理步骤
1、编写一个接口,并写一个或者多个方法
2、真实的角色实现这个接口,并实现了这个方法
3、代理类,实现这个接口并作为属性,生成无参构造方法并new真实角色,同时也生成含有这个属性有参构造方法
4、测试代理类,new出代理类,并调用实现的方法
例子(潘金莲、王婆、西门庆)
1、编写一个这类的女人(你懂得)接口
public interface KindWomen {
//这种类型的女人能做什么事情呢?
public void makeEyesWithMan(); //抛媚眼
public void happyWithMan(); //happy what? You know that!
}
|
2、创建真实角色、潘金莲实现这类女人(接口)
public class PanJinLian implements KindWomen {
public void happyWithMan() {
System.out.println("潘金莲在和男人做那个.....");
}
public void makeEyesWithMan() {
System.out.println("潘金莲抛媚眼");
}
}
|
3、创建代理类王婆,也实现这类女人(接口),并把这类女人作为王婆的爱好(属性),生成无参构造方法(默认是潘金莲的代理),这类女人的爱好作为王婆的有参构造方法
public class WangPo implements KindWomen {
private KindWomen kindWomen;
public WangPo() { // 默认的话,是潘金莲的代理
this.kindWomen = new PanJinLian();
}
// 她可以是KindWomen的任何一个女人的代理,只要你是这一类型
public WangPo(KindWomen kindWomen) {
this.kindWomen = kindWomen;
}
public void happyWithMan() {
this.kindWomen.happyWithMan(); // 自己老了,干不了,可以让年轻的代替
}
public void makeEyesWithMan() {
this.kindWomen.makeEyesWithMan(); // 王婆这么大年龄了,谁看她抛媚眼?!
}
}
|
4、测试类(西门庆)来了
public class XiMenQing {
public static void main(String[] args) {
//把王婆叫出来
WangPo wangPo = new WangPo();
//然后西门庆就说,我要和潘金莲happy,然后王婆就安排了西门庆丢筷子的那出戏:
wangPo.makeEyesWithMan(); //看到没,虽然表面上时王婆在做,实际上爽的是潘金莲(其实调用的就是默认的无参构造方法)
wangPo.happyWithMan();
}
}
|
5、再来一个贾氏,也实现这类女人
public class JiaShi implements KindWomen {
public void happyWithMan() {
System.out.println("贾氏正在Happy中......");
}
public void makeEyesWithMan() {
System.out.println("贾氏抛媚眼");
}
}
|
6、测试类(西门庆)又勾引贾氏
public class XiMenQing {
public static void main(String[] args) {
//改编一下历史,贾氏被西门庆勾走:
JiaShi jiaShi = new JiaShi();
WangPo wangPo = new WangPo(jiaShi); //让王婆作为贾氏的代理人,调用了王婆的有参构造方法
wangPo.makeEyesWithMan();
wangPo.happyWithMan();
}
}
|
7、总结
说完这个故事,那额总结一下,代理模式主要使用了 Java 的多态,干活的是被代理类,代理类主要是接活,你让我干活,好,我交给幕后的类去干,你满意就成,那怎么知道被代理类能不能干呢?同根就成,大家知根知底,你能做啥,我能做啥都清楚的很,同一个接口呗
4. 线程共享问题
一、当继承Thread类,无法实现共享变量
用窗口买票举例
|
1、创建Ticket.java
public class Ticket extends Thread {
static int ticket = 5;//共享变量
@Override
public void run() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "窗口在售卖第"
+ ticket + "张票");
ticket--;
}
}
}
2、测试类
/**
* 当实现Thread类的时候,无法实现共享变量
* 每次创建一个ticket对象,里面都有五张票,各个线程之间的ticket是不共享
*/
public class TestTicket {
public static void main(String[] args) {
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
Ticket t5 = new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
结果输出会买到同样的票
|
二、当实现Runnable接口,可以实现共享变量
1、
public class TicketRun implements Runnable {
static int ticket = 5;//共享变量
@Override
public void run() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "窗口在售卖第"
+ ticket-- + "张票");
}
}
}
2、测试类
public class TestTicketRun {
public static void main(String[] args) {
TicketRun tr = new TicketRun();
new Thread(tr).start();
new Thread(tr).start();
new Thread(tr).start();
new Thread(tr).start();
new Thread(tr).start();
}
}
以上实现了变量的共享,买到的票不会有重复
5. 状态
一、线程状态
1、新生状态 2、就绪状态 3、运行状态 4、就绪状态 5、死亡状态
二、停止线程
1、自然终止:线程体正常执行完毕
2、外部干涉:
使用interrupt(),让线程在run方法中停
三、wait()、sleep()、notify()、notifyAll()
wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程(一般是最先开始等待的线程),而且不是按优先级。
notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。
四、为什么wait()、notify()、notifyAll()这些用来操作线程的方法定义在Object类中?
(1)、这些方法只存在于同步中;
(2)、使用这些方法时必须要指定所属的锁,即被哪个锁调用这些方法;
(3)、而锁可以是任意对象,所以任意对象调用的方法就定义在Object中。
案例
1、sleep
/**
* 倒计时 1、倒数10个数,一秒内打印一个 2、倒计时
*/
public class SleepDemo01 {
public static void main(String[] args) throws InterruptedException {
Date endTime = new Date(System.currentTimeMillis() + 10 * 1000);
long end = endTime.getTime();
while (true) {
// 输出
System.out.println(newSimpleDateFormat("mm:ss").format(endTime));
// 等待一秒
Thread.sleep(1000);
// 构建下一秒时间
endTime = new Date(endTime.getTime() - 1000);
// 10秒以内 继续 否则 退出
if (end - 10000 > endTime.getTime()) {
break;
}
}
}
}
2、join
/**
* join:合并线程
*/
public class TestThread2 {
public static void main(String[] args) throws Exception {
MyThread my = new MyThread();
Thread t1 = new Thread(my);
System.out.println(t1.isAlive());// false
t1.start();
System.out.println(t1.isAlive());// true
Thread main = Thread.currentThread();
for (int i = 0; i < 10; i++) {
if (i == 3) {
// t1强行加入执行,等t1线程执行完毕之后,main线程继续执行
t1.join();
}
System.out.println(main.getName() + "----" + i);
}
// 跟主线程相比较,如果优于主线程结束,则返回false,如果比主线程后结束,则返回true
System.out.println(t1.isAlive());
}
}
|
3、yied暂停自己的线程
public class YieldDemo01 extends Thread {
public static void main(String[] args) {
YieldDemo01 demo = new YieldDemo01();
Thread t = new Thread(demo); //新生
t.start();//就绪
//cpu调度 运行
for(int i=0;i<100;i++){
if(20==i){
//暂停本线程 main,只是暂停某次,下次会继续执行
Thread.yield();
}
System.out.println("main...."+i);
}
}
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("yield...."+i);
}
}
}
|
4、interrupt
/*
* public final void stop():让线程停止,过时了,但是还可以使用。
* public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。
*/
public class ThreadStopDemo {
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();//休息1秒
// 你超过三秒不醒过来,我就干死你
try {
Thread.sleep(3000);//主线程休息三秒
// ts.stop();
ts.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
|
public class ThreadStop extends Thread {
@Override
public void run() {
System.out.println("开始执行:" + new Date());
// 我要休息10秒钟,亲,不要打扰我哦
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("线程被终止了");
}
System.out.println("结束执行:" + new Date());
}
}
6. 线程基本信息
public class TestThread {
public static void main(String[] args) {
// 自己创建的线程
MyThread my = new MyThread();
Thread t1 = new Thread(my, "线程1");
// t1.setPriority(10);优先权
t1.start();
// 获取主线程就是main函数的线程
Thread main = Thread.currentThread();
for (int i = 0; i < 10; i++) {
System.out.println(main.getName()+"----------"+i);
}
// 返回现在激活的线程数
// System.out.println(tt.activeCount());
// 返回当前的线程id,作为唯一标识
// System.out.println(tt.getId());
// System.out.println(tt.getName());
// 一般的计算机返回的在1-10之间,但是有些计算机是1-100
// 优先级别越高,一定优先执行吗?不一定,优先执行的可能性更大
System.out.println(main.getPriority());
System.out.println(t1.getPriority());
// 获取线程的最高或者最低优先级
System.out.println(Thread.MAX_PRIORITY);//10
System.out.println(Thread.MIN_PRIORITY);//1
System.out.println(Thread.NORM_PRIORITY);//5
// System.out.println(tt.getPriority());
// 返回线程的状态
System.out.println(t1.getState());
// 返回线程是否存活
System.out.println(t1.isAlive());
}
}
|
/*
* 如何获取线程对象的名称呢?
* public final String getName():获取线程的名称。
* 如何设置线程对象的名称呢?
* public final void setName(String name):设置线程的名称
*
* 针对不是Thread类的子类中如何获取线程对象名称呢?
* public static Thread currentThread():返回当前正在执行的线程对象
* Thread.currentThread().getName()
*/
public class MyThreadDemo {
public static void main(String[] args) {
// 创建线程对象
//无参构造+setXxx()
MyThread my1 = new MyThread();//这里省略MyThread类
MyThread my2 = new MyThread();
//调用方法设置名称
my1.setName("林青霞");
my2.setName("刘意");
my1.start();
my2.start();
//带参构造方法给线程起名字
// MyThread my1 = new MyThread("林青霞");
// MyThread my2 = new MyThread("刘意");
// my1.start();
// my2.start();
//我要获取main方法所在的线程对象的名称,该怎么办呢?
//遇到这种情况,Thread类提供了一个很好玩的方法:
//public static Thread currentThread():返回当前正在执行的线程对象
System.out.println(Thread.currentThread().getName());
}
}
/*
名称为什么是:Thread-? 编号
class Thread {
private char name[];
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
//大部分代码被省略了
this.name = name.toCharArray();
}
7. 线程同步
同步解决线程安全问题
1、多线程安全问题:
(1)原因:当程序的多条语句在操作线程共享数据时(如买票例子中的票就是共享资源),由于线程的随机性导致一个线程对多条语句,执行了一部分还没执行完,另一个线程抢夺到cpu执行权参与进来执行,此时就导致共享数据发生错误。比如买票例子中打印重票和错票的情况。
(2)解决方法:对多条操作共享数据的语句进行同步,一个线程在执行过程中其他线程不可以参与进来
2、Java中多线程同步是什么?
同步是用来解决多线程的安全问题的,在多线程中,同步能控制对共享数据的访问。如果没有同步,当一个线程在修改一个共享数据时,而另外一个线程正在使用或者更新同一个共享数据,这样容易导致程序出现错误的结果。
3、什么是锁?锁的作用是什么?
锁就是对象
锁的作用是保证线程同步,解决线程安全问题。
持有锁的线程可以在同步中执行,没有锁的线程,即使获得cpu执行权,也进不去。
4、同步的前提:
(1)必须保证有两个以上线程
(2)必须是多个线程使用同一个锁,即多条语句在操作线程共享数据
(3)必须保证同步中只有一个线程在运行
5、同步的好处和弊端
好处:同步解决了多线程的安全问题
弊端:多线程都需要判断锁,比较消耗资源
A:同步代码块
synchronized(对象) {
需要被锁的代码//线程只有拿到了锁对象,才能执行这里的代码!!!
换言之,这里的代码如果执行了,说明该线程拿到了锁对象,其他线程不能拿到该锁对象
}
这里的锁对象可以是任意对象。
B:同步方法
把同步加在方法上。
public synchronized void show(){} //普通方法的锁是this
C:静态同步方法
把同步加在方法上。
public static synchronized void show(){} //静态方法的锁是当前类的字节码文件对象 类名.class
6、回顾以前的线程安全的类
A:StringBuffer
B:Vector
C:Hashtable
D:如何把一个线程不安全的集合类变成一个线程安全的集合类用Collections工具类的方法即可。
7、如何解决线程安全问题呢?
要想解决问题,就要知道哪些原因会导致出问题:(而这些原因也是以后我们判断一个程序是否会有线程安全问题的标准)
A:是否是多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据
8、我们来回想一下我们的程序有没有上面的问题呢?
A:是否是多线程环境 是
B:是否有共享数据 是
C:是否有多条语句操作共享数据 是
9、由此可见我们的程序出现问题是正常的,因为它满足出问题的条件。
接下来才是我们要想想如何解决问题呢?
A和B的问题我们改变不了,我们只能想办法去把C改变一下。
思想:
把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行。
问题是我们不知道怎么包啊?其实我也不知道,但是Java给我们提供了:同步机制。
|
1、同步代码块
* synchronized(对象){
* 需要同步的代码;
* }
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
// 定义同一把锁
private Object obj = new Object();
//同步代码块用任意对象做锁
@Override
public void run() {
while (true) {
// t1,t2,t3都能走到这里
// 假设t1抢到CPU的执行权,t1就要进来
// 假设t2抢到CPU的执行权,t2就要进来,发现门是关着的,进不去。所以就等着。
// 门(开,关)
synchronized (obj) { // 发现这里的代码将来是会被锁上的,所以t1进来后,就锁了。(关)
if (tickets > 0) {
try {
Thread.sleep(100); // t1就睡眠了
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
//窗口1正在出售第100张票
}
} //t1就出来可,然后就开门。(开)
}
}
}
|
2、同步方法:
//如果一个方法一进去就看到了代码被同步了,那么我就把这个同步加在方法上
private synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
}
|
3、静态方法
private static synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
4、测试
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
8. 死锁问题
死锁
两个线程对两个同步对象具有循环依赖时,就会发生死锁。即同步嵌套同步,而锁却不同。
/**
* 过多的同步代码块可能造成死锁
*/
public class SynDemo03 {
public static void main(String[] args) {
Object g =new Object();
Object m = new Object();
Test t1 =new Test(g,m);
Test2 t2 = new Test2(g,m);
Thread proxy = new Thread(t1);
Thread proxy2 = new Thread(t2);
proxy.start();
proxy2.start();
}
}
class Test implements Runnable{
Object goods ;
Object money ;
public Test(Object goods, Object money) {
super();
this.goods = goods;
this.money = money;
}
@Override
public void run() {
while(true){
test();
}
}
public void test(){
synchronized(goods){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(money){
}
}
System.out.println("一手给钱");
}
}
class Test2 implements Runnable{
Object goods ;
Object money ;
public Test2(Object goods, Object money) {
super();
this.goods = goods;
this.money = money;
}
什么输出结果都没有
9. 单例模式
1、懒汉式:
/**
* 单例设计模式
* 确保一个类只有一个对象
* 懒汉式 double checking
* 1、构造器静态私有化,避免外部直接创建对象
* 2、声明一个私有的静态变量
* 3、创建一个对外的公共的静态方法 访问该变量,如果变量没有对象,创建该对象
*/
public class Singleton {
private static Singleton singleton;
private Singleton() {
System.out.println("构造方法");
}
public static Singleton getInstance() {
if (singleton == null) {//提供效率
singleton = new Singleton();//安全
}
return singleton;
}
public void function() {
System.out.println("Singleton.function()");
}
}
|
测试:
public class Test {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
singleton.function();
Singleton singleton2 = Singleton.getInstance();
singleton2.function();
}
}
|
2、饿汉式:
/**
1)、构造器私有化
* 2)、声明私有的静态属性,同时创建该对象
* 3)、对外提供访问属性的静态方法
*
*/
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
System.out.println("Singleton.Singleton()");
}
public static Singleton getInstance() {
return singleton;
}
}
|
测试:
public class Test {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
}
}
|
3、延缓类加载时间(在类加载时为其添加一个静态属性并new自己)
/**
* 类在使用的时候加载 ,延缓加载时间
*/
class MyJvm3 {
private static class JVMholder{
private static MyJvm3 instance =new MyJvm3();
}
private MyJvm3(){
}
public static MyJvm3 getInstance (){
return JVMholder.instance;
}
}
public class Test {
public static void main(String[] args) {
MyJvm myjvm = MyJvm.getInstance();
}
}
10. 生产者消费者模式
1、电影类(生产者生成消费者观看)
/**
一个场景,共同的资源,生产者消费者模式 信号灯法
wait() :等待,释放锁 sleep 不释放锁 notify()/notifyAll():唤醒 与 synchronized
*/
public class Movie {
private String pic ;
//信号灯
//flag -->T 生产生产,消费者等待 ,生产完成后通知消费
//flag -->F 消费者消费 生产者等待, 消费完成后通知生产
private boolean flag =true;
/**
*播放
*/
public synchronized void play(String pic){
if(!flag){ //生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//开始生产
try {
Thread.sleep(500);//代表正在生产的意思
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产了:"+pic);
//生产完毕
this.pic =pic;
//通知消费
this.notify();
//生产者停下
this.flag =false;
}
public synchronized void watch(){
if(flag){ //消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//开始消费
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费了"+pic);
//消费完毕
//通知生产
this.notifyAll();
//消费停止
this.flag=true;
}
}
|
2、生产者
public class Player implements Runnable {
private Movie m ;
public Player(Movie m) {
super();
this.m = m;
}
@Override
public void run() {
for(int i=0;i<20;i++){
if(0==i%2){
m.play("左青龙");
}else{
m.play("右白虎");
}
}
}
}
|
3、消费者
public class Watcher implements Runnable {
private Movie m ;
public Watcher(Movie m) {
super();
this.m = m;
}
@Override
public void run() {
for(int i=0;i<20;i++){
m.watch();
}
}
}
4、测试类
public class App {
public static void main(String[] args) {
//共同的资源
Movie m = new Movie();
//多线程
Player p = new Player(m);
Watcher w = new Watcher(m);
new Thread(p).start();
new Thread(w).start();
}
}
11. 任务调度
了解
Timer()
schedule(TimerTask task, Date time)
schedule(TimerTask task, Date firstTime, long period)
|
public class TimeDemo01 {
public static void main(String[] args) {
Timer timer =new Timer();
timer.schedule(new TimerTask(){
@Override
public void run() {
System.out.println("so easy....");
}}, new Date(System.currentTimeMillis()+1000), 200);
}
}
|
/*
* 需求:在指定的时间删除我们的指定目录(你可以指定c盘,但是我不建议,我使用项目路径下的demo)
*/
class DeleteFolder extends TimerTask {
@Override
public void run() {
File srcFolder = new File("demo");
deleteFolder(srcFolder);
}
// 递归删除目录
public void deleteFolder(File srcFolder) {
File[] fileArray = srcFolder.listFiles();
if (fileArray != null) {
for (File file : fileArray) {
if (file.isDirectory()) {
deleteFolder(file);
} else {
System.out.println(file.getName() + ":" + file.delete());
}
}
System.out.println(srcFolder.getName() + ":" + srcFolder.delete());
}
}
}
|
public class TimerTest {
public static void main(String[] args) throws ParseException {
Timer t = new Timer();
String s = "2014-11-27 15:45:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(s);
t.schedule(new DeleteFolder(), d);
}
}
12. Lock
/*
* 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
* 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。
*
* Lock:
* void lock(): 获取锁。
* void unlock():释放锁。
* ReentrantLock是Lock的实现类.
*/
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个窗口
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
|
public class SellTicket implements Runnable {
// 定义票
private int tickets = 100;
// 定义锁对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
// 加锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票");
}
} finally {
// 释放锁
lock.unlock();
}
}
}
}
13. 匿名内部类创建多线程
匿名内部类创建多线程(了解)
/*
* 匿名内部类的格式:
* new 类名或者接口名() {
* 重写方法;
* };
* 本质:是该类或者接口的子类对象。
*/
public class ThreadDemo {
public static void main(String[] args) {
// 继承Thread类来实现多线程
new Thread() {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":"
+ x);
}
}
}.start();
|
// 实现Runnable接口来实现多线程
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":"
+ x);
}
}
}) {
}.start();
|
// 更有难度的
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println("hello" + ":" + x);
}
}
}) {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println("world" + ":" + x);
}
}
}.start();
}
}
14. 总结
一、创建线程 重点
1、继承 Thread
2、实现 Runnable
3、实现 Callable (了解)
二、线程的状态
1、
新生 -->start -->就绪 -->运行-->阻塞 -->终止
2、终止线程 (重点)
3、阻塞: join yield sleep
三、线程的信息
1、Thread.currentThread
2、获取名称 设置名称 设置优先级 判断状态
四、同步:对同一份资源
synchronized(引用类型变量|this|类.class){
}
修饰符 synchronized 方法的签名{
方法体
}
过多的同步可能造成死锁
五、生产者消费者模式
六、任务调度