《学习笔记Day22》使用多线程的2种方法写火车票卖票系统以及单例设计模式

一、线程同步

(一)同步方法

1、同步代码块:在某段代码执行的时候,希望CPU不要执行那些会对当前线程数据产生干扰的线程上,所以给这段代码加上了同步代码块
2、如果某个方法中,所有的代码都要加上同步代码块,使用同步方法就可以让代码格式变得更加简洁
3、同步方法的格式
修饰符 【static】 synchronized 返回值类型 方法名称(参数列表) {

需要保证同步的方法体
}
4、同步锁对象
(1)非静态方法:同步锁对象是this,即当前调用者,谁来调用这个方法,同步锁对象就是谁
(2)静态方法:同步方法所在类的字节码对象,类名.class,哪个类调用这个同步方法,同步方法的锁对象就是哪个类的字节码对象

(二)线程安全类型的总结

1、线程不安全的类型:
StringBuilder、ArrayList、HashMap
2、线程安全的类型:
StringBuffer、Vector、Hashtable

(三)死锁

1、A线程具有甲资源,继续执行需要乙资源;B线程具有乙资源,继续执行需要甲资源。两条线成同时拥有对方所具有的资源,两条线成都不肯释放自己拥有的资源,所以谁也不能继续执行,就形成“死锁”的现象。
2、代码表现:如果有了同步代码块的嵌套,就可能出现死锁。所以我们需要避免同步代码块的嵌套,以防死锁。

public class Demo03_DeadLock {

	public static void main(String[] args) {
		
		
		Thread t1 = new Thread("家") {

			@Override
			public void run() {
				synchronized ("车钥匙") {
					System.out.println("车钥匙在家里");
					
					synchronized ("家钥匙") {
						System.out.println("进去家得到车钥匙,既能进去家,也能进去车");
					}
				}
			}
			
		};
		
		Thread t2 = new Thread("车") {

			@Override
			public void run() {
				synchronized ("家钥匙") {
					System.out.println("家钥匙在车里");
					
					synchronized ("车钥匙") {
						System.out.println("进去车得到家钥匙,既能进去车,也能进去家");
					}
				}
			}	
		};
		t1.start();
		t2.start();
	}
}

(四)线程安全火车票案例

三个窗口,同时售卖100张火车票
打印某个窗口卖出了1张票,还剩几张

/**
 * 三个窗口,同时售卖100张火车票 打印某个窗口卖出了1张票,还剩几张
 * 
 * 三个窗口:三条线程
 * 
 * @author Zihuatanejo
 *
 */
public class Demo04_Exercise {

	public static void main(String[] args) {
		Ticket t = new Ticket();
		
		Thread t1 = new Thread(t, "窗口1");
		Thread t2 = new Thread(t, "窗口2");
		Thread t3 = new Thread(t, "窗口3");
		
		t1.start();
		t2.start();
		t3.start();
	}
	
	public static void test1() {
		Window w1 = new Window("窗口1");
		Window w2 = new Window("窗口2");
		Window w3 = new Window("窗口3");
		
		w1.start();
		w2.start();
		w3.start();
	}
}

class Ticket implements Runnable {
	private int tickets = 100;

	@Override
	public void run() {
		
		while(true) {
			
			synchronized (this) {
				if (tickets == 0) {
					break;
				}
				tickets--;
				System.out.println(Thread.currentThread().getName() + "卖出了1张票,还剩" + tickets + "张");
				
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			
		}
	}
}

class Window extends Thread {
	private static int tickets = 100;

	public Window() {
		super();
	}

	public Window(Runnable target, String name) {
		super(target, name);
	}

	public Window(Runnable target) {
		super(target);
	}

	public Window(String name) {
		super(name);
	}
	
