欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java 笔记 17:JavaSE:多线程基础,单例设计模式(重要)

程序员文章站 2022-04-15 19:12:40
Java 笔记 17JavaSE:多线程基础线程的生命周期线程安全问题死锁生产者,消费者问题单例设计模式(重要)JavaSE:多线程基础多线程相关知识的课程安排:1、JavaSE:多线程基础2、后面:多线程高级 juc多线程相关的一些概念:(了解)程序:当你要完成某个/些任务,功能时,选择一种编程语言而编写的一组指令的集合。软件:软件 = 程序 + 程序运行所需要的一些资源文件。一个软件中可能会有很多个程序构成。进程:程序的一次运行。每个进程之间是独立。操作系统在分配资源(例...

JavaSE:多线程基础

多线程相关知识的课程安排:

  • 1、JavaSE:多线程基础
  • 2、后面:多线程高级 juc
  • 多线程相关的一些概念:(了解)
  • 程序:
  • 当你要完成某个/些任务,功能时,选择一种编程语言而编写的一组指令的集合。
  • 软件:
  • 软件 = 程序 + 程序运行所需要的一些资源文件。
  • 一个软件中可能会有很多个程序构成。
  • 进程:
  • 程序的一次运行。
  • 每个进程之间是独立。操作系统在分配资源(例如:内存)时,是以进程为单位。
  • 两个进程之间进行切换,通信(交换数据)等操作时,成本比较高。
  • 线程:
  • 进程中的其中一条执行路径。
  • 同一个进程中的多个线程之间是可以共享部分内存(方法区、堆),每个线程的有些内存又是独立(虚拟机栈、本地方法栈、程序计数器)。
  • 因为线程之间可能使用共享内存,那么在数据交换成本上就比较低。而且线程之间的切换,对于CPU和操作系统来说,成本比较低。
  • 所以我们通常用多线程来代替多进程的方式,实现多任务开发。
  • 线程是CPU调度的最小单位。
  • 并行:
  • 多个线程同时运行。
  • 并行,要求同时进行。针对CPU多核,甚至多个CPU,同时运行多个线程任务。
  • 并发:
  • 多个进程同时运行
  • 高并发,多个任务处理功能,但是不要求同时进行。
  • CPU:一个CPU同一个时间只能够运行一个线程的任务。
  • 如何实现多个线程同时运行的呢?
  • 是因为CPU是非常快,这个速度远远高于内存、硬盘、人的大脑反应的速度。
  • 那么CPU会在多个线程之间,快速的切换,人是感觉不到。

1、Java中如何去实现多线程?

  • (1)Java的程序入口是main,其实也是main线程,主线程。
  • 线程是进程的其中一条执行路径,即一个进程至少有一个线程。那么main线程就是Java程序进程的第一个线程了。
  • (2)如何开启main线程以外的其他线程呢?
  • 这里讲解JavaSE阶段2种,后面会发现还有其他方式。
  • 方式有两种:①继承Thread类②实现Runnable接口
  • 2、继承Thread类
  • 步骤:
  • (1)编写线程类去继承java.lang.Thread类
  • (2)必须重写父类的public void run(){}
  • 在run()中需要编写,你这个线程需要完成的任务。
  • (3)创建线程类对象
  • (4)启动线程:start()
  • 3、实现Runnable 接口
  • 步骤:
  • (1)编写线程类去实现java.lang.Runnable接口
  • (2)必须实现接口的抽象方法:public void run()
  • 在run()中需要编写,你这个线程需要完成的任务。
  • (3)创建线程类对象
  • (4)启动线程:start()
  • 这个start()方法只有Thread类中才有,说明我们要借用Thread类的对象。

线程的生命周期

线程的生命周期:

  • (1)新建/出生
  •  new好了一个线程对象,此时它和普通的Java对象并没有区别。
    
  •  好比一个美女出生并慢慢长大。
    
  • (2)就绪
  •  就绪状态的线程是具备被CPU调用的能力和状态,也只有这个状态的线程才能被CPU调用。
    
  •  即线程调用了start()
    
  •  好比这个美女被送进了宫。她此时可以被皇帝宠幸。
    
  • (3)运行
  •  运行状态就是当前线程正在被CPU调度执行。
    
  •  好比这个美女正在被宠幸。
    
  •  运行->就绪  ①时间到②yield()
    
  • (4)阻塞
  •  从运行状态到阻塞状态有几种情况:
    
  •  ①sleep()
    
  •  ②wait()
    
  •  ③join()
    
  •  ④没锁
    
  •  ⑤suspend()已过时
    
  •  从阻塞回到就绪状态
    
  •  ①sleep()时间到,sleep()被打断interrupt()
    
  •  ②notify()
    
  •  ③加塞的线程结束
    
  •  ④占用锁的线程释放锁
    
  •  ⑤resume()已过时
    
  • (5)死亡
  •  从运行到死亡:①run()正常结束②run()遇到异常但是没处理③其他线程把你stop()(已过时)
    

Java 笔记 17:JavaSE:多线程基础,单例设计模式(重要)
java.lang.Thread类的API:

  • (1)public void run():子类必须重写,它的方法体也称为线程体,即线程的任务代码
  • (2)public void start():线程启动必须用它
  • (3)public static void sleep(毫秒):休眠
  • (4)public String getName():线程的名称
  •  主线程的名称:main
    
  •  其他线程:默认是Thread-编号
    
  • (5)public static Thread currentThread()
  • (6)线程优先级
  • getPriority()
  • setPriority()
  •  优先级的范围:MIN_PRIORITY - MAX_PRIORITY ,[1,10]
    
  •  普通优先级:NORM_PRIORITY
    
  •  一共10个等级。
    
  • 优先级高:被CPU调度的概率增加,不表示低的没有机会。
  • 所以:不能依赖于优先级来解决先后的任务问题。
  • (7)public void interrupt()
  • (8)public void join():加塞
  • (9)public static void yield() :暂停当前线程,让出本次的CPU资源,加入下一次CPU的抢夺中
/*
 * 2、案例:编写龟兔赛跑多线程程序,设赛跑长度为30米
兔子的速度是10米每秒,兔子每跑完10米休眠的时间10秒
乌龟的速度是1米每秒,乌龟每跑完10米的休眠时间是1秒
要求,
(1)每跑1米,显示一下结果:xxx跑了几米,
	  休息时,显示一下:xxx在休息...
	  
(2)只要有跑完的,比赛就结束,最先跑完的就赢了,其他运动员就停下来别跑了	
相当于,只比冠军  
 */
public class TestExer08 {
	public static void main(String[] args) {
		//1、启动线程
		Sportman t = new Sportman("兔子",30,100,10000);
		Sportman w = new Sportman("乌龟",30,1000,1000);
		
		t.start();
		w.start();
		
		//2、判断是否有运动员跑完了
		while(true){
			//如果有人跑完了,停下所有线程
			if(t.isFinish() || w.isFinish()){
				t.interrupt();
				w.interrupt();
				
/*				t.stop();
				w.stop();*/
				
				t.setStop(true);
				w.setStop(true);
				
				break;
			}
		}
		
		//3、宣布结果
		//看谁先跑完,谁就赢了
		if(t.isFinish() && w.isFinish()){
			System.out.println(t.getName() + "," + w.getName() + "平局");
		}else if(t.isFinish()){
			System.out.println(t.getName() + "赢了");
		}else{
			System.out.println(w.getName() + "赢了");
		}
	}
}
class Sportman extends Thread{
	private int distance;//距离
	private long runMillsPerMeter;//每米的时间,毫秒
	private long restMillsPerTenMeter;//每10米休息的时间,毫秒
	private long totalTime;
	private volatile boolean finish;//默认值是false,如果跑完了,修改为true
	private boolean stop;//默认值是false
	
	public Sportman(String name, int distance ,long millsPerMeter, long restPerTenMeter) {
		super(name);
		this.distance = distance;
		this.runMillsPerMeter = millsPerMeter;
		this.restMillsPerTenMeter = restPerTenMeter;
	}

	public void run(){
		long start = System.currentTimeMillis();
		int i;
		for (i = 1; i <= distance && !stop; i++) {
			try {
				Thread.sleep(runMillsPerMeter);//模拟跑1米的时间
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//用线程名称代替运动员的名称
			System.out.println(getName() + "跑了" + i + "米");
			
			if(i<distance && i%10==0){
				System.out.println(getName() + "在休息....");
				try {
					Thread.sleep(restMillsPerTenMeter);//休息n秒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			
		}
		long end = System.currentTimeMillis();
		totalTime =  end - start;
		if(i >= distance){//不是中途结束,而是全程跑完的
			finish = true;
		}
	}

	public long getTotalTime() {
		return totalTime;
	}

	public boolean isFinish() {
		return finish;
	}

	public void setStop(boolean stop) {
		this.stop = stop;
	}
	
}

线程安全问题

举例:卖票

  • 假设,有10张票,分三个窗口同时卖
  • 1、线程安全问题:
  • 当多个线程使用“共享数据”时,就会有线程安全问题。
  • 当一个线程修改了“共享数据”,是会影响其他线程。
  • 2、如何解决?
  • 加锁
  • 形式一:同步代码块
  • 形式二:同步方法
  • 3、同步代码块
  • 语法格式:
  • synchronized(锁对象){
  •  需要加锁的代码
    
  • }
  • 锁对象,又称为监视器对象,同一时刻,某一段代码,只允许一个线程运行,这个锁就记录谁现在在运行,其他线程进不来。
  • 锁对象的选择:
  • (1)可以是任意类型的对象
  • (2)必须是这几天线程要使用同一个锁对象
  • 锁的代码的范围的选择:
  • (1)太大了:不行
  • (2)太小了:不行
  • 锁一次任务
public class Test09 {
	public static void main(String[] args) {
		Ticket t1 = new Ticket("窗口一");
		Ticket t2 = new Ticket("窗口二");
		Ticket t3 = new Ticket("窗口三");
		
		t1.start();
		t2.start();
		t3.start();
	}
}
class Ticket extends Thread{
	private static int total = 1000;
	private static Object lock = new Object();//锁的选择之一,单独造一个锁对象
	
	public Ticket(String name) {
		super(name);
	}

	public void run(){
	//	synchronized (this) {//这里使用this不行,因为这个this,对于三个线程来说不是同一个
		while(true){
			synchronized (lock) {
				if(total > 0){
					System.out.println(getName() + "卖出一张票");
					total--;
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("剩余:" + total);
				}else{
					break;
				}
			}
		}
	}
}

这一种更好,因为没有用到静态数据,静态数据生命周期太长,会影响之后对于这一类的使用

/*
 * 步骤:
 * (1)编写线程类,实现Runnable
 * (2)重写run
 * (3)创建线程对象
 * (4)启动线程
 */
public class Test10 {
	public static void main(String[] args) {
		Ticket t = new Ticket();
		
		Thread t1 = new Thread(t,"窗口一");
		Thread t2 = new Thread(t,"窗口二");
		Thread t3 = new Thread(t,"窗口三");
		
		t1.start();
		t2.start();
		t3.start();
	}
}
class Ticket implements Runnable{
	private int total = 10;

	@Override
	public void run() {
		while(true){
			synchronized (this) {//选择this当锁,可以,因为只有一个Ticket的对象
				if(total>0){
					System.out.println(Thread.currentThread().getName() +"卖出一张票");
					total--;
					System.out.println("剩余:" + total);
				}else{
					break;
				}
			}
		}
	}
	
}

/*
 * 同步方法的语法格式:
 * 【修饰符】 synchronized 返回值类型  方法名(【形参列表】)throws 异常列表{
 * }
 * 
 * synchronized 【修饰符】  返回值类型  方法名(【形参列表】)throws 异常列表{
 * }
 * 
 * 同步方法的锁对象,程序员无法选择:
 * (1)非静态方法:this
 * (2)静态方法:当前类的Class对象
 */
public class Test11 {
	public static void main(String[] args) {
		Ticket t1 = new Ticket("窗口一");
		Ticket t2 = new Ticket("窗口二");
		Ticket t3 = new Ticket("窗口三");
		
		t1.start();
		t2.start();
		t3.start();
	}
}
class Ticket extends Thread{
	private static int total = 10;
	
	public Ticket(String name) {
		super(name);
	}

	public void run(){
		while(total>0){//程序停止的条件
			saleOneTicket();
		}
	}
	
	public synchronized static void saleOneTicket(){
		if(total > 0){//线程安全问题的条件
			System.out.println(Thread.currentThread().getName() + "卖出一张票");
			total--;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("剩余:" + total);
		}
	}
	
	//同步方法,锁的是方法的一次调用过程
	//非静态方法的锁对象是this,这里使用this,不是合格的锁对象
	/*public synchronized void saleOneTicket(){
		if(total > 0){//线程安全问题的条件
			System.out.println(getName() + "卖出一张票");
			total--;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("剩余:" + total);
		}
	}*/
}

死锁

  • 两个线程,互相持有,占有对方想要的锁,不放手。

生产者,消费者问题

线程通信是用来解决生产者与消费者问题。
*

  • 生产者与消费者问题:
  • 有两个或者多个线程。
  • 其中一个/一部分线程,生产“数据”,称为生产者线程;
  • 另一个/一部分线程,消耗“数据”,称为消费者线程。
  • 这些数据放在一个“共享”区域。
  • 那么就会出现:
  • 当“共享”区域中的数据空了,消费者线程必须"停/等待",等待到产者线程生产了新数据后,继续进行。
  • 当“共享”区域中的数据满了,生产者线程必须"停下/等待",等到消费者线程消耗了数据后,继续进行。
  • 生产者与消费者问题:
  • (1)共享数据: 就会有线程安全问题,就需要同步
  • (2)共享区域大小固定,有限的:就需要用到“协作”,线程通信。
  • Object类中有:
  • (1)wait():必须由锁对象(线程的监视器对象)来调用。
  • (2)notify():必须由锁对象(线程的监视器对象)来调用。
  • notify()作用就是唤醒一个正在等待的线程。唤醒的是同一个锁对象监视的等待线程。
public class Test14 {
	public static void main(String[] args) {
		Workbench tai = new Workbench();
		
		Cook c = new Cook("崔志恒", tai);
		Waiter w = new Waiter("翠花", tai);
		
		c.start();
		w.start();
	}
}
class Workbench{
	//假设工作台上最多能够放10盘
	private static final int MAX = 10;
	private int count;
	
	//同步方法,非静态方法来说,锁对象就是this
	public synchronized void put(){//往工作台上放一盘菜
		if(count >= MAX){
			try {
				//生产者停下来,等待
				wait();//默认是this.wait()
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		count++;
		
		System.out.println(Thread.currentThread().getName() + "放了一盘菜,剩余:" + count);
		this.notify();
	}
	
	public synchronized void take(){//从工作台上取走一盘菜
		if(count<=0){
			try {
				//工作台没有菜,消费者应该停下来
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		count--;
		System.out.println(Thread.currentThread().getName() + "取走一盘菜,剩余:" + count);
		this.notify();
	}
}
class Cook extends Thread{
	private Workbench tai;

	public Cook(String name, Workbench tai) {
		super(name);
		this.tai = tai;
	}

	public void run(){
		while(true){
			tai.put();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
class Waiter extends Thread{
	private Workbench tai;
	
	public Waiter(String name, Workbench tai) {
		super(name);
		this.tai = tai;
	}

	public void run(){
		while(true){
			tai.take();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

改良

/*
 * 线程通信是用来解决生产者与消费者问题。
 * 
 * 生产者与消费者问题:
 * 	  有两个或者多个线程。
 * 	 其中一个/一部分线程,生产“数据”,称为生产者线程;
 * 	另一个/一部分线程,消耗“数据”,称为消费者线程。
 * 	这些数据放在一个“共享”区域。
 *  那么就会出现:
 *    当“共享”区域中的数据空了,消费者线程必须"停/等待",等待到产者线程生产了新数据后,继续进行。
 *    当“共享”区域中的数据满了,生产者线程必须"停下/等待",等到消费者线程消耗了数据后,继续进行。
 *    
 *  生产者与消费者问题:
 *  (1)共享数据:    就会有线程安全问题,就需要同步
 *  (2)共享区域大小固定,有限的:就需要用到“协作”,线程通信。
 *  
 *  Object类中有:
 *  (1)wait():必须由锁对象(线程的监视器对象)来调用。
 *  (2)notify():必须由锁对象(线程的监视器对象)来调用。
 *  notify()作用就是唤醒一个正在等待的线程。唤醒的是同一个锁对象监视的等待线程。
 *  (3)notifyAll():唤醒所有和我是同一个监视器对象的正在等待的线程
 */
public class Test16 {
	public static void main(String[] args) {
		Workbench tai = new Workbench();
		
		Cook c1 = new Cook("崔志恒", tai);
		Cook c2 = new Cook("甄玉禄", tai);
		Waiter w1 = new Waiter("翠花", tai);
		Waiter w2 = new Waiter("如花", tai);
//		Waiter w3 = new Waiter("秋香", tai);
		
		c1.start();
		c2.start();
		w1.start();
		w2.start();
//		w3.start();
	}
}
class Workbench{
	//假设工作台上最多能够放10盘
	private static final int MAX = 1;
	private int count;
	
	//同步方法,非静态方法来说,锁对象就是this
	public synchronized void put(){//往工作台上放一盘菜
		while(count >= MAX){
			try {
				//生产者停下来,等待
				wait();//默认是this.wait()
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		count++;
		System.out.println(Thread.currentThread().getName() + "放了一盘菜,剩余:" + count);
		this.notify();
	}
	
	public synchronized void take(){//从工作台上取走一盘菜
		while(count<=0){
			try {
				//工作台没有菜,消费者应该停下来
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		count--;
		System.out.println(Thread.currentThread().getName() + "取走一盘菜,剩余:" + count);
//		this.notify();
		this.notifyAll();
	}
}
class Cook extends Thread{
	private Workbench tai;

	public Cook(String name, Workbench tai) {
		super(name);
		this.tai = tai;
	}

	public void run(){
		while(true){
			tai.put();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
class Waiter extends Thread{
	private Workbench tai;
	
	public Waiter(String name, Workbench tai) {
		super(name);
		this.tai = tai;
	}

	public void run(){
		while(true){
			tai.take();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

单例设计模式(重要)

单例设计模式:
*

  • 单例:某个类只能有唯一的一个实例对象。
  • 如何实现单例?
  • 1、饿/恶汉式
  • 不管我们使用者是否需要这个对象,它都上来先给你创建好这个唯一的对象。
  • (1)枚举类型
  • (2)形式二
  • ①构造器私有化
  • ②用一个全局的静态的常量,来保存这个唯一的实例对象
  • (3)形式三
  • ①构造器私有化
  • ②用一个私有的静态的常量,来保存这个唯一的实例对象
  • ③提供一个静态方法,来返回这个常量对象
  • 2、懒汉式
  • 延迟创建对象。当使用者来或者这个对象,要用到对象时,我再创建。
  • (1)形式一:
  • 见下面,考虑线程安全问题和性能问题
  • (2)形式二:内部类形式
public class Test17 {
	@Test
	public void test1(){
		SingleEnum s1 = SingleEnum.INSTANCE;
		SingleEnum s2 = SingleEnum.INSTANCE;
		System.out.println(s1 == s2);
	}
	
	@Test
	public void test2(){
//		SingleEnum.test();//此时我并没有需要用到这个对象,但是它也创建出来了
	}
	
	@Test
	public void test3(){
		SingleClass s1 = SingleClass.INSTANCE;
		SingleClass s2 = SingleClass.INSTANCE;
		System.out.println(s1==s2);
	}
	
	@Test
	public void test4(){
		Single s1 = Single.getInstance();
		Single s2 = Single.getInstance();
		System.out.println(s1 == s2);
	}
	
	@Test
	public void test5(){
		LazyClass s1 = LazyClass.getInstance();
		LazyClass s2 = LazyClass.getInstance();
		System.out.println(s2 == s1);
	}
	
	LazyClass s1;
	LazyClass s2;
	@Test
	public void test6(){
		//匿名的内部类,继承Thread类
		Thread t1 = new Thread(){
			public void run(){
				s1 = LazyClass.getInstance();
			}
		};
		
		Thread t2 = new Thread(){
			public void run(){
				s2 = LazyClass.getInstance();
			}
		};
		
		t1.start();
		t2.start();
		
		try {
			//这里用join的目的是,为了两个子线程都执行完,再执行主线程的System.out.println(s1);
			t1.join();
			t2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(s1);
		System.out.println(s2);
		System.out.println(s1 == s2);
	}
	
}
enum SingleEnum{
	INSTANCE;
//	public static void test(){
//		//..
//	}
}
class SingleClass{
	public static final SingleClass INSTANCE = new SingleClass();
	private SingleClass(){
		
	}
}
class Single{
	private static final Single INSTANCE = new Single();
	private Single(){
		
	}
	public static Single getInstance(){
		return INSTANCE;
	}
}

class LazyClass{
	private static LazyClass instance;
	private LazyClass(){
		
	}
	
	public static LazyClass getInstance(){
		if(instance == null){//提高效率
			synchronized(LazyClass.class){//当前类的Class对象
				if(instance == null){//安全判断
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					instance = new LazyClass();
				}
			}
		}
		return instance;
	}
	
	//安全没问题,但是认为不是最优的
/*	public synchronized static LazyClass getInstance(){
		if(instance == null){
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			instance = new LazyClass();
		}
		return instance;
	}*/
	
	//有安全问题
/*	public static LazyClass getInstance(){
//		return new LazyClass();//错误的
		if(instance == null){
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			instance = new LazyClass();
		}
		return instance;
	}*/
}

class Lazy{
	private Lazy(){
		
	}
	
	private static class Inner{
		public static final Lazy INSTANCE = new Lazy();//在内部类中,创建外部类的唯一对象
	}
	
	public static Lazy getInstance(){
		return Inner.INSTANCE;
	}
}

本文地址:https://blog.csdn.net/qq_40473204/article/details/107575951