Java线程知识概况

多线程:是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器;

软件多线程,即便处理器只能运行一个线程,操作系统也可以通过快速的在不同线程之间进行切换,由于时间间隔很小,来给用户造成一种多个线程同时运行的假象。这样的程序运行机制被称为软件多线程;

一、进程

1、概念

就是正在执行的程序,也就是代表了程序锁占用的内存空间区域。

2、特点

**独立性:**进程是系统中独立存在的实体,他可以拥有自己的独立的资源,每一个进程都是拥有自己的
地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间;

**动态性:**进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的动态指令集合。在进程中假如了时间的概念,进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的;

并发性: 多个进程可以在单个处理器上并发执行,多个进程之间不会相互影响;

二、线程

1、概念

线程(Thread)是操作系统能够进行运算的最小单位。他是被包含在进程之中,是进程中的实际运作单位。一个进程可以开启多个线程;

多线程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务;

简而言之,一个程序运行后至少一个线程,一个进程包含多个线程 ;

如果一个进程只有一个线程,这种程序被称之为单线程;

如果一个进程中有多条执行路径被称之为多线程程序;

进程包含线程
在这里插入图片描述

2、进程和线程的关系

在这里插入图片描述
从上图可以看出一个操作系统中可以有多个进程,一个进程可以有多个线程,每个进程有自己的独立的内存,每个线程共享一个进程中的内存,每个线程又有自己独立的内存;(记清这个关系,非常重要)

所以想使用线程技术,得先有进程,进程的创建时OS创建的,你能实现吗?

	**答**不能,一般都是C和C++ 语言完成的。

并发和并行的区别
如果只有一个CPU去干活,大家都在抢占CPU资源,发生了资源共享被强占的线程–并发;
假设有多个CPU在干活,每个CPU干一个任务,只不过是多个CPU一起各干各的-并行
多线状态
5种 新建状态-可运行状态-运行状态-阻塞状态-终止状态

3、多线程特性

3.1随机性

在这里插入图片描述

3.2线程状态

在这里插入图片描述

线程的生命周期,总共有五种状态

1.新建状态(New):在当前对象创建后,即进入新建状态,如Thread t = new MyThread();

2.就绪状态(Runnable):当调用线程对象的start()方法,线程就进入了就绪状态。处于就绪状态的线程,只能说明线程多好了准备,随时等待CPU的调用,并不是说执行t.start() 此线程就会立即执行;

3.运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行。即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态,必须处于就绪状态中;
4.阻塞状态(Blocked):处于状态中的线程由于某种原因,暂时放弃了对CPU的使用权,停止状态。此时进入了阻塞状态,知道其进入就绪状态,才有机会被CPU再次调用被CPU调用以进入到运行状态;

5.根据阻塞状态山城的原因不同,阻塞状态又可以分三种:
a)等待阻塞:运行状态中的线程wait()方法,使得本线程进入到等待阻塞状态;
b)同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他的线程所占用),他会进入同步素色状态;
c)其他阻塞:通过调用线程的sleep()或Join()或发出了I\O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I\O处理完毕时,线程重新转如就绪状态。
6.死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

三、多线程创建

1、继承Thread

1.1概述

1.Thread类本质上是实现了一个Runnable接口的实例,代表一个线程的实例,启动线程的唯一方法就是通过Thread类的start()实例方法。Start()方法是一个native方法,他将通知底层操作系统,最终由操作系统启动一个新的线程,操作系统将执行run()方法。这种实现多线程简单通过自己的类extend Thread,并复写run()方法,就可以启动新的线程并执行自己的定义的run()方法。

2.模拟开启多个线程,每个线程调用的run()方法

1.2创建对象

Thread(); 分配新的Thread对象;
Thread(Runnable target); 分配新的Thread对象;
Thread(Runnable target,String name) :分配新的Thread对象;
Thread(String name) :分配新的Thread对象;

1.3常用方法