	@Override
	public void run() {

		while (true) {

			synchronized (Window.class) {
				if (tickets == 0) {
					break;
				}
				
				tickets--;
				
				System.out.println(getName() + "卖出了1张票,还剩" + tickets + "张");
				
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

二、线程中的其他内容

(一)其他方法

1、join() 使当前线程挂起,等待加入的线程执行完毕后,再恢复执行(在哪个线程里执行,就挂起哪条线程)
2、yield() 线程让步。和sleep方法很像,和sleep不同。它只是短暂的挂起当前线程,让别的线程先运行,而自己进入到准备运行的状态(就绪态)。在大多数视线中,线程只是让步于优先级相同或者跟高的线程。

(二)线程协作

1、案例:让两条线程交替打印1-100的数字
2、所用方法:
(1)wait() 让当前线程进入无限期的等待,并释放同步锁对象
(2)notify() 唤醒一个被wait()的线程,如果有多条线程被wait(),换线优先级最高的那一个
(3)notifyAll() 唤醒所有被wait()的线程

3、注意事项:

(1)wait()、notify()、notifyAll() 必须使用同步代码块或者同步方法的同步锁对象来调用
(2)wait()、notify()、notifyAll() 必须在同步代码块或者同步方法中才能使用
(3)wait()、notify()、notifyAll()属于Object类,因为任何一个类型的实例都有可能成为同步锁对象,所以将这些方法放入Object中,任意类型的对象都能调用

public class Demo06 {

	public static void main(String[] args) throws InterruptedException {
		//13579
		//248 10
		
		MyTask mt = new MyTask();
		
		Thread t1 = new Thread(mt, "------线程1111");
		Thread t2 = new Thread(mt, "==线程2222");
		
		t1.start();
		t2.start();
		
		Thread.sleep(100);
		
	}
}

class MyTask implements Runnable {
	
	private int num = 1;
	
	@Override
	public void run() {
		
		while (true) {
			synchronized (this) {
				
				this.notify();
				
				if (num > 100) {
					break;
				}
				
				System.out.println(num + Thread.currentThread().getName());
				
				num++;
				
				try {
					Thread.sleep(30);//让线程休眠,但是不释放同步锁对象
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				
				try {
					this.wait();//让线程进入无限期等待并且释放同步锁对象
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

(三)sleep(long millis)方法和wait()方法的区别

1、相同点:都是让线程进入阻塞态
2、不同点:
(1)方法所属类型不同:sleep属于Thread类,wait属于Object类
(2)调用要求不同:sleep方法任何场景下都能调用,wait方法必须在同步方法或者同步代码块中调用
(3)关于释放同步锁对象:sleep方法让线程进入休眠但是不会释放同步锁对象,wait方法让线程进入无限期等待并且释放同步锁对象

三、单例设计模式

1、模式:在生产实践中,积累下来的经验、办事的套路
2、设计模式:在开发中,针对类、接口、方法等的设计套路,就是设计模式
3、在软件开发中,有23种设计模式,在不同的场景下,不同的需求中,可以使用不同的设计模式良好的解决问题
4、单例设计模式:单:一个、单个;例:实例;在某些情况下,设计一个类,这个类有且仅有一个对象。
5、单例设计模式的原则:
(1)构造方法私有化
(2)在类中创建好对象
(3)在类中对外提供获取对象的方式

饿汉式

1、在加载类的同时,就要初始化静态成员变量,所以就同时将本类对象创建出来
2、饿汉式:一给食物就吃,类一加载就要去创建对象

public class Demo07_SingletonHungry {

	public static void main(String[] args) {
		SingletonHungry sh1 = SingletonHungry.getInstance();
		SingletonHungry sh2 = SingletonHungry.getInstance();
		
		System.out.println(sh1);
		System.out.println(sh2);
	}
}

class SingletonHungry {
	
	//1.构造方法私有化:限制外界创建对象
	private SingletonHungry() {}
	
	//2.在类中创建好对象
	private static SingletonHungry sh = new SingletonHungry();
	
	//3.在类中对外提供获取对象的方式
	public static SingletonHungry getInstance() {
		return sh;
	}
}

懒汉式

1、懒汉式:在加载类的时候不着急创建对象,等到调用方法经过好几层判断后,非得创建不可才去创建,就像懒汉,能不做就不做,能拖就拖

public class Demo08_SingletonLazy {

	public static void main(String[] args) {
		
	}
}

class SingletonLazy {
	
	//1.构造方法私有化限制外界创建对象
	private SingletonLazy() {}
	
	//2.在类中提前创建好对象
	private static SingletonLazy sl;
	
	//A B
	
	//3.对外提供公开的获取方式
	public static SingletonLazy getInstance() {
		//内外层的判断一个都不能少,外层主要提高效率,但是如果将内层if去掉,会重新出现线程安全问题
		//3.多线程环境下如果每一次都获取同步锁对象再去判断效率低下,外层加上一个判断,能尽可能的提高效率
		if (sl == null) {
			//2.多线程环境下,无法保证1的线程安全,所以加上同步代码块保证操作的完整性
			synchronized (SingletonLazy.class) {
				//1.判断当前对象是否存在,如果存在就不创建不存在才创建
				if (sl == null) {
					sl = new SingletonLazy();//12345
				}
			} 
		}
		return sl;
	}
}

老汉式

public class Demo09_SingletonOld {

	public static void main(String[] args) {
		SingletonOld so1 = SingletonOld.so;
		SingletonOld so2 = SingletonOld.so;
		
		System.out.println(so1 == so2);
		System.out.println(so1);
		System.out.println(so2);
	}
}

class SingletonOld {
	//构造方法私有化
	private SingletonOld() {}
	
	//提前在类中创建好对象
	public static final SingletonOld so = new SingletonOld();
}

四、线程的生命周期

1、别名:线程的状态图、线程的生命周期图、线程的状态周期
2、状态罗列:

(1)新建态:线程对象创建出来之后,从未start
(2)就绪态:线程start了,但是CPU没有来临
(3)运行态:正在运行的线程处于这种状态
(4)阻塞态:线程主动休息、或者缺少一些执行所必须的资源,即使CPU来临也无法执行
(5)死亡态:线程完成了业务逻辑,或者出现了异常打断了执行,或者线程被破坏
在这里插入图片描述

Java中关于线程状态的描述

1、Java语言中,有可以获取线程状态的方法:getState()
2、Thread.state:Java语言中描述线程状态的枚举类型
3、因为线程的状态是有限的,所以该类型的对象,不需要手动创建,在类中已经创建好了,我们直接获取即可。每个对象,称为枚举项。

(1)NEW:尚未start的线程
(2)RUNNABLE:正在运行的线程
(3)BLOCKED:等待锁对象
(4)WAITING:被wait()的线程处于这种状态
(5)TIMED_WAITING:有时限的等待,例如线程sleep
(6)TERMINATED:死亡态线程

五、线程池

1、没有线程池:
(1)需要手动创建线程,还需要手动启动线程
(2)在线程运行的过程中,如果出现了业务逻辑破坏力大的情况,线程有可能被破坏,业务逻辑执行到一半就无法执行了,并且,业务逻辑也不能用新的线程继续执行
(3)当业务逻辑非常简单的时候,也要创建线程对象,只是简单地使用线程之后,线程就会变成系统垃圾无法再用,浪费系统资源
2、有线程池:
(1)不需要手动创建线程,线程会自动完成线程创建
(2)在业务逻辑破坏力大的时候,线程被破坏,线程池会安排其他线程继续执行没有执行完的业务逻辑
(3)当业务非常简单,只需要将业务逻辑提交给线程池,线程会自动安排线程执行任务,任务执行完毕后,线程不会变成系统垃圾,而是被线程池回收,继续在线程池中活跃,等待下一次提交任务

线程池的使用

1、步骤:
(1)获取线程池对象
(2)创建任务对象
(3)将任务提交到线程池中
2、获取线程对象:
(1)工具类:Executors:用于生成线程池的工具类,根据需求可以指定线程池中线程的数量
(2)ExecutorService newFixedThreadPool(int nThreads) 创建一个线程池,线程数量由参数指定
(3)ExecutorService newSingleThreadExecutor() 创建一个只具有一条线程的线程池
3、创建任务对象:创建Runnable接口的实现类对象即可
4、将任务提交到线程池中:
(1)submit(Runnable task) 将任务提交到线程池。当提交的任务数量大于池中线程数,现有的线程先执行任务,当第一个线程将任务执行完毕后,紧接着执行后续排队的任务
(2)shutdown() 结束线程池。执行已经提交的任务,不接受新任务
(3)shutdownNow() 结束线程池。试图结束正在执行的任务,不执行已经提交的任务,不接受新任务

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo11_Executors {

	public static void main(String[] args) {
		//1.获取线程池
		ExecutorService es = Executors.newFixedThreadPool(2);
		
		//2.创建任务对象
		Runnable task1 = new Runnable() {
			public void run() {
				for (int i = 0; i < 1000; i++) {
					System.out.println(i + "-----" + Thread.currentThread().getName());
					
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		};
		
		Runnable task2 = new Runnable() {
			public void run() {
				for (int i = 1000; i < 2000; i++) {
					System.out.println(i + "-----" + Thread.currentThread().getName());
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		};
		
		Runnable task3 = new Runnable() {
			public void run() {
				for (int i = 9000; i < 10000; i++) {
					System.out.println(i + "-----" + Thread.currentThread().getName());
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		};
		
		//3.将任务提交到线程池
		es.submit(task1);
		es.submit(task2);
		es.submit(task3);
		
		//es.shutdown();
		
		es.shutdownNow();
		
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

阿福真的不想掉头发

大爷?赏点?

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

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

打赏作者

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

抵扣说明:

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

余额充值