当前位置: 移动技术网 > IT编程>开发语言>Java > Java 笔记 17:JavaSE:多线程基础,单例设计模式(重要)

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

2020年07月27日  | 移动技术网IT编程  | 我要评论
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.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

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网