static Thread currentThread():返回对当前正在执行的线程对象的引用;
long getId():返回该线程的标识符;
String getName(): 返回该线程的名称;
void run() ;
void setName(String name):改变线程名称,使之与参数 name 相同;
static void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠(暂停执行);
void start():使该线程开始执行;Java 虚拟机调用该线程的 run 方法;
void stop():来终止线程将释放它已经锁定的所有监视器

1.4测试案例

package cn.tedu.thread;

//这个类用来测试 -- 继承Thread
public class Test_Thread {
	public static void main(String[] args) {
		//3、创建线程对象
		MyThread t1 = new MyThread();//线程状态:新建状态
		//4、启动线程
		t1.start();//线程状态:可运行状态,等待CPU调度
//		t1.run();		
		//5、实现多线程程序:一定是有一个以上的线程对象在执行任务
		MyThread t2 = new MyThread();
		t2.start();
//		t2.run();
		/*
		 * 6、start()和run()的区别:
		 * 	1、start()方法可以实现多线程效果
		 * 	2、run()方法只是当做一个普通的方法调用,是一个顺序执行的效果
		 */
		
		//7、程序会自动分配线程名称:命名方式类似于:Thread-0 Thread-1
		t1.setName("美队");
		t2.setName("钢铁侠");//修改线程名称
		
	}
}
//1、自定义多线程类
class MyThread extends Thread{
	public MyThread() {}//无参构造
//	public MyThread(String name) {}
	
	//2、写业务,需要都写在run()里面 -- 就重写run()
	@Override
	public void run() {
//		super.run();//默认使用类父类的run()
		for (int i = 0; i < 10; i++) {
			System.out.println(getId()+"==="+getName());//线程名称
		}
	}
}

11===钢铁侠
10===美队
11===钢铁侠
10===美队
11===钢铁侠
11===钢铁侠
10===美队
11===钢铁侠
10===美队
10===美队
11===钢铁侠
10===美队
10===美队
11===钢铁侠
11===钢铁侠
11===钢铁侠
11===钢铁侠
10===美队
10===美队
10===美队

注意:自己运行上面的程序的结果,从上面结果可以确认,start()方法只是通知操作系统线程就绪,具体什么时间执行,操作系统来决定,我么JVM已经控制不了了。

2、实现Runnable接口

2.1概述

如果自己的类已经extends另一个类, 就无法多继承,此时,可以实现一个Runnable接口。

2.2常用方法

void run() :使用实现接口Runnable的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的run方法;

2.3 测试案例

package cn.tedu.thread;

public class Test_Runnable {
	public static void main(String[] args) {
		//4、创建线程对象
		MyRunnable target = new MyRunnable();
		
		//能不能把target和Thread绑定关系 -- 利用了Thread类的构造方法
		Thread thread = new Thread(target);
		//5、启动线程
		thread.start();
		
		//模拟多线程程序
		Thread thread2 = new Thread(target);
		thread2.start();
		thread.setName("1号线程");
		thread2.setName("2号线程");
	}
}
//1、自定义多线程,implements Runnable
//2、实现了接口以后的实现类,有两种选择:作为一个抽象类 + 把所有抽象方法都重写
class MyRunnable implements Runnable {
	//把你自己的业务,都放在重写的run()
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			//Thread.currentThread()是获取正在执行任务的Thread对象
			System.out.println(i+"==="+Thread.currentThread().getName());
		}
	}
	
}
0===1号线程
0===2号线程
1===2号线程
2===2号线程
3===2号线程
4===2号线程
5===2号线程
6===2号线程
1===1号线程
7===2号线程
2===1号线程
3===1号线程
4===1号线程
8===2号线程
9===2号线程
5===1号线程
6===1号线程
7===1号线程
8===1号线程
9===1号线程

注意:
可以看到执行顺序是乱的,我们已经知道start()方法只是通知操作系统线程就绪,具体什么时间执行,操作系统来决定,我们JVM虚拟机已经控制不了了,这就是乱序的结果,也是正常的。
个人建议
启动线程时注意绑定,实现Runnable接口,,,不能直接使用start()
Thread和Runnable之间的比较,优缺点
在这里插入图片描述

