多线程拓展
守护线程
守护线程又叫做兜底线程,每当程序运行,都会默认开启一个守护线程,用于监听我们正常的程序。
守护线程就是在当前线程执行完毕,守护线程就会跟着结束执行,注意完成垃圾回收等功能。
使用setDaemon()方法把某个线程设置为守护线程
但是必须在启动线程之前设置守护线程,否则会报错
如:
public class Thread_01_Daemon {
public static void main(String[] args) {
Thread thread1 = new Processor_01();
thread1.setName("Thread->1->");
//设置守护线程
thread1.setDaemon(true);
thread1.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(Thread.currentThread().getName() + " = " + i++);
try {
sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在main线程执行完毕后,thread1线程会立即结束运行。
Timer定时器
在JDK类库中Timer类主要负责计划任务的功能,也就是在指定的时间开始执行某一个任务。此类也常用来做一下周期性同步工作。
使用schedule()方法设置定时器
方法参数为:
- 需要执行的任务类
- 执行任务的起始时间
- 执行任务的时间间隔
如:
public class Thread_02_Timer {
public static void main(String[] args) {
//创建定时器
Timer timer = new Timer();
//执行任务对象
LogTimerTask logTimerTask = new LogTimerTask();
//指定起始时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
//任务 起始时间 间隔时间(毫秒)
try {
timer.schedule(logTimerTask, sdf.parse("2021-01-30 21:24:58 150"), 1000 * 3);
} catch (ParseException e) {
e.printStackTrace();
}
//System.out.println(sdf.format(new Date()));
}
}
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));
}
}
死锁
锁的相关知识
如果访问了一个对象中加锁的成员方法,那么该对象中所有的加锁的成员方法全部锁定,都不能被其他线程访问。和静态方法无关
如果访问了一个类加锁的静态方法,那么该类中所有的加锁的静态方法都被锁定,不能访问。和成员方法无关。
如测试代码:
public class Thread_03_Synchronized {
public static void main(String[] args) {
MyClass_01 myClass_01 = new MyClass_01();
Thread thread1 = new Thread(new Processor_03(myClass_01));
Thread thread2 = new Thread(new Processor_03(myClass_01));
Thread thread3 = new Thread(new Processor_03(myClass_01));
thread1.setName("t1");
thread2.setName("t2");
thread3.setName("t3");
thread1.start();
//睡一下会儿,保证thread1先执行,先进入加锁m1方法,并睡眠5秒
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
thread3.start();
}
}
class Processor_03 implements Runnable{
MyClass_01 myClass_01;
public Processor_03(MyClass_01 myClass_01) {
this.myClass_01 = myClass_01;
}
@Override
public void run() {
if ("t1".equals(Thread.currentThread().getName())){
myClass_01.m1();
}else if ("t2".equals(Thread.currentThread().getName())){
myClass_01.m2();
}else if ("t3".equals(Thread.currentThread().getName())){
myClass_01.m3();
}
}
}
//业务类
class MyClass_01 {
public synchronized void m1() {
System.out.println(Thread.currentThread().getName()+"****"+"加锁的成员方法m1,即将进入睡眠");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void m2() {
System.out.println(Thread.currentThread().getName()+"****"+"加锁的成员方法m2");
}
public void m3() {
System.out.println(Thread.currentThread().getName()+"****"+"未加锁的成员方法m3");
}
}
代码块锁
语法格式:
synchronized(xxx){} 代码块锁,可以锁类,也可以锁对象。
如果锁对象,括号中应填写对象名,当防卫该代码块锁的时候,该对象中所有的代码块锁和加锁的成员方法都被锁定。同理访问对象中加锁的成员方法的时候,代码快锁也会被锁定。
如果锁类,当访问该代码块的时候,该类中所有的代码块锁和加锁的静态方法都被锁定,同理,访问类中加锁的静态方法的时候,代码块锁也会被锁定。
死锁
两个线程分别占用对方需要的同步资源,等在等待对象释放资源。出现死锁后,不会出现异常,不会出现提示,所有的线程都处于阻塞状态,程序无法继续执行。
原理:
- 某个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象1
- 另一个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象2
- 在第一个线程中,要去访问对象2的时候,发现被锁定了,只能等待
- 在第二个线程中,要去访问对象1的时候,发现被锁定,只能等待
实现死锁的思路:
- 两个线程
- 两个对象
- 两个线程中保存的对象时相同的
- 线程1先访问对象1再嵌套访问对象2
- 线程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 T1(o1, o2);
Thread t2 = new T2(o1, o2);
t1.start();
t2.start();
}
}
class T1 extends Thread {
Object o1;
Object o2;
public T1(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o1) {
System.out.println("*************");
//加入睡眠,保证一定会死锁,因为到这里先确保让t2把o2锁定
try {
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) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2) {
System.out.println("-------------------");
//加入睡眠,保证一定会死锁,因为到这里先确保让t1把o1锁定
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("T2执行完成");
}
}
}
}
线程通信
使用Object中方法实现
- wait():该线程进入等待状态,功能还在,只是没有运行,当被唤醒之后进入就绪状态,当执行的时候接着当前等待的状态继续执行。
必须用在成员方法中,因为是对象相关,并且该成员方法必须加上synchronized。
无参或者传入0,说明不会自动唤醒,只能被notifyAll()/notify唤醒。
也可以传入long类型的值,单位是毫秒,过了指定时间之后自动醒。 - notify():唤醒该对象上等待中的某一个线程(优先级)
- notifyAll():唤醒该对象上所有等待的线程
实现生产者和消费者问题:
实现思路:
- 有一个业务类,SunStack 其中有一个成员变量cnt用来保存生产的个数,还需要一个容器来存储生产的数据char[]。
- 业务类需要有对应的生产方法和消费方法
① 生产push:想数组中添加元素,需要判断数组是否满了,如果满了就不生产,唤醒消费者
② 消费pop:同上,判断使用没有数据,如果没有就唤醒生产者
③ 两个线程分别调用生产和消费
如实现:
public class Thread_06_ProducerConsumer {
public static void main(String[] args) {
SynStack synStack = new SynStack();
Thread thread1 = new Thread(new Consumer(synStack));
Thread thread2 = new Thread(new Producer(synStack));
thread1.start();
thread2.start();
}
}
//消费者
class Consumer implements Runnable {
SynStack synStack;
public Consumer(SynStack synStack) {
this.synStack = synStack;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
synStack.pop();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//生产者
class Producer implements Runnable {
SynStack synStack;
public Producer(SynStack synStack) {
this.synStack = synStack;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
synStack.push((char) ('a' + i));
}
}
}
class SynStack {
//保存生产的个数,同时也是下一个元素的下标
int cnt = 0;
char[] data = new char[6];
public synchronized void push(char c) {
//生产个数如果和容器大小一致,说明生产满了,就不再生产
if (cnt == data.length) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//唤醒消费者
notifyAll();
//把元素保存到数组中
data[cnt] = c;
//容器中个数+1
cnt++;
System.out.println("生产了 : " + c + " , 容器中剩余 " + cnt + " 个字符");
}
public synchronized char pop() {
//容器中个数为0,就不进行消费
if (cnt == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//唤醒生产者
notifyAll();
//cnt先进行--操作,因为cnt保存的是下一个元素的小标
cnt--;
char c = data[cnt];
System.out.println("消费了 : " + c + " , 容器中剩余 " + cnt + " 个字符");
return c;
}
}
线程单例模式的使用
原因:
线程启动后,如果同时调用单例模式创建对象的方法,将会都在if中判断为null,多个线程会创建不同的单例模式的对象,为了避免这个问题,将getInstance()方法同步。
如果在方法声明上加入synchronized可以实现,但是一旦跳过第一次,后续不管多少个并发/并行singLethon_01都不会等于null,就不会再创建。而我们使用synchronized,效率会降低太多,除了第一次,以后使用这个方法,都需要排队等待。降低了程序的运行效率。
所以需要在方法中使用同步代码块来实现。并且在同步代码块中需要再次判断对象是否为null,否则第一次执行时,虽然都在排队,当第一个线程创建完对象,第二个线程会进入同步代码块继续创建一个对象。
如:
ublic class SingLethon_01 {
//私有化构造方法
private SingLethon_01() {
}
//创建私有化静态变量,类名为当前类类名
private static SingLethon_01 singLethon_01 = null;
//加锁可以实现
//但是一旦跳过第一次,后续不管多少个并发/并行singLethon_01都不会等于null,就不会再创建
//而我们使用synchronized,效率会降低太多,除了第一次,以后使用这个方法,都需要排队等待
//而程序中,只需要第一次排队等待即可
/*public synchronized static SingLethon_01 getInstance() {
if (singLethon_01 == null) {
singLethon_01 = new SingLethon_01();
}
return singLethon_01;
}*/
public static SingLethon_01 getInstance() {
if (singLethon_01 == null) {
//添加第二层判断是因为如果有线程都执行到了这一行,在排队等待
//第一个线程进入创建完对象后
//第二个线程进入,没有if判断会再次创建对象
synchronized (SingLethon_01.class) {
if (singLethon_01 == null) {
singLethon_01 = new SingLethon_01();
}
}
}
return singLethon_01;
}
}
这样创建就可以保证创建出来的都是同一个对象
线程池的使用
概述
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。
使用线程池的好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理.
JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors。
- ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor。
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
- Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
- Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
- Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
创建一个可根据需要创建新线程的线程newCachedThreadPool
创建一个可缓存的线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程。
若没有可以回收的,则新建线程,线程池规模不存在限制,数量不固定
如:
public class _01_NewCachedThreadPoolTest {
public static void main(String[] args) {
//创建线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0;i<1000;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();
}
}
创建一个固定长度线程池newFixedThreadPool
可控制线程最大并发数,超出此数量的线程,会在队列中等待。
如:
public class _02_NewFixedThreadPoolTest {
/*
* 创建一个固定长度线程池,可控制线程最大并发数
* 超出此数量的线程,会在队列中等待
*
* */
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 100; i++) {
fixedThreadPool.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("=============");
fixedThreadPool.shutdown();
}
}
创建定时及周期性执行任务newScheduledThreadPool
有两种实现方法:
- 延迟执行
- 延迟并间隔执行
需传入执行的任务,延迟时间,延迟时间单位
如:
public class _03_NewScheduledThreadPool {
public static void main(String[] args) {
// 创建池子,并指定数量5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
//执行
//执行的任务,延迟时间,延迟时间的单位
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("延迟3秒再执行");
}
}, 3, TimeUnit.SECONDS);
//关闭池子
scheduledThreadPool.shutdown();
}
}
需传入执行的任务,延迟时间,时间间隔,延迟时间单位。
注意:不可以关闭池子,否则无法运行。
如:
public class _03_NewScheduledThreadPool {
public static void main(String[] args) {
// 创建池子,并指定数量5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
//执行
//执行的任务,延迟时间,时间间隔,延迟时间的单位
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("延迟3秒再执行,并且每2秒执行一次");
}
}, 3, 2, TimeUnit.SECONDS);
//不可以关闭池子,关闭池子该方法无法运行
//关闭池子
//scheduledThreadPool.shutdown();
}
}
单线程池newSingleThreadExecutor
只创建一个线程,如果这个线程因为异常或正常结束,则会有一个新的线程来替代它。
执行顺序按照先来后到顺序执行,谁先来谁先执行。
适用于一个一个任务执行的情况
如:
public class _04_NewSingletThreadExecutor {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
//一次性创建十个请求
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();
}
}
});
}
}
}