本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2024-11(1)

Java学习笔记_Day022_多线程详解(二)

发布于2020-11-19 20:41     阅读(1568)     评论(0)     点赞(27)     收藏(2)


Java学习笔记_Day022_多线程详解(二)

课程内容

  1. 线程同步
  2. 线程协作
  3. 单例设计模式
  4. 线程生命周期
  5. 线程池

一、线程同步

(一)同步方法

  1. 同步代码块:在某段代码执行的时候,希望CPU不要执行那些会对当前线程数据产生干扰的线程上,所以给这段代码加上了同步代码块
  2. 如果某个方法中,所有的代码都要加上同步代码块,使用同步方法就可以让代码格式变得更加简洁
  3. 同步方法的格式:
    修饰符 【static】 synchronized 返回值类型 方法名称(参数列表) {
    需要保证同步的方法体
    }
  4. 同步锁对象
    (1)非静态方法:同步锁对象是this,即当前调用者,谁来调用这个方法,同步锁对象就是谁
    (2)静态方法:同步方法所在类的字节码对象,类名.class,哪个类调用这个同步方法,同步方法的锁对象就是哪个类的字节码对象

代码示例

package com.csdn.day022;

public class Demo02_SynchronizedMethod {

	public static void main(String[] args) {
		PrintString ps = new PrintString();
		
		new Thread() {

			@Override
			public void run() {
				while(true) {
					//ps.test1();
					PrintString.test1();
				}
				
			}
		}.start();
		
		new Thread() {

			@Override
			public void run() {
				while(true) {
					//ps.test2();
					PrintString.test2();
				}
				
			}
		}.start();
	}
	
}
/*
 * 非静态同步方法:同步锁对象是this
 * 静态同步方法:同步所对象是类的字节码对象
 * 
 * 
 * */
class PrintString {
	
	/*public synchronized void test1() {
		
		System.out.print("松");
		System.out.print("活");
		System.out.print("弹");
		System.out.println("抖");
	}
	
	public synchronized void test2() {
		System.out.print("闪");
		System.out.print("电");
		System.out.println("鞭");
	}*/
	
	/*public void test1() {
		
		synchronized (this) {
			System.out.print("松");
			System.out.print("活");
			System.out.print("弹");
			System.out.println("抖");
		}
	}
	
	public void test2() {
		
		synchronized (this) {
			System.out.print("闪");
			System.out.print("电");
			System.out.println("鞭");
		}
	}*/
	
	/*public static synchronized void test1() {
		
		System.out.print("松");
		System.out.print("活");
		System.out.print("弹");
		System.out.println("抖");
	}
	
	public static synchronized void test2() {
		
		System.out.print("闪");
		System.out.print("电");
		System.out.println("鞭");
	}*/
	
	public static void test1() {
		
		synchronized (PrintString.class) {
			System.out.print("松");
			System.out.print("活");
			System.out.print("弹");
			System.out.println("抖");
		}
	}
	
	public static void test2() {
		
		synchronized (PrintString.class) {
			System.out.print("闪");
			System.out.print("电");
			System.out.println("鞭");
		}
	}
}

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

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

(三)死锁

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

代码示例

package com.csdn.day022;

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张票,还剩几张

代码示例

package com.csdn.day022;