四、售票案例

需求:设计4个售票窗口,总计售票100张、
用多线程的程序设计并写出代码

方案1:继承Thread

package cn.tedu.thread;

//这个类用来测试多线程售票方式 -- 方案1:继承Thread
public class Test_Tickets {
	public static void main(String[] args) {
		//3、创建线程对象
		MyTickets target = new MyTickets();
		//4、启动线程
		target.start();
		target.setName("1号窗口");
		
		//问题1:我们总共有100张票,但是卖了400张,为什么?
		//因为每个对象初始化时都初始化了自己的ticket100张
		//模拟多线程
		MyTickets target2 = new MyTickets();
		target2.start();
		target2.setName("2号窗口");
		MyTickets target3 = new MyTickets();
		target3.start();
		target3.setName("3号窗口");
		MyTickets target4 = new MyTickets();
		target4.start();
		target4.setName("4号窗口");
	}
}
//1、自定义多线程
class MyTickets extends Thread {
	//定义变量,记录票数
	//成员变量 -- 实例变量,这个资源会跟着对象存在,new几次就有几份
//	int tickets = 100;
	//问题1解决方案:把资源变成共享资源,不跟随对象而产生,保证程序中只有这一份资源,只有100张票
	private static int tickets = 100;
	
	
	//2、所有业务放入重写的run()
	@Override
	public void run() {
//		super.run();
		//重复卖掉100张票
		while (true) {//一直卖票
			if (tickets > 0) {
				//5、增加延迟性的考察。
				//问题2:程序出现了越界现象:0,-1,-2
				//问题3:程序出现了重复卖的问题,一张票卖给多个人
				
				//当tickets=1,target1,target2,target3,target4都睡眠状态
				try {
					Thread.sleep(10);//让程序休眠10ms
				} catch (Exception e) {
					e.printStackTrace();
				}
				/*
				 * 假设target1醒了,tickets=1,准备执行tickets--,输出1,还要把tickets变成0,还没变呢!!
				 * target2醒了,tickets=0时,准备执行tickets--,输出了0,还要把tickets变成-1
				 * target3醒了,tickets=-1时,准备执行tickets--,输出了-1,还要把tickets变成-2
				 * target4醒了,tickets=-2时,准备执行tickets--,输出了-2,还要把tickets变成-3
				 */
				System.out.println(getName()+ "=" + tickets--);
			}else {
				break;//<=0时结束死循环
			}
		}
	}
}

控制台显示:

