文章目录
并发编程三要素并发编程内存模型多线程创建线程的三种方式volatilesynchronized线程池ThreadPoolExcutor![在这里插入图片描述](https://img-/714b156cd3bd441d94a10396580ab638.png)阻塞队列线程中断基础线程机制线程之间的协作线程的状态并发编程三要素
原子性:和数据库事务特性的原子性类似,一组操作要么全部失败,要么全部成功可见性:某个线程对变量的修改,对其它线程是可见的有序性:java编译器为了提升效率,会改变代码顺序,这违背了有序性,通过happen-before先行发送原则可以保证有序性并发编程内存模型
多线程
多线程的目的是为了提高cpu的利用率,我们在学设计模式的时候单例模式的完美方案是要考虑多线程的,在学多线程我们需要了解操作系统讲的进程和线程、死锁,了解jvm的知识
创建线程的三种方式
继承Thread,一般不推荐,因为java是单继承的实现Runnable接口,重写run方法实现Callable接口,Callable 可以有返回值,返回值通过 FutureTask 进行封装,volatile
不满足原子性满足可见性可以通过内存屏障防止指令重排保证有序性参考: /tutorials/java-concurrency/volatile.html
场景
参考: /xichenguan/article/details/119425408
当一个变量依赖其他变量或变量的新值依赖旧值时,不能用volatile
适用场合:多个线程读,一个线程写的场合
使用场景:通常被 作为标识完成、中断、状态的标记,值变化应具有原子性
这里简单说一说状态标志:比如有个boolean变量,如果多个线程都要访问它,作为判断标志,这时就可以使用volatile修饰该变量,可以保证多线程对变量的可见性
synchronized
Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock
思考:volatile怎么保证可见性、volatile怎么保证有序性
参考博客:/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:工作队列,当线程数大于最大线程数时会将线程放到工作队列中,这里用的是阻塞队列BlockingQueuethreadFactory:线程工厂,用于创建工作线程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()
线程的状态
参考:/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资源,运行结束的状态;
线程的状态这个人讲的不错:/weixin_44673534/article/details/121344594