/**
 * 三个窗口,同时售卖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不同。它只是短暂的挂起当前线程,让别的线程先运行,而自己进入到准备运行的状态(就绪态)。在大多数视线中,线程只是让步于优先级相同或者跟高的线程。

代码示例

package com.csdn.day022;

public class Demo05_OtherMethod {

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

			@Override
			public void run() {
				for (int i = 1; i <= 1000; i++) {
					System.out.println(i);
				}
			}
		};
		
		Thread t2 = new Thread("=====线程1111") {

			@Override
			public void run() {
				
				Thread.yield();
				
				for (int i = 9000; i <= 9999; i++) {
					System.out.println(i + getName());
				}
			}
		};
		
		t2.start();
		t1.start();
		
	}

	public static void test1() {
		Thread t1 = new Thread() {

			@Override
			public void run() {
				for (int i = 1; i <= 1000; i++) {
					System.out.println(i);
				}
			}
		};
		
		Thread t2 = new Thread("=====线程1111") {

			@Override
			public void run() {
				
				try {
					t1.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				for (int i = 9000; i <= 9999; i++) {
					System.out.println(i + getName());
				}
			}
		};
		
		t1.start();
		t2.start();
	}
}

(二)线程协作

  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中,任意类型的对象都能调用

代码示例

package com.csdn.day022;

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. 饿汉式:一给食物就吃,类一加载就要去创建对象

代码示例

package com.csdn.day022;

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;
	}
}

(三)懒汉式

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

代码示例

package com.csdn.day022;

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;
	}
}

(四)饿汉式的第二种写法

代码示例

package com.csdn.day022;

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. 别名:线程的状态图、线程的生命周期图、线程的状态周期
  3. 状态罗列:
    (1)新建态:线程对象创建出来之后,从未start
    (2)就绪态:线程start了,但是CPU没有来临
    (3)运行态:正在运行的线程处于这种状态
    (4)阻塞态:线程主动休息、或者缺少一些执行所必须的资源,即使CPU来临也无法执行
    (5)死亡态:线程完成了业务逻辑,或者出现了异常打断了执行,或者线程被破坏

(二)状态转换图

在这里插入图片描述

(三)Java中关于线程状态的描述

  1. 线程的状态指示理论上的描述,Java中给出了更加精确的描述,可以将各个不同的状态,封装成对象,这些对象表示的状态可能和理论上的描述不太一样。
  2. Java语言中,有可以获取线程状态的方法:getState()
  3. Thread.state:Java语言中描述线程状态的枚举类型
  4. 因为线程的状态是有限的,所以该类型的对象,不需要手动创建,在类中已经创建好了,我们直接获取即可。每个对象,称为枚举项。
    (1)NEW:尚未start的线程
    (2)RUNNABLE:正在运行的线程
    (3)BLOCKED:等待锁对象
    (4)WAITING:被wait()的线程处于这种状态
    (5)TIMED_WAITING:有时限的等待,例如线程sleep
    (6)TERMINATED:死亡态线程

代码示例

package com.csdn.day022;

public class Demo10_GetState {

	public static void main(String[] args) {
		
		Thread t = new Thread() {

			@Override
			public void run() {
				for (int i = 0; i <= 10; i++) {
				}
			}
			
		};
		
		t.start();
		
		while (true) {
			System.out.println(t.getState());
		}
	}

	public static void test4() {
		Thread t = new Thread() {

			@Override
			public void run() {
				for (int i = 0; i <= 100; i++) {
					System.out.println(i);
					
					try {
						Thread.sleep(3000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			
		};
		
		t.start();
		
		while (true) {
			System.out.println(t.getState());
		}
	}

	public static void test3() {
		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();
		
		while (true) {
			System.out.println(t1.getState());
			System.out.println(t2.getState());
		}
	}

	public static void test2() {
		Thread t = new Thread() {

			@Override
			public void run() {
				while(true) {
					
				}
			}
			
		};
		
		t.start();
		
		System.out.println(t.getState());
	}

	public static void test1() {
		Thread t = new Thread();
		
		System.out.println(t.getState());
	}
}

五、线程池

(一)概述

  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() 结束线程池。试图结束正在执行的任务,不执行已经提交的任务,不接受新任务

代码示例

package com.csdn.day022;

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();
		
	}
}

总结:单例设计模式一定要好好理解,记忆现成的五种状态以及线程协作!

原文链接:https://blog.csdn.net/xxxx1982/article/details/109755851



所属网站分类: 技术文章 > 博客

作者:java之恋

链接:http://www.javaheidong.com/blog/article/944/5d3206670339cb560569/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

27 0
收藏该文
已收藏

评论内容:(最多支持255个字符)