1号窗口=100
3号窗口=100
2号窗口=100
4号窗口=100
2号窗口=99
1号窗口=98
3号窗口=99
4号窗口=99
1号窗口=97
4号窗口=97
3号窗口=97
2号窗口=97
2号窗口=96
1号窗口=93
4号窗口=94
3号窗口=95
2号窗口=92
4号窗口=92
3号窗口=91
1号窗口=92
1号窗口=90
2号窗口=89
4号窗口=90
3号窗口=90
2号窗口=88
1号窗口=87
3号窗口=88
4号窗口=88
4号窗口=86
2号窗口=85
1号窗口=85
3号窗口=86
3号窗口=84
2号窗口=84
4号窗口=84
1号窗口=84
4号窗口=83
3号窗口=80
1号窗口=82
2号窗口=81
1号窗口=79
3号窗口=76
4号窗口=77
2号窗口=78
3号窗口=75
1号窗口=74
4号窗口=75
2号窗口=75
1号窗口=73
3号窗口=71
2号窗口=72
4号窗口=73
1号窗口=70
2号窗口=69
3号窗口=70
4号窗口=68
2号窗口=67
3号窗口=66
1号窗口=67
4号窗口=65
1号窗口=64
2号窗口=63
3号窗口=64
4号窗口=62
1号窗口=61
2号窗口=61
3号窗口=61
4号窗口=60
2号窗口=59
1号窗口=59
3号窗口=59
4号窗口=58
1号窗口=57
2号窗口=55
3号窗口=56
4号窗口=54
1号窗口=53
3号窗口=52
2号窗口=53
4号窗口=51
2号窗口=50
1号窗口=48
3号窗口=49
4号窗口=47
3号窗口=46
2号窗口=44
1号窗口=45
4号窗口=43
1号窗口=42
3号窗口=40
2号窗口=41
4号窗口=39
2号窗口=38
4号窗口=35
1号窗口=36
3号窗口=37
4号窗口=34
2号窗口=33
3号窗口=32
1号窗口=31
4号窗口=30
1号窗口=29
3号窗口=28
2号窗口=27
4号窗口=26
2号窗口=25
3号窗口=24
1号窗口=23
4号窗口=22
2号窗口=21
3号窗口=20
1号窗口=19
4号窗口=18
2号窗口=17
1号窗口=16
3号窗口=15
4号窗口=14
2号窗口=13
1号窗口=12
3号窗口=12
4号窗口=11
2号窗口=10
1号窗口=9
3号窗口=8
4号窗口=7
2号窗口=6
1号窗口=5
3号窗口=5
4号窗口=4
2号窗口=3
1号窗口=2
3号窗口=2
4号窗口=1
2号窗口=0
3号窗口=-2
1号窗口=-1

遇到以下问题不要慌,继续往下看,解决方案在下面

方案2:实现Runnable接口

package cn.tedu.thread;

//这个类用来测试多线程售票方式2 -- 方案2:实现Runnable接口
public class Test2_Tickets {
	public static void main(String[] args) {
		//3、创建线程对象
		MyTickets2 target = new MyTickets2();
		//把target和Thread绑定关系
		Thread thread = new Thread(target,"1号窗口--");
		Thread thread2 = new Thread(target,"2号窗口--");
		Thread thread3 = new Thread(target,"3号窗口--");
		Thread thread4 = new Thread(target,"4号窗口--");
		//4、启动线程
		thread.start();
		thread2.start();
		thread3.start();
		thread4.start();
	}
}
//1、自定义多线程类
class MyTickets2 implements Runnable {
	int tickets = 100;	
	//2、重写run(),把业务放进来
	@Override
	public void run() {
		while (true) {
			if (tickets > 0) {//多个线程使用同一个对象s,可以解决问题
				//多线程延时访问
				try {
					//问题1:程序出现了越界现象:0,-1,-2
					//问题2:程序出现了重复卖的问题,一张票卖给多个人
					Thread.sleep(10);//睡10ms
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + tickets--);
			}else {
				break;
			}
		}
	}
}


控制台显示:

