多线程(补充)

多线程

1.1 线程守护

守护线程 又叫兜底线程

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

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

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

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

1.2 Timer

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

1.3 死锁

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

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

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

但是和成员无关

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃
自己需要的同步资源,就形成了线程的死锁
解决方法
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步

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

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

代码块锁
synchronized(xxx){} 代码块锁,可以锁类,也可以锁对象

如果锁对象,当访问该代码块锁的时候,该对象中所有的代码块锁和加锁的成员方法都被锁定
访问对象中加锁的成员方法的时候,代码块锁也会被锁定

锁类,当访问该代码块的时候,该类中所有的代码块锁和加锁的静态方法都被锁定
访问类中加锁的静态方法的时候,代码块锁也会被锁定

1.4 线程通信

wait() 与 notify() 和 notifyAll()

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

b) notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待

c) notifyAll ():唤醒正在排队等待资源的所有线程结束等待.

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

wait : 无参 或者是传入 0 ,说明不会自动醒,只能被notifyAll/notify唤醒
也可以传入long类型的值,单位是毫秒数,过了指定时间之后自动醒

注意 sleep是Thread类中的静态方法,睡眠指定时间,不会交出锁(其他线程依旧不能交出该方法)
而 wait是Object中的成员方法,也就是每个对象都有的方法,挂起,会交出锁(其他线程就可以访问该方法了)

1.5 面试题之生产者和消费者

1.6 单例模式

单例模式目的 : 让一个类只创建一个对象
根据对象的创建时机不同,分为两种
懒汉模式 : 第一次使用的时候创建对象
饿汉模式 : 类加载的时候创建对象
实现步骤 :
构造方法私有化
公共的静态方法用于获取对象
私有化静态变量存储创建后的对象

1.7 线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。

好处
a) 提高响应速度(减少了创建新线程的时间)
b) 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
c) 便于线程管理

i. corePoolSize:核心池的大小
ii. maximumPoolSize:最大线程数
iii. keepAliveTime:线程没有任务时最多保持多长时间后会终止

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

一个任务 执行完成,再从队列中取最前面的任务开始执行

如果队列中没有等待进程,线程池的这个资源处于等待状态

当一个新任务需要运行时,如果此时线程池中还有等待中的工作线程时,可以直接开始运行,否则需要进入等待队列

为什么要使用线程池?
1 减少了创建和销毁线程的次数,因为每个工作线程都可以被重复使用,可执行多个任务

2 可以根据系统的承受能力,调整线程池中的线程数量,防止因为消耗过多的内存,导致服务器死机(每个线程需要大概1MB内存,线程开的越多,消耗内存越大,最后导致死机)

1.7.1 使用方式

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):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

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

猜你喜欢

转载自blog.csdn.net/MIRACLE_Ying/article/details/113420034