java-day23

Day23-多线程

1.多线程

1.1 守护线程
1.1.1 概述

守护线程 又叫兜底线程

每个程序运行当中,都会默认开启一个守护线程,用于监听我们正常的程序

简单来说,就是没有任何一个线程的时候,JVM就需要退出了,这个时候守护线程也会退出,主要完成垃圾回收等功能

但是 我们可以使用Thread.setDameon() 方法 把某个线程设置为守护线程

但是必须在启动 static之前,否则报错

1.1.2 使用

package com.demo._Tread;

/**
 * 守护线程 又叫兜底线程
 * 
 * 每个程序运行当中,都会默认开启一个守护线程,用于监听我们正常的程序
 * 
 * 简单来说,就是没有任何一个线程的时候,JVM就需要退出了,这个时候守护线程也会退出,主要完成垃圾回收等功能
 * 
 * 但是 我们可以使用Thread.setDameon() 方法 把某个线程设置为守护线程
 * 
 * 但是必须在启动 start()之前设置,否则报错
 * 
 
 */
public class Thread_01_Daemon {
	public static void main(String[] args) {
		Thread t1 = new Processor_01();
		Thread t2 = new Processor_01();
		t1.setName("t1");
		t2.setName("t2");
		// 设置为守护线程
		t1.setDaemon(true);

		t1.start();
//		/*Exception in thread "main" java.lang.IllegalThreadStateException*/
//		t1.setDaemon(true);
		t2.start();
		for (int i = 0; i < 10; i++) {
			System.out.println("main-->" + i);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

class Processor_01 extends Thread {
	@Override
	public void run() {
		int i = 0;
		// 死循环
		while (true) {
			System.out.println(getName() + " = " + i++);
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

1.2 Timer

1.2.1 概述

定时器 计划任务,只要有一个任务监听 就会是一个线程

1 执行任务的类 , 2 执行任务起始时间 3 执行任务间隔时间

1.2.2 使用

package com.demo._Tread;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/**
 * 定时器 计划任务,只要有一个任务监听 就会是一个线程
 * 
 * 1 执行任务的类 , 2 执行任务起始时间 3 执行任务间隔时间
 * 

 */
public class Thread_02_Timer {

	public static void main(String[] args) throws ParseException {
		// 1 创建定时器
		Timer t = new Timer();

		// 执行任务对象
		LogTimerTask logTimerTask = new LogTimerTask();

		// 指定起始时间
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
		// 1 任务 , 2 起始时间 , 3 间隔时间 (毫秒)
		t.schedule(logTimerTask, sdf.parse("2021-01-30 11:04:00 000"), 1000 * 3);
	}
}

// 任务类
class LogTimerTask extends TimerTask {

	@Override
	public void run() {
		// 日期格式化对象
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
		// 当前系统时间
		Date date = new Date();
		// 格式化输出
		System.out.println(sdf.format(date));
		/**
		 * 2021-01-30 11:04:00 000
			2021-01-30 11:04:03 001
			2021-01-30 11:04:06 001
*/
	}
}

1.3 死锁

1.3.1 锁相关知识

如果访问了一个对象中加锁的成员方法,那么该对象中所有的加锁的成员方法全部锁定,都不能被其他线程访问

但是和静态无关,和其他对象也无关

如果访问了一个类加锁的静态方法,那么该类中所有的加锁的静态方法都被锁定,不能访问

但是和成员无关

package com.demo._Tread;


/**
 * 如果访问了一个对象中加锁的成员方法,那么该对象中所有的加锁的成员方法全部锁定,都不能被其他线程访问
 * 
 * 但是和静态无关,和其他对象也无关
 * 
 * 如果访问了一个类加锁的静态方法,那么该类中所有的加锁的静态方法都被锁定,不能访问
 * 
 * 但是和成员无关
 * 
 */
public class Thread_03_Synchronized {
	public static void main(String[] args) {
		MyClass_01 my = new MyClass_01();
		Thread t1 = new Thread(new Processor_03(my));
		Thread t2 = new Thread(new Processor_03(my));
		Thread t3 = new Thread(new Processor_03(my));
		t1.setName("t1");
		t2.setName("t2");
		t3.setName("t3");
		t1.start();
		// 睡眠一小会,保证让t1先执行,先进入加锁m1方法,并睡眠5秒
		try {
			Thread.sleep(500);//main线程先睡0.5秒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		// 因为t1进入m1后就加锁,并睡眠,查看t2和t3是否可以进入m2和m3,其中m2加锁,m3未加锁
		t2.start();
		t3.start();
	}
}

class Processor_03 implements Runnable {
	MyClass_01 my;

	public Processor_03(MyClass_01 my) {
		super();
		this.my = my;
	}

	@Override
	public void run() {
		if ("t1".equals(Thread.currentThread().getName())) {
			my.m1();
		} else if ("t2".equals(Thread.currentThread().getName())) {
			my.m2();
		} else if ("t3".equals(Thread.currentThread().getName())) {
			my.m3();
		}
	}
}

// 业务类
class MyClass_01 {
	public synchronized void m1() {
		System.out.println("加锁成员方法m1,即将进入睡眠");
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public synchronized void m2() {
		System.out.println("加锁成员方法m2");
	}

	public void m3() {
		System.out.println("未加锁成员方法m3");
	}
}

1.3.2 概述

死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃
自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续
解决方法
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步

死锁 : 就是大家在执行过程中,都遇到对方进入加锁方法中,导致大家都访问不了

原理 :
1 某个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象1
2 另一个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象2
3 在第一个线程中,要去访问对象2的时候,发现被锁定 了,只能等待
3 在第二个线程中,要去访问对象1的时候,发现被锁定了,只能等待

代码块锁
synchronized(xxx){} 代码块锁,可以锁类,也可以锁对象
如果锁对象,当访问该代码块锁的时候,该对象中所有的代码块锁和加锁的成员方法都被锁定
同理 访问对象中加锁的成员方法的时候,代码块锁也会被锁定
如果是锁类,当访问该代码块的时候,该类中所有的代码块锁和加锁的静态方法都被锁定
同理 访问类中加锁的静态方法的时候,代码块锁也会被锁定

1.3.3 代码实现

package com.demo._Tread;

/**
 * 死锁 : 就是大家在执行过程中,都遇到对方进入加锁方法中,导致大家都访问不了
 * 
 * 原理 : 
 * 		1 某个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象1
 * 		2 另一个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象2
 * 		3 在第一个线程中,要去访问对象2的时候,发现被锁定 了,只能等待
 * 		3 在第二个线程中,要去访问对象1的时候,发现被锁定了,只能等待
 * 
 * 代码块锁 
 * 		synchronized(xxx){} 代码块锁,可以锁类,也可以锁对象
 * 		如果锁对象,当访问该代码块锁的时候,该对象中所有的代码块锁和加锁的成员方法都被锁定
 * 		同理 访问对象中加锁的成员方法的时候,代码块锁也会被锁定		
 * 
 * 		如果是锁类,当访问该代码块的时候,该类中所有的代码块锁和加锁的静态方法都被锁定
 * 		同理 访问类中加锁的静态方法的时候,代码块锁也会被锁定
 * 
 * 
 * 实现思路 : 
 * 			1 两个线程
 * 			2 两个对象
 * 			3 两个线程中 保存的两个对象是相同的
 * 			4 线程1 先访问对象1 再嵌套访问对象2
 * 			5 线程2 先访问对象2 再嵌套访问对象1
 
 */
public class Thread_04_DeadLock {
	public static void main(String[] args) {
		Object o1 = new Object();
		Object o2 = new Object();
		Thread t1 = new Thread(new T1(o1,o2));
		Thread t2 = new Thread(new T2(o1,o2));
		t1.start();
		t2.start();
	}
}
class T1 extends Thread{
	Object o1 ;
	Object o2;
	
	public T1(Object o1, Object o2) {
		super();
		this.o1 = o1;
		this.o2 = o2;
	}

	@Override
	public void run() {
		synchronized (o1) {
			try {
				// 加入睡眠 保证一定会死锁,因为到这里先确保让t2把o2锁定
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (o2) {
				System.out.println("T1执行完成");
			}
		}
	}
}

class T2 extends Thread{
	Object o1 ;
	Object o2;
	
	public T2(Object o1, Object o2) {
		super();
		this.o1 = o1;
		this.o2 = o2;
	}

	@Override
	public void run() {
		synchronized (o2) {
			try {
				// 加入睡眠 保证一定会死锁,因为到这里先确保让t1把o1锁定
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (o1) {
				System.out.println("T2执行完成");
			}
		}
	}
}

1.4 线程通信

1.4.1 概述

在这里插入图片描述

wait() 与 notify() 和 notifyAll()
a)wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当 前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有 权后才能继续执行。
b)notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
c)notifyAll ():唤醒正在排队等待资源的所有线程结束等待.

注意:这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报
java.lang.IllegalMonitorStateException异常
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁, 因此这三个方法只能在Object类中声

Object 中的方法

wait() : 该线程进入等待状态,功能还在,只是没有运行,当被唤醒之后进入就绪状态,当执行的时候接着当前等待的状态继续执行

notify() : 唤醒该对象上等待中的某一个线程(优先级)

notifyAll() : 唤醒该对象上所有等待的线程

必须用在成员方法中,因为是对象相关,并且该成员方法必须加锁(synchronized)

wait : 无参 或者是传入 0 ,说明不会自动醒,只能被notifyAll/notify唤醒

也可以传入long类型的值,单位是毫秒数,过了指定时间之后自动醒

注意 sleep是Thread类中的静态方法,睡眠指定时间,不会交出锁(其他线程依旧不能交出该方法)

而 wait是Object中的成员方法,也就是每个对象都有的方法,挂起,会交出锁(其他线程就可以访问该方法了)

1.4.2 使用方式

package com.demo._Tread;

/**
 * Object 中的方法
 * 
 * wait() : 该线程进入等待状态,功能还在,只是没有运行,当被唤醒之后进入就绪状态,当执行的时候接着当前等待的状态继续执行
 * 
 * notify() : 唤醒该对象上等待中的某一个线程(优先级)
 * 
 * notifyAll() : 唤醒该对象上所有等待的线程
 * 
 * 必须用在成员方法中,因为是对象相关,并且该成员方法必须加锁(synchronized)
 * 
 * wait : 无参 或者是传入 0 ,说明不会自动醒,只能被notifyAll/notify唤醒
 * 
 * 也可以传入long类型的值,单位是毫秒数,过了指定时间之后自动醒
 * 
 * 注意  sleep是Thread类中的静态方法,睡眠指定时间,不会交出锁(其他线程依旧不能交出该方法)
 * 
 * 			而 wait是Object中的成员方法,也就是每个对象都有的方法,挂起,会交出锁(其他线程就可以访问该方法了)
 * 
 * 以打印奇数偶数为例
 * 		1 有一个业务类 Num ,其中有一个成员变量 count
 * 		2 业务类提供打印奇数和偶数的方法
 * 					1 奇数 printOdd : 打印奇数, count++ , 变成偶数,唤醒其他线程,自身进入wait等待
 * 					2 偶数 printEven : 打印偶数 count++ , 变成奇数,唤醒其他线程,自身进入wait等待
 * 		3 两个线程,保存同一个Num对象,分别调用 printOdd和printEven
 * 

 */
public class Thread_05_Wait {
	public static void main(String[] args) {
		Num num = new Num(1);
		Thread t1 = new PrintOdd(num);
		Thread t2 = new Thread(new PrintEven(num));
		t1.setName("t1");
		t2.setName("t2");
		t1.start();
		t2.start();
	}
}
// 打印奇数
class PrintOdd extends Thread{
	Num num;
	
	public PrintOdd(Num num) {
		super();
		this.num = num;
	}

	@Override
	public void run() {
		while (true) {
			num.printOdd();
		}
	}
}
class PrintEven implements Runnable{
	Num num;
	
	public PrintEven(Num num) {
		super();
		this.num = num;
	}

	@Override
	public void run() {
		while (true) {
			num.printEven();
		}
	}
}
// 打印偶数
// 业务类
class Num{
	int count ;

	public Num(int count) {
		super();
		this.count = count;
	}
	public synchronized void printOdd(){
		System.out.println(Thread.currentThread().getName()+"--->"+count);
		count++;
		
		// 唤醒其他线程
		this.notifyAll();
		
		// 自己进入挂起等待
		try {
			// 加入睡眠,效果更直观
			Thread.sleep(500);
			this.wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	public synchronized void printEven(){

		System.out.println(Thread.currentThread().getName()+"--->"+count);
		count++;
		
		// 唤醒其他线程
		this.notifyAll();
		
		// 自己进入挂起等待
		try {
			// 加入睡眠,效果更直观
			Thread.sleep(500);
			this.wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	
	}
}

1.4.3 面试题之生产者与消费者

思路 :
1 有一个业务类,SynStack 其中有一个成员变量 cnt 用来保存生产的个数,还需要一个容器来存储生产的数据char[]

2 业务类需要有对应的生产方法和消费方法

1 生产 push : 向数组中添加元素,需要判断数组是否满了,如果满了 就不生产,唤醒消费者
2 消费 pop : 同上,判断使用没有数据,如果没有 就唤醒生产者
3 两个线程分别调用生产和消费

wait 和 notify

package com.demo._Tread;

/**
 * 比较经典的面试题 : 生产者和消费者
 * 
 * 思路 : 
 * 		1 有一个业务类,SynStack 其中有一个成员变量 cnt 用来保存生产的个数,还需要一个容器来存储生产的数据char[]
 * 		2 业务类需要有对应的生产方法和消费方法
 * 				1 生产 push : 向数组中添加元素,需要判断数组是否满了,如果满了 就不生产,唤醒消费者
 * 				2 消费 pop : 同上,判断使用没有数据,如果没有 就唤醒生产者
 * 				3 两个线程分别调用生产和消费
 * 
 * wait 和 notify

 */
public class Thread_06_ProducerConsumer {
	public static void main(String[] args) {
		SynStack s = new SynStack();
		Thread t1 = new Thread(new Consumer(s));
		Thread t2 = new Thread(new Producer(s));
		t1.start();
		t2.start();
	}
}

// 消费线程类
class Consumer implements Runnable{
	SynStack s;
	
	public Consumer(SynStack s) {
		super();
		this.s = s;
	}

	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			s.pop();
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
// 生产线程类
class Producer implements Runnable{
	SynStack s;
	
	public Producer(SynStack s) {
		super();
		this.s = s;
	}

	@Override
	public void run() {
		char ch;
		for (int i = 0; i < 20; i++) {
			ch = (char)('a'+i);
			s.push(ch);
		}
	}
}
// 业务类
class SynStack{
	// 保存以生产的个数.同时也是下一个元素的下标
	int cnt = 0;
	char[] data = new char[6];
	public synchronized void push(char ch){
		// 生产个数如果和容器大小一致,说明生产满了,就不再生产
		if (cnt == data.length) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		// 先唤醒消费者 提醒消费
		notifyAll();
		// 把元素保存到数组中
		data[cnt] = ch;
		// 个数+1
		cnt++;
		System.out.println("生产了 : "+ch+" , 容器中剩余 "+cnt+" 个字符");
	}
	
	public synchronized char pop() {
		// 生产个数如果是0 就不进行消费
		if (cnt == 0) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		// 先唤醒生产者提醒生产
		notifyAll();
		// 先 -- 再取出,因为最后一个元素为个数-1 , 而 cnt是个数,所以要先--
		cnt--;
		char ch = data[cnt];
		System.out.println("消费了 : " + ch + " , 容器中剩余 " + cnt + " 个字符");
		return ch;
	}
}

1.5 单例模式

1.5.1 概述

单例模式目的 : 让一个类只创建一个对象

根据对象的创建时机不同,分为两种
懒汉模式 : 第一次使用的时候创建对象
饿汉模式 : 类加载的时候创建对象

实现步骤 :
1 构造方法私有化
2 公共的静态方法用于获取对象
3 私有化静态变量存储创建后的对象

1.5.2 单例模式编码

package com.demo.Singleton;

public class SingLethon_01 {
	//创建私有的构造方法
	private SingLethon_01() {

	}

	//创建私有静态变量,储存静态方法
	private static SingLethon_01 s = null;

	//创建公共静态方法,并返回地址
	public  static SingLethon_01 getInstance() {
		if (s == null) {
			synchronized (SingLethon_01.class) {
				if (s == null) {
					s = new SingLethon_01();
				}
			}
		}
		return s;
	}
}

package com.demo.Singleton;

/**
 * 单例模式目的 : 让一个类只创建一个对象
 * 
 * 根据对象的创建时机不同,分为两种
 * 			懒汉模式 : 第一次使用的时候创建对象
 * 			饿汉模式 : 类加载的时候创建对象
 * 
 * 实现步骤 : 
 * 		1 构造方法私有化
 * 		2 公共的静态方法用于获取对象
 * 		3 私有化静态变量存储创建后的对象
 * 
 
 */
public class Test {
	public static void main(String[] args) {
		// System.out.println(SingLethon_01.getInstance());
		// System.out.println(SingLethon_01.getInstance());
		// System.out.println(SingLethon_01.getInstance());
		// System.out.println(SingLethon_01.getInstance());
		Thread t1 = new AA();
		Thread t2 = new AA();
		Thread t3 = new AA();
		Thread t4 = new AA();
		Thread t5 = new AA();
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}
}
class AA extends Thread{
	@Override
	public void run() {
		System.out.println(SingLethon_01.getInstance());
//		com.demo.Singleton.SingLethon_01@f5c02ee
//		com.demo.Singleton.SingLethon_01@f5c02ee
//		com.demo.Singleton.SingLethon_01@f5c02ee
//		com.demo.Singleton.SingLethon_01@f5c02ee
//		com.demo.Singleton.SingLethon_01@f5c02ee
	}
}

1.5.3 问题-多线程环境下不行

1.5.3.1 分析原因

package com.demo.Singleton;

/**
 * 单例模式目的 : 让一个类只创建一个对象
 * 
 * 根据对象的创建时机不同,分为两种
 * 			懒汉模式 : 第一次使用的时候创建对象
 * 			饿汉模式 : 类加载的时候创建对象
 * 
 * 实现步骤 : 
 * 		1 构造方法私有化
 * 		2 公共的静态方法用于获取对象
 * 		3 私有化静态变量存储创建后的对象
 * 
 
 */
public class Test {
	public static void main(String[] args) {
		// System.out.println(SingLethon_01.getInstance());
		// System.out.println(SingLethon_01.getInstance());
		// System.out.println(SingLethon_01.getInstance());
		// System.out.println(SingLethon_01.getInstance());
		Thread t1 = new AA();
		Thread t2 = new AA();
		Thread t3 = new AA();
		Thread t4 = new AA();
		Thread t5 = new AA();
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}
}
class AA extends Thread{
	@Override
	public void run() {
		System.out.println(SingLethon_01.getInstance());
//com.demo.Singleton.SingLethon_01@118f2161
//com.demo.Singleton.SingLethon_01@118f2161
//com.demo.Singleton.SingLethon_01@118f2161
//com.demo.Singleton.SingLethon_01@f5c02ee
//com.demo.Singleton.SingLethon_01@1ec599cd
	}
}

package com.demo.Singleton;

public class SingLethon_01 {
	//创建私有的构造方法
	private SingLethon_01() {

	}

	//创建私有静态变量,储存静态方法
	private static SingLethon_01 s = null;

	//创建公共静态方法,并返回地址
	public  static SingLethon_01 getInstance() {
		if (s == null) {//可能有多个线程同时执行这个判断,判断s的时候为null,所以出现多次创建对象
			s = new SingLethon_01();
		}
		return s;
	}
}

1.5.3.2 解决方案1

package com.demo.Singleton;

public class SingLethon_01 {
	//创建私有的构造方法
	private SingLethon_01() {

	}

	//创建私有静态变量,储存静态方法
	private static SingLethon_01 s = null;

	//创建公共静态方法,并返回地址
	public synchronized static SingLethon_01 getInstance() {
		if (s == null) {
			s = new SingLethon_01();
		}
		return s;
	}
}

//com.demo.Singleton.SingLethon_01@f5c02ee
//com.demo.Singleton.SingLethon_01@f5c02ee
//com.demo.Singleton.SingLethon_01@f5c02ee
//com.demo.Singleton.SingLethon_01@f5c02ee
//com.demo.Singleton.SingLethon_01@f5c02ee


加锁 可以解决,一定不会出现问题

但是会有新的问题,
在这里插入图片描述

没有加锁前,出现问题,是因为,第一次执行的时候,多个线程并行执行到这个判断了
因为第一次执行,s是null,没有对象,所以导致创建多个对象

但是 一旦跳过第一次,后续不管多少个并发/并行 s都不再等于null,就不会再创建

而我们如果使用synchronized修饰方法的话,虽然结果一定没有问题,但是效率降低太多了

因为不仅仅是第一次,任何时候只要想来获取对象,都需要排队等待

而 我们这个程序中,其实只需要保证第一次排队等待即可,一旦创建了对象之后,则不需要排队,即使在这时候有很多并行线程同时执行,判断s==null的时候 也是false,因为有对象了

所以 以上编码 , 不是最佳选择

1.5.3.3 解决方案2

package com.demo.Singleton;

public class SingLethon_01 {
	//创建私有的构造方法
	private SingLethon_01() {

	}

	//创建私有静态变量,储存静态方法
	private static SingLethon_01 s = null;

	//创建公共静态方法,并返回地址
	public  static SingLethon_01 getInstance() {
		if (s == null) {
			synchronized (SingLethon_01.class) {
				if (s == null) {
					s = new SingLethon_01();
				}
			}
		}
		return s;
	}
}
//com.demo.Singleton.SingLethon_01@586f4a16
//com.demo.Singleton.SingLethon_01@586f4a16
//com.demo.Singleton.SingLethon_01@586f4a16
//com.demo.Singleton.SingLethon_01@586f4a16
//com.demo.Singleton.SingLethon_01@586f4a16

这样的话,只有第一次请求需要排队,一会就算有很多线程同时执行这个方法,也不会排队等待,因为方法没有加锁,多个线程 可以同时进来执行该方法
另外s已经在第一次请求的时候赋值过了,所以判断s==null时 也是false

所以 这种写法才是多线程环境下的最佳选择

1.6 线程池

1.6.1 概述

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。
好处:
a)提高响应速度(减少了创建新线程的时间)
b)降低资源消耗(重复利用线程池中线程,不需要每次都创建)
c)便于线程管理
i.corePoolSize:核心池的大小
ii.maximumPoolSize:最大线程数
iii.keepAliveTime:线程没有任务时最多保持多长时间后会终止

线程池的作用 :

线程池作用就是限制系统中执行线程的数量

根据系统的环境情况,可以自动或者手动来设置线程数量,以达到运行的最佳效果
少了浪费系统资源,多了造成系统拥挤效率不高
用线程池控制线程数量,其他线程排队等候

一个任务 执行完成,再从队列中取最前面的任务开始执行
如果队列中没有等待进程,线程池的这个资源处于等待状态
当一个新任务需要运行时,如果此时线程池中还有等待中的工作线程时,可以直接开始运行
否则需要进入等待队列

为什么要使用线程池
1 减少了创建 和销毁线程的次数,因为每个工作线程都可以被重复使用,可执行多个任务
2 可以根据系统的承受能力,调整线程池中的线程数量,防止因为消耗过多的内存,导致服务器死机
(每个线程需要大概1MB内存,线程开的越多,消耗内存越大,最后导致死机)

1.6.2使用方式

JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
 Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

1.6.2.1 NewCachedThreadPool

创建一个可根据需要创建新线程的线程池
// 创建一个可缓存线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程
// 若没有可以回收的,则新建线程,线程池规模不存在限制,数量不固定

package com.demo.pool;

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

/**
 * 线程池的作用 :
 * 		
 * 	线程池作用就是限制系统中执行线程的数量
 * 根据系统的环境情况,可以自动或者手动来设置线程数量,以达到运行的最佳效果
 * 少了浪费系统资源,多了造成系统拥挤效率不高
 * 用线程池控制线程数量,其他线程排队等候
 * 
 * 一个任务 执行完成,再从队列中取最前面的任务开始执行
 * 如果队列中没有等待进程,线程池的这个资源处于等待状态
 * 当一个新任务需要运行时,如果此时线程池中还有等待中的工作线程时,可以直接开始运行
 * 否则需要进入等待队列
 * 
 * 为什么要使用线程池
 * 		1 减少了创建 和销毁线程的次数,因为每个工作线程都可以被重复使用,可执行多个任务
 * 		2 可以根据系统的承受能力,调整线程池中的线程数量,防止因为消耗过多的内存,导致服务器死机
 * 				(每个线程需要大概1MB内存,线程开的越多,消耗内存越大,最后导致死机)
 
 */
public class _01_NewCachedThreadPoolTest {
	public static void main(String[] args) {
		// 创建一个可缓存线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程
		// 若没有可以回收的,则新建线程,线程池规模不存在限制,数量不固定

		// 创建池子
		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
		for (int i = 0; i < 10; i++) {
			// 执行任务,传入了Runnable的匿名内部类,也可以是实现类对象
			cachedThreadPool.execute(new Runnable() {
				@Override
				public void run() {
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName());
				}
			});
		}
		System.out.println("----------");
		// 关闭线程池
		cachedThreadPool.shutdown();
	}
}

1.6.2.2 NewFixedThreadPool

创建一个固定 长度线程池,可控制线程最大并发数

超出此数量的线程,会在队列中等待

package com.demo.pool;

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

/**
 * 创建一个固定 长度线程池,可控制线程最大并发数
 * 
 * 超出此数量的线程,会在队列中等待

 */
public class _02_NewFixedThreadPoolTest {
	public static void main(String[] args) {
		// 创建池子,并指定最高 并发3个
		//如果创建10线程池,就是并行了
		ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
		for (int i = 0; i < 10; i++) {
			// 执行任务,传入了Runnable的匿名内部类,也可以是实现类对象
			fixedThreadPool.execute(new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName());
				}
			});
		}
		// 关闭线程池
		fixedThreadPool.shutdown();
	}
}

1.6.2.3 NewScheduledThreadPool

创建一个固定长度线程池,支持定时及周期性执行任务

package com.demo.pool;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 创建一个固定长度线程池,支持定时及周期性执行任务
 * 
 
 */
public class _03_NewScheduledThreadPoolTest {

	public static void main(String[] args) {
		// test1();
		test2();
	}

	/**
	 * 延迟执行
	 */
	public static void test1() {
		// 创建池子,并指定数量5
		ScheduledExecutorService scheduledThreadPool = Executors
				.newScheduledThreadPool(5);
		// 执行
		// 1 执行的任务, 2 延迟时间 , 3 延迟时间的单位
		scheduledThreadPool.schedule(new Runnable() {
			@Override
			public void run() {
				System.out.println("延迟3秒再执行");
			}
		}, 3, TimeUnit.SECONDS);

		// 关闭池子
		scheduledThreadPool.shutdown();
	}

	/**
	 * 延迟并间隔执行
	 */
	public static void test2() {
		// 创建池子,并指定数量5
		ScheduledExecutorService scheduledThreadPool = Executors
				.newScheduledThreadPool(5);
		// 执行
		// 1 执行的任务, 2 延迟时间 , 3, 时间间隔 , 4 延迟时间的单位
		scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				System.out.println("延迟3秒再执行,并且每2秒执行一次");
			}
		}, 3, 2, TimeUnit.SECONDS);
	}
}

1.6.2.4 NewSingleThreadExcutor

单线程线程池,只创建一个线程,如果这个 线程因为异常结束,那么会有一个新的线程来替代他

该线程保证所有的任务的执行顺序,按照任务的提交顺序执行,谁先来谁先执行

适用于一个一个任务执行的情况

package com.demo.pool;

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

/**
 * 单线程线程池,只创建一个线程,如果这个 线程因为异常结束,那么会有一个新的线程来替代他
 * 
 * 该线程保证所有的任务的执行顺序,按照任务的提交顺序执行,谁先来谁先执行
 * 
 * 适用于一个一个任务执行的情况
 * 

 */
public class _04_NewSingletThreadExecutor {
	public static void main(String[] args) {
		// 创建池子
		ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
		
		// 一次性创建10个请求
		for (int i = 0; i < 10; i++) {
			final int index = i;
			System.out.println("创建 "+i+" 次");
			// 只能依次执行
			singleThreadExecutor.execute(new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(1000);
						System.out.println(Thread.currentThread().getName()+" : "+index);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
		}
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值