4号窗口--99
1号窗口--97
2号窗口--100
3号窗口--98
2号窗口--96
3号窗口--95
1号窗口--94
4号窗口--93
3号窗口--92
4号窗口--91
2号窗口--92
1号窗口--92
3号窗口--90
2号窗口--90
4号窗口--90
1号窗口--90
1号窗口--89
4号窗口--88
2号窗口--87
3号窗口--86
3号窗口--85
1号窗口--82
4号窗口--83
2号窗口--84
2号窗口--81
4号窗口--80
1号窗口--79
3号窗口--78
1号窗口--77
2号窗口--76
4号窗口--77
3号窗口--75
2号窗口--74
4号窗口--73
1号窗口--74
3号窗口--72
1号窗口--71
2号窗口--70
4号窗口--71
3号窗口--69
1号窗口--68
2号窗口--67
4号窗口--66
3号窗口--65
2号窗口--64
1号窗口--63
4号窗口--64
3号窗口--62
4号窗口--61
1号窗口--60
2号窗口--59
3号窗口--58
2号窗口--57
4号窗口--55
1号窗口--56
3号窗口--54
1号窗口--53
4号窗口--52
2号窗口--51
3号窗口--50
2号窗口--49
1号窗口--47
4号窗口--48
3号窗口--46
4号窗口--45
2号窗口--45
1号窗口--45
3号窗口--44
1号窗口--42
2号窗口--43
4号窗口--43
3号窗口--41
2号窗口--40
4号窗口--40
1号窗口--40
3号窗口--39
1号窗口--37
4号窗口--38
2号窗口--36
3号窗口--35
4号窗口--34
1号窗口--32
2号窗口--33
3号窗口--31
1号窗口--30
2号窗口--28
4号窗口--29
3号窗口--27
1号窗口--26
2号窗口--25
4号窗口--25
3号窗口--24
1号窗口--23
2号窗口--22
4号窗口--22
3号窗口--21
1号窗口--20
2号窗口--19
4号窗口--19
3号窗口--18
1号窗口--17
4号窗口--16
2号窗口--16
3号窗口--15
1号窗口--14
2号窗口--13
4号窗口--12
3号窗口--11
1号窗口--10
4号窗口--9
2号窗口--8
3号窗口--7
1号窗口--6
4号窗口--5
2号窗口--5
3号窗口--4
1号窗口--3
2号窗口--1
4号窗口--2
3号窗口--0
1号窗口---1

通过控制台的显示我们会发现,两种方法都有出现重卖,超卖现象

【问题汇总】
1.每次创建线程对象,都会生成一个tickets变量时100,创建4次对象就生成了400张票了。不符合需求,怎么解决呢?能不能把tickets变量在每个对象间的共享,就保证多少个对象都是卖着100张票的。–用静态修饰;
2. 产生超卖,-1张、-2张;
3.产生重卖,同一张票买个多人;
4.多线程安全问题是如何出现的?常见情况是由线程的随机性+访问延迟sleep();
5.以后如何判断程序有没有线程安全问题? 在多线程中+有共享数据+多条语句操作共吸纳过数据。

【解决方法】
多线程编程数据安全隐患的解决方案:同步锁

五、同步锁

	把有可能出现问题的代码包起来,一次只让一个线程执行,通过sychronized关键字实现同步。
	当多个对象操作共享数据库时,可以使用同步解锁线程安全问题

使用方法:

synchronized(对象){
    需要同步的代码;
}

1、同步和异步的概念

同步是:同一时刻,只有一个人拿着钥匙在开门,也就是说,共享资源同一时刻只有一个线程在操作,其他没有钥匙的线程在等待;

异步是:同一时刻,大家一起上,相当于,多个 线程一起操作了共享数据。

2、特点

1.前提1,同步需要两个或者两个以上的线程;
2.前提2,多个线程间必须使用一个同步锁;
3.同步缺点是会降低程序的执行效率,为了保证线程安全必须牺牲性能;
4.可以修饰方法称为同步方法,使用锁对象this;
5.可以修饰代码块称之为同步代码块,锁对象可以任意;
6.synchronized可以修饰方法称为同步方法,也可以修饰代码块称之为同步代码块

使用同步代码块,你需要考虑两件事情,使用锁的位置+使用锁的对象

3、售票案例代码改造

package cn.tedu.thread;

//这个类用来测试多线程售票方式2 -- 方案2:实现Runnable接口
public class Test2_Tickets {
	public static void main(String[] args) {
		//3、创建线程对象
		MyTickets2 target = new MyTickets2();
		//把target和Thread绑定关系
		Thread thread = new Thread(target,"1号窗口--");
		Thread thread2 = new Thread(target,"2号窗口--");
		Thread thread3 = new Thread(target,"3号窗口--");
		Thread thread4 = new Thread(target,"4号窗口--");
		//4、启动线程
		thread.start();
		thread2.start();
		thread3.start();
		thread4.start();
	}
}
//1、自定义多线程类
class MyTickets2 implements Runnable {
	int tickets = 100;
//	static int tickets = 100;
	Object obj = new Object();
//	String s = "abc";
	
