多线程学习
Posted qqkkovo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程学习相关的知识,希望对你有一定的参考价值。
一、简介
程序:指令和数据的集合
进程:程序的一次执行过程,是系统资源分配的基本单位
线程:是cpu调度和执行的单位
二、线程实现
- 继承Thread
- ThreadImpl extends Thread
- 重写run()
- new Thread().start()
- 实现Runnable接口
- ThreadImpl implements Runnable
- 重写run()
- new Thread(new ThreadImpl( )).start()
- 实现Callable接口
- ThreadImpl implements Callable< call()的返回值 >
- 重写call()
- TestCallable t1 = new TestCallable()
- 创建执行服务 ExcutorService ser = Excutors.newFixedThreadPool(3);创建池子数量
- 提交执行 Future< bollean > result = ser.submit( t1 );
- 获取结果:result.get()
- 关闭服务:ser.shutdownNow();
? 推荐使用实现Runnable接口,避免了单继承的局限性,灵活方便,方便同一个对象被多个线程使用
三、Lambda表达式
- λ是希腊字母中,排第十一的字母==>英语名称为Lamda
- 优点:
- 避免匿名内部类过多,代码看起更加简洁;
- 去掉了一堆没有意义的代码,只留下核心的逻辑
- 实质属于函数式编程
(param)->expression [ 表达式 ]
(param)->statement [ 语句 ]
(param)->{statements}
lambda演化:外部类 --> 静态内部类 --> 局部类 --> 匿名内部类(借助接口或父类实现) --> lambda表达式【 (参数)-> { 方法体 } 】
函数式接口
定义:只包含一个抽象方法。
对于函数式接口,可以通过Lamda表达式来创建该接口的对象
四、线程状态
-
新建:线程被创建,处于新建状态
-
就绪:线程对象调用start()方法开启线程,等待被cpu调度执行
-
运行:就绪状态的线程获得了cpu时间片,执行run()方法
-
阻塞: 线程放弃了对cpu的使用权,暂时停止运行
? 等待阻塞:线程调用wait()方法,进入等待阻塞
? 同步阻塞:线程获取synchronized同步锁失败,进入同步阻塞
? 其他阻塞:线程调用sleep()方法,进入阻塞状态
wait() sleep() 会释放锁,属于Object类 不会释放锁,属于Thread类 只能在同步方法或者代码块里使用,不需要抛异常 可以在任何地方使用,需要抛出异常 需要notify()或notifyAll()唤醒 不需要唤醒,休眠之后自动退出阻塞 -
死亡:线程执行完了run()方法,或者因为异常而退出了run()方法,线程的生命周期结束
方法 | 说明 |
---|---|
setPriority( int ) | 更改线程的优先级 |
sleep( long ) | 指定的毫秒内休眠 |
join() | 等待线程终止 |
yield() | 让出cpu,再和其他线程一起争抢cpu |
interrupt() | 中断线程,别用 |
isAlive() | 测试线程是否处于活动状态 |
守护线程(setDaemon(true))
- 线程分为用户线程和守护线程
- jvm必须确保用户线程执行完毕,不用等待守护线程执行完毕
- 比如:后台记录操作日志,监控内存,垃圾回收等待。。。
死锁
? 两个或多个线程,同时阻塞,都在等待某个资源的释放,而这个资源又被其他的线程占用,所以线程就一致阻塞,导致线程死锁。比如:现在有A、B两个线程,同时申请对方的资源,就会相互等待,线程一直处于等待状态,导致死锁。
? 一个同步代码块同时拥有 " 两个以上对象的锁 ",就可能发生 " 死锁 "。
线程死锁的四个条件:
- 互斥:一个资源任意时刻,只能被一个线程占用
- 不剥夺:线程资源在使用完之前,不能被其他线程剥夺
- 请求和保持:线程保持了多个资源,又发出了新的资源请求,该资源被其他线程占用
- 循环等待:在资源等待链中,每一个线程获得的资源同时,被下一个线程请求
破坏死锁:破坏四个条件中的一种, 互斥条件没办法破坏。
- 破坏不剥夺:申请不到资源时,可以主动释放当前占有的资源
- 破坏请求和保持:一次性申请所有资源
- 破坏循环等待:按照顺序请求和释放资源
五、线程同步
? 一、synchronized
? 为了保证数据在方法中被正确访问的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源其他线程必须等待,存在以下问题:
- 一个线程持有锁会导致其他需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级导致,引起性能问题
CopyOnWriteArrayList是线程安全的集合
同步方法以及同步块
? private关键字保证数据只能被方法访问,所以只需要对方法提出一套机制--->synchronized关键字,它包括两种用法,同步方法和同步代码块
- 同步方法:public synchronized void method(){},锁的是this,缺陷—>影响效率
- 同步代码块:synchronized (Obj){},需要锁的是增删改的对象
二、Lock
? JDk5.0开始,提供了更强大的线程同步机制——通过显示显示定义同步锁。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,访问资源之前应先获得Lock对象。
? ReentrantLock(可重入锁)类实现了Lock,拥有和synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁和释放锁。
三、Lock和Synchronized的对比
Lock | Synchronized |
---|---|
显示锁,需要手动开启和关闭锁 | 隐式锁,出了作用域会自动释放锁 |
只能锁代码块 | 锁代码块和方法 |
JVM花费更少的时间去调度线程,性能好 |
六、线程间通信
java提供了几个方法解决了线程间的通信问题
方法名 | 描述 |
---|---|
wait() | 线程一直等待,直到其他线程通知,会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 随机唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait()的线程,优先级别高的优先调度 |
生产者消费者问题
这是一个线程同步问题,生/消共享同一个资源,仅有一个synchronized是不够的
- synchronized可以阻止并发更新同一个共享资源,实现了同步
- 但是,不能实现线程间的通信
解决方式一:管程法
- 生产者:负责生产数据的模块(模块可以是方法、对象、线程、进程)
- 消费者:负责处理数据的模块(模块可以是方法、对象、线程、进程)
- 缓冲区:消费者不能直接使用生产者的数据,他们之间有个 " 缓冲区 " ; 生产者将生产好的数据放入缓冲区,消费者从缓冲区取出数据
package thread;
/**
*
* @author 柯神_
* @date 2020-11-29 17:17:17
* @Description 线程间通信,
* 生产者消费者问题
* 解决方式:一、管程法
* 二、信号灯法
*/
public class ThreadCommunication {
public static void main(String[] args) {
Buffer buffer = new Buffer();
Producer producer = new Producer(buffer);
Consumer consumer = new Consumer(buffer);
new Thread(producer).start();
new Thread(consumer).start();
}
}
/**
* 一、管程法
*/
//生产者
class Producer implements Runnable{
Buffer buffer;
public Producer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
buffer.push(new Product(i));
System.out.println("生产了" + i + "只鸡!");
}
}
}
//消费者
class Consumer implements Runnable{
Buffer buffer;
public Consumer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了==>" + buffer.getP().id + "只鸡!");
}
}
}
//产品
class Product{
int id;
public Product(int id) {
this.id = id;
}
}
//缓冲区
class Buffer{
//定义一个容器大小
Product[] products = new Product[10];
//容器计数器
int count = 0;
//1.生产者生产产品
public synchronized void push(Product product){
/**
* 缓冲区满了,生产等待,通知消费
*/
if (count == products.length - 1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 缓冲区未满,可以生产
*/
products[count] = product;
count++;
/**
* 有产品了
* 通知消费者消费
*/
this.notifyAll();
}
//2.消费者消费产品
public synchronized Product getP(){
/**
* 缓冲区空了,等待消费,通知生产
*/
if (count == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 缓冲区有产品,可以消费
*/
count--;
Product product = products[count];
/**
* 吃了,缓冲区没满了
* 通知生产者生产
*/
this.notifyAll();
return product;
}
}
解决方式二:信号灯法
信号灯法是通过标志位来实现
/**
* 二、信号灯法:通过标志位来实现
*/
package thread;
import lombok.SneakyThrows;
/**
* 二、信号灯法:通过标志位来实现
*/
public class ThreadCommunication2 {
public static void main(String[] args) {
TV tv = new TV();
Player player = new Player(tv);
Watcher watcher = new Watcher(tv);
new Thread(player).start();
new Thread(watcher).start();
}
}
//生产者 ==>演员录制
class Player implements Runnable{
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
this.tv.play("录制快本中。。。");
} else {
this.tv.play("新闻联播录制...");
}
}
}
}
//消费者 ==>观众
class Watcher implements Runnable{
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
tv.watch();
}
}
}
//产品 ==>小视频节目
class TV{
private String name;
private boolean flag = true; //标志位
/**
* 表演录制:观众等待
*/
@SneakyThrows
public synchronized void play(String name){
if (!flag){
wait();
}
System.out.println("演员表演了" + name);
this.notify();
this.name = name;
this.flag = !this.flag;
}
/**
* 观众观看:演员等待
*/
@SneakyThrows
public synchronized void watch(){
if (flag){
this.wait();
}
System.out.println("观众观看=======>" + name);
this.notifyAll();
this.flag = !this.flag;
}
}
七、线程池
背景:线程频繁地创建和销毁,对系统的性能影响很大
线程池:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,实现资源重复利用。
优点:
- 提高了响应速度(减少了创建线程的时间)
- 降低了资源的消耗(线程使用完放回池中,实现资源重复利用)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时,多长时间后终止
JDK5.0开始,提供了线程池相关的API:ExcutorService 和 Excutors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExcutor
- shutdown()关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。
package thread;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*
* @author 柯神_
* @date 2020-11-29 20:21:10
* @Description
* 测试线程池
*/
public class PoolTest {
public static void main(String[] args) {
//创建线程服务,线程池,参数为线程池的大小
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new MyPool());
service.execute(new MyPool());
service.execute(new MyPool());
service.execute(new MyPool());
service.shutdown();
}
}
class MyPool implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
以上是关于多线程学习的主要内容,如果未能解决你的问题,请参考以下文章