java并发编程:多线程基础
文章目录
- 并发编程三要素
- 并发编程内存模型
- 多线程
- 创建线程的三种方式
- volatile
- synchronized
- 线程池
- ThreadPoolExcutor![在这里插入图片描述](https://img-blog.csdnimg.cn/714b156cd3bd441d94a10396580ab638.png)
- 阻塞队列
- 线程中断
- 基础线程机制
- 线程之间的协作
- 线程的状态
并发编程三要素
- 原子性:和数据库事务特性的原子性类似,一组操作要么全部失败,要么全部成功
- 可见性:某个线程对变量的修改,对其它线程是可见的
- 有序性:java编译器为了提升效率,会改变代码顺序,这违背了有序性,通过happen-before先行发送原则可以保证有序性
并发编程内存模型
多线程
多线程的目的是为了提高cpu的利用率,我们在学设计模式的时候单例模式的完美方案是要考虑多线程的,在学多线程我们需要了解操作系统讲的进程和线程、死锁,了解jvm的知识
创建线程的三种方式
- 继承Thread,一般不推荐,因为java是单继承的
- 实现Runnable接口,重写run方法
- 实现Callable接口,Callable 可以有返回值,返回值通过 FutureTask 进行封装,
volatile
参考: https://jenkov.com/tutorials/java-concurrency/volatile.html
- 不满足原子性
- 满足可见性
- 可以通过内存屏障防止指令重排保证有序性
场景
参考: https://blog.csdn.net/xichenguan/article/details/119425408
当一个变量依赖其他变量或变量的新值依赖旧值时,不能用volatile
适用场合:多个线程读,一个线程写的场合
使用场景:通常被 作为标识完成、中断、状态的标记,值变化应具有原子性
这里简单说一说状态标志:比如有个boolean变量,如果多个线程都要访问它,作为判断标志,这时就可以使用volatile修饰该变量,可以保证多线程对变量的可见性
synchronized
Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock
思考:volatile怎么保证可见性、volatile怎么保证有序性
参考博客:https://blog.csdn.net/weixin_37990128/article/details/110955558
四个窗口卖500张票:加sychronized
package interview.sort.test;public class SellTicket implements Runnable {int ticket = 500;public void sellTicket() throws InterruptedException {synchronized (this) {while (ticket > 0) {Thread.sleep(10);System.out.println(Thread.currentThread().getName() + "卖出第" + ticket-- + "张票");}}}@Overridepublic void run() {try {sellTicket();} catch (InterruptedException e) {e.printStackTrace();}}
}class Test {public static void main(String[] args) {SellTicket sellTicket = new SellTicket();Thread t1 = new Thread(sellTicket);Thread t2 = new Thread(sellTicket);Thread t3 = new Thread(sellTicket);Thread t4 = new Thread(sellTicket);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t4.setName("窗口4");t1.start();t2.start();t3.start();t4.start();}
}
四个窗口卖500张票:加可重入锁ReentrantLock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class SellTickets implements Runnable {int ticket = 500;private Lock lock = new ReentrantLock();public void sellTicket() throws InterruptedException {lock.lock();while (ticket > 0) {Thread.sleep(10);System.out.println(Thread.currentThread().getName() + "卖出第" + ticket-- + "张票");}lock.unlock();}@Overridepublic void run() {try {sellTicket();} catch (InterruptedException e) {e.printStackTrace();}}
}class Test {public static void main(String[] args) {SellTickets sellTicket = new SellTickets();Thread t1 = new Thread(sellTicket);Thread t2 = new Thread(sellTicket);Thread t3 = new Thread(sellTicket);Thread t4 = new Thread(sellTicket);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t4.setName("窗口4");t1.start();t2.start();t3.start();t4.start();}
}
四个窗口卖500张票:使用AtomicInteger实现用的是底层是CAS算法
import java.util.concurrent.atomic.AtomicInteger;public class SellTicket implements Runnable {AtomicInteger ticket = new AtomicInteger(500);public void sellTicket() throws InterruptedException {while (ticket.get() > 0) {Thread.sleep(10);System.out.println(Thread.currentThread().getName() + "卖出第" + ticket.decrementAndGet() + "张票");}}@Overridepublic void run() {try {sellTicket();} catch (InterruptedException e) {e.printStackTrace();}}
}class Test {public static void main(String[] args) {SellTicket sellTicket = new SellTicket();Thread t1 = new Thread(sellTicket);Thread t2 = new Thread(sellTicket);Thread t3 = new Thread(sellTicket);Thread t4 = new Thread(sellTicket);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t4.setName("窗口4");t1.start();t2.start();t3.start();t4.start();}
}
区别
- 实现:synchronized是jvm实现的,ReentrantLock是jdk实现的
- 性能:sychronized做了优化,速度和ReentrantLock差不多,前者的锁,jvm会帮我们自动释放,后者需要手动释放锁
- 公平:公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁,synchronized是公平锁,ReetrantLock是非公平锁
- 中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情,ReentrantLock 可中断,而 synchronized 不行
- 绑定多对象:ReentrantLock可以绑定多个Condition对象
线程池
建立线程池好处:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消- 耗。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
常见线程池
- CachedThreadPool:一个任务创建一个线程;
- FixedThreadPool:所有任务只能使用固定大小的线程;
- SingleThreadExecutor:相当于大小为 1 的 FixedThreadPool;
- newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
- newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池
- newWorkSteadingThreadPool: 创建一个抢占式执行的线程池(任务执行顺序不确定),此方法是 JDK 1.8 版本新增的
ExecutorService executorService1 = Executors.newScheduledThreadPool(12);
ThreadPoolExecutor是Executor的实现类,可以通过它的execute()方法创建线程,构造方法里面需要设置很多参数
ThreadPoolExcutor
- corePoolSize: 核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:空闲线程存活时间,当线程数量大于核心线程数小于最大线程数时,存在空闲的线程它们的存活时间就是这个,当存活的线程数小于等于核心线程数就失效了
- unit:空闲线程的存活时间的单位
- workQueue:工作队列,当线程数大于最大线程数时会将线程放到工作队列中,这里用的是阻塞队列BlockingQueue
- threadFactory:线程工厂,用于创建工作线程
- handler: 当工作队列达到上限的时候采取拒绝策略,常见的拒绝策略就是抛出异常
阻塞队列
ArrayBlockingQueue:先进先出,阻塞队列,插入元素时队列满了会一直等待。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。
SynchronousQueue:数组结构的阻塞队列。队列满了以后,任何一次插入操作的元素都要等待相对的删除/读取操作。
LinkedBlockingQueue:先进先出,链表结构非阻塞队列,适合插入和删除较多的操作。
DelayedWorkQueue:延迟队列,延迟的时间还未到的获取的任务会返回空。
PriorityBlockingQueue:优先级队列,元素必须实现Comparable接口,优先级最高的始终在队列的头部,任务会优先执行。
线程中断
- 在捕获异常中抛出中断异常
- 将interrupted()方法当作标志状态,做判断,没中断在执行后面的语句
- Executor可以通过shutdown()中断所有线程,如果要中断某一个线程就是用submit()方法会返回一个Future对象,该对象调用cancel(true)方法中断线程
基础线程机制
线程睡眠sleep():单位是毫秒,是个本地方法,执行此方法会让线程睡眠
yield():是个本地方法,执行此方法代表当前线程已经执行差不都了,可以切换另外的线程;该方法只是向线程调度器提供一个建议,建议具有相同优先级的其它线程可以运行
守护线程setDaemon():这时final修饰的方法,执行此方法是将线程设置为守护线程(后台线程),守护线程是指在后台执行的一种服务线程,这种线程不是必须的,当所有的非守护线程执行完毕后,程序停止,会杀死所有的守护线程
线程优先级setPriority():final修饰的方法,为线程设置优先级,可以是1-10级,10级最高,目的是为了告诉线程调度器优先执行优先级高的线程
线程之间的协作
join(): final()修饰的方法,执行此方法当前线程将会挂起,执行目标线程后,在执行当前线程
wait()、notify()、notifyAll()
- 调用wait()方法会使当前线程挂起,会释放锁,其它线程执行满足这个条件时其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程
- final修饰,是本地方法,并且是Object里面的方法
- 只能在同步方法或者同步控制块中使用
sleep()和wait()的区别
- sleep()是Thread类的方法,wait()是Object的方法
- 执行wait()时会释放锁,sleep()则不会释放锁
- wait()只能在synchronized中使用
await()、signal()、signalAll()
- juc包下Condition的方法,一个Lock可以创建多个Condition,synchronized块搭配wait()、notify()、notifyAll(),相当于仅有一个Condition,所有线程的调度通信都是由这个Condition完成的,不够灵活
- await()对用wait(),signal()对用notify(),signalAll()对用notifyAll()
线程的状态
参考:https://www.cnblogs.com/IUbanana/p/7110297.html
新建状态(New):新线程对象已经创建,还没有在其上调用start()方法
就绪状态(Runnable):当前线程调用了start()方法,随时等待CPU调度执行
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
- 等待阻塞 – 运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
- 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
- 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,当前线程的任务已处理完毕,释放CPU资源,运行结束的状态;
线程的状态这个人讲的不错:https://blog.csdn.net/weixin_44673534/article/details/121344594
java并发编程:多线程基础相关推荐
- Java并发编程-多线程基础
Java多线程基础 1.多线程概述 实现线程的两种方式 继承Thread类 实现Runnable接口 2.线程生命周期 获取线程的名字和线程对象 3.线程的休眠 sleep方法 终止线程的休眠 强行终 ...
- java 并发编程多线程_多线程(一)java并发编程基础知识
线程的应用 如何应用多线程 在 Java 中,有多种方式来实现多线程.继承 Thread 类.实现 Runnable 接口.使用 ExecutorService.Callable.Future 实现带 ...
- 学习笔记:Java 并发编程①_基础知识入门
若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 视频链接:https://www.bilibili.com/video/av81461839 视频下载: ...
- Java并发编程实战基础概要
文章目录 Java并发编程实战基础概要 开篇 多线程问题有啥难点呢? 为啥要学习并发编程? 并发问题的根源是什么? CPU切换线程执导致的原子性问题是如何发生的? 缓存导致的可见性问题是如何发生的? ...
- 并发编程-多线程基础
1.引言 推荐书籍 深入理解Java并发编程 Java并发编程 核心知识点 多线程基础知识 同步和异步的概念 线程安全(线程同步)相关 线程通讯 java1.8并发包 线程池原理分析 锁的概念 专题类 ...
- JAVA并发编程的基础
1.线程简介 什么是线程? 操作系统在运行一个程序时,会为其创建一个进程. 线程是操作系统调度的最小单元,也叫轻量级进程. 在一个进程里可以创建多个线程,这些线程拥有各自的计数器.堆栈和局部变量等属性 ...
- java并发编程艺术——基础篇
这篇文章目的是为了总结一下这段时间看<java并发编程艺术>学到的东西,尝试用自己的话说出来对java多线程的理解和使用. 一.什么是多线程,为什么要用多线程,多线程带来的挑战 多线程定义 ...
- Java并发编程(多线程)中的相关概念
众所周知,在Java的知识体系中,并发编程是非常重要的一环,也是面试中必问的题,一个好的Java程序员是必须对并发编程这块有所了解的. 并发必须知道的概念 在深入学习并发编程之前,我们需要了解几个基本 ...
- JAVA并发编程JUC基础学习(简介)
2019独角兽企业重金招聘Python工程师标准>>> 之前写过一篇并发编程的简单实例应用,Future快速实现并发编程,可以很快的在自己的项目中应用,但并不系统,之前说过总结一篇( ...
最新文章
- tensorflow兼容处理 tensorflow.compat.v1 tf.contrib
- keras cnn注意力机制_从发展历史视角解析Transformer:从全连接CNN到Transformer
- matlab无限长序列卷积,怎样求未知长度序列的卷积
- Python 制作微信全家福,你就是朋友圈最亮的仔!
- 支持字典_【多测师小课堂】python数据类型之列表、字典、元祖、字符串
- linux的ctrl alt f6的作用,Linux(Centous6.4)操作系统中,快捷键Alt+Ctrl+F10是什么作用?...
- JavaScript新手入门教程大全~~~
- android so导致启动慢,谈谈Android NDK中动态链接库(.so文件)的优化
- 使用 CSS 模拟鼠标点击交互
- 【经典】MIT人工智能实验室: 如何做研究?
- Ubuntu上安装NS3(最详细的图文介绍)
- 一张图慢慢转换成下一张图_给一张照片做一个视频 如何把一张图片制作成几分钟的视频|图片做成视频软件...
- 快递查询单号查询追踪,一键查询全部物流
- 时间格式化 几分钟前 几小时前 几天前
- 比例谐振(PR)控制
- python通信达数据_Python读取通达信数据
- html/css做一个简单的个人简历
- 传统特征点检测器的检测特征点和匹配流程
- 角色转移服务器维护怎么回事,梦幻西游角色转移热点问题解答
- Excel取消合并自动填充