	//2、重写run(),把业务放进来
	@Override
	public void run() {
		while (true) {
			//同步锁的位置:是从问题发生点开始,到结束为止
			//同步锁的对象:如果是同步代码块,可以锁任意对象,根本不关心锁对象是谁;要求多线程间使用同一个对象
//			synchronized(new Object()) {//每个线程有自己的对象
			synchronized(obj) {//多个线程使用同一个对象obj,可以解决问题
//			synchronized(s) {//多个线程使用同一个对象obj,可以解决问题
				if (tickets > 0) {//多个线程使用同一个对象s,可以解决问题
					//多线程延时访问
					try {
						//问题1:程序出现了越界现象:0,-1,-2
						//问题2:程序出现了重复卖的问题,一张票卖给多个人
						Thread.sleep(10);//睡10ms
					} catch (Exception e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + tickets--);
				}else {
					break;
				}
			}
		}
	}
}


控制台显示:

1号窗口--100
1号窗口--99
1号窗口--98
1号窗口--97
1号窗口--96
1号窗口--95
1号窗口--94
1号窗口--93
1号窗口--92
1号窗口--91
1号窗口--90
1号窗口--89
1号窗口--88
1号窗口--87
1号窗口--86
1号窗口--85
1号窗口--84
1号窗口--83
1号窗口--82
1号窗口--81
1号窗口--80
1号窗口--79
1号窗口--78
1号窗口--77
1号窗口--76
1号窗口--75
1号窗口--74
1号窗口--73
1号窗口--72
1号窗口--71
1号窗口--70
1号窗口--69
1号窗口--68
1号窗口--67
1号窗口--66
1号窗口--65
1号窗口--64
1号窗口--63
1号窗口--62
1号窗口--61
1号窗口--60
1号窗口--59
1号窗口--58
1号窗口--57
1号窗口--56
1号窗口--55
1号窗口--54
1号窗口--53
1号窗口--52
1号窗口--51
1号窗口--50
1号窗口--49
1号窗口--48
1号窗口--47
1号窗口--46
1号窗口--45
1号窗口--44
1号窗口--43
1号窗口--42
1号窗口--41
1号窗口--40
1号窗口--39
1号窗口--38
1号窗口--37
1号窗口--36
1号窗口--35
1号窗口--34
1号窗口--33
1号窗口--32
1号窗口--31
1号窗口--30
1号窗口--29
1号窗口--28
1号窗口--27
1号窗口--26
1号窗口--25
1号窗口--24
1号窗口--23
1号窗口--22
1号窗口--21
1号窗口--20
1号窗口--19
1号窗口--18
1号窗口--17
1号窗口--16
1号窗口--15
1号窗口--14
1号窗口--13
1号窗口--12
1号窗口--11
1号窗口--10
1号窗口--9
1号窗口--8
1号窗口--7
1号窗口--6
1号窗口--5
4号窗口--4
4号窗口--3
4号窗口--2
4号窗口--1


六、单例设计模式

单例设计模式,是一种常用的软件设计模式,在他核心的结构中之包含一个被称之为单例的特殊类,通过单例模式可以保证系统中,应用该模式的类一个类只有一个实现类,及一个类也只有一个对象实例

概念

单例设计模式可以说是大多数开发人员在实际使用中最多的,常见的Spring默认常见的是bean就是单例模式的。

单例模式又很多好处,比如说可节约系统内存空间,控制资源的使用。
其中单例模式最重要的是保证对象只有一个。
简单来说,保证一个类在内存中的对象就一个。
RunTime就是典型的单例设计,我们通过RunTime类的分析,一窥究竟

2、源码剖析

/**
 * Every Java application has a single instance of class
 * <code>Runtime</code> that allows the application to interface with
 * the environment in which the application is running. The current
 * runtime can be obtained from the <code>getRuntime</code> method.
 * <p>
 * An application cannot create its own instance of this class.
 *
 * @author  unascribed
 * @see     java.lang.Runtime#getRuntime()
 * @since   JDK1.0
 */

RunTime.java

package java.lang;
 
public class Runtime {
    //1、创建静态的全局唯一的对象
    private static Runtime currentRuntime = new Runtime();
 
    //2、私有构造方法,不让外部来调用
    /** Don't let anyone else instantiate this class */
    private Runtime() {}
 
    //3、通过自定义的静态方法获取实例
    public static Runtime getRuntime() {
        return currentRuntime;
    }
}

3、饿汉式

package cn.tedu.thread;

//这个类用来测试单例设计模式
//单例设计模式:保证整个工程中,只有唯一的实例
public class Test_Singleton {
	public static void main(String[] args) {
		
		//4、调用类MySingleton内部创建好的对象
		MySingleton m1 = MySingleton.getSingle();
		System.out.println(m1);
		MySingleton m2 = MySingleton.getSingle();
		System.out.println(m2);
		
		//5、全局真的只要一个对象吗??
		System.out.println(m1 == m2);//==比较引用类型比较的是地址值
		System.out.println(m1.equals(m2));
	}
}
//自定义单例类 -- 饿汉式
class MySingleton {
	//1、私有化构造方法,目的是不让外界随意new
	private MySingleton() {}
	
	//2、在类的内部,提供创建好的对象
	//static -- 因为静态只能调用静态,所以想要被getSingle()调用,必须也是静态资源
	static private MySingleton single = new MySingleton();
	
	//3、提供公共的,访问方式,把single返回给外界调用位置
	//静态资源通过"类名."来调用,静态只能调用静态
	static public MySingleton getSingle() {
		return single;
	}
}


控制台显示

cn.tedu.thread.MySingleton@7a5d012c
cn.tedu.thread.MySingleton@7a5d012c
true
true

4、懒汉式

package cn.tedu.thread;

//这个类用来测试单例设计模式
//单例设计模式:保证整个工程中,只有唯一的实例
public class Test_Singleton {
	public static void main(String[] args) {
		//4、调用类MySingleton2内部创建好的对象
		MySingleton2 m1 = MySingleton2.getSingle();
		System.out.println(m1);
		MySingleton2 m2 = MySingleton2.getSingle();
		System.out.println(m2);
		
		//5、全局真的只要一个对象吗??
		System.out.println(m1 == m2);//==比较引用类型比较的是地址值
		System.out.println(m1.equals(m2));
	}
}


//自定义单例类 -- 懒汉式 -- 创建对象的时机不同(面试重点)
//懒汉式:延迟访问 + 线程安全隐患(有多条语句,对共享资源进行操作)
class MySingleton2 {
	//1、私有化构造方法,目的是不让外界随意new
	private MySingleton2() {}
	
	//2、在类的内部,提供创建好的对象
	//static -- 因为静态只能调用静态,所以想要被getSingle()调用,必须也是静态资源
	static private MySingleton2 single;//懒汉式,默认值是null
	
	//3、提供公共的,访问方式,把single返回给外界调用位置
	//静态资源通过"类名."来调用,静态只能调用静态
//	synchronized static public MySingleton2 getSingle() {//直接把方法同步了
	static public MySingleton2 getSingle() {
		//如果你要锁的共享资源是静态的,此时,锁对象必须是MySingleton2的字节码对象
//		synchronized(MySingleton2.class){
			//什么时候get,什么时候创建对象
			if (single == null) {
				single = new MySingleton2();
			}
			return single;
//		}
	}
}






控制台显示:

cn.tedu.thread.MySingleton2@7a5d012c
cn.tedu.thread.MySingleton2@7a5d012c
true
true

设计模式在开发过程中很常用的,Java发展至今,才一共有那么二十多种,当然最常用的还是饿汉式和懒汉式,双重锁模式有兴趣的可以了解一下,在此还推荐一本书《设计模式》,自己感觉是作为程序员必备的一本书吧!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZangChen.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值