昆山网站建设,昌吉市静态管理公告最新,做网站的上市公司,下拉网站导航用ps怎么做文章目录 (一) 进程线程和协程的区别创建线程的4种方式1. 继承Thread类2. 实现runnable接口3. 实现Callable接口4. 线程池创建 runnable 和 callable 有什么区别线程的 run()和 start()有什么区别#xff1f;线程之间的状态变化notify()和 notifyAll()有什么区别#xff1f;j… 文章目录 (一) 进程线程和协程的区别创建线程的4种方式1. 继承Thread类2. 实现runnable接口3. 实现Callable接口4. 线程池创建 runnable 和 callable 有什么区别线程的 run()和 start()有什么区别线程之间的状态变化notify()和 notifyAll()有什么区别java 中 wait 和 sleep 方法的不同 (二) 线程中并发锁1synchronizedsynchronized关键字的底层原理synchronized关键字的底层原理-进阶 2) CAS3) volatile4) AQS5) ReentranLock6) synchronized和Lock的区别7 ConcurrentHashMap8 导致并发出现的根本原因 (三) 线程池1线程池的核心参数线程池中有哪些常见的阻塞队列 2如何确定核心线程数3 线程池的种类 (四) 对ThreadLocal的理解ThreadLocal基本使用ThreadLocal的实现原理源码解析 (一) 进程线程和协程的区别
程序由指令和数据组成但这些指令要运行数据要读写就必须将指令加载至CPU数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的。 当一个程序被运行从磁盘加载这个程序的代码至内存这时就开启了一个进程。
一个进程之内可以分为一到多个线程。 一个线程就是一个指令流将指令流中的一条条指令以一定的顺序交给 CPU 执行。 Java 中线程作为最小调度单位进程作为资源分配的最小单位。在 windows中进程是不活动的只是作为线程的容器 协程 协程英文Coroutines是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样一个线程也可以拥有多个协程。协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时将寄存器上下文和栈保存到其他地方在切回来的时候恢复先前保存的寄存器上下文和栈直接操作栈则基本没有内核切换的开销可以不加锁的访问全局变量所以上下文的切换非常快。协程与线程主要区别是它将不再被内核调度而是交给了程序自己而线程是将自己交给内核调度所以也不难理解golang中调度器的存在。
定义协程是轻量级线程。 在一个用户线程上可以跑多个协程这样就提高了单核的利用率。协程不像进程或者线程可以让系统负责相关的调度工作协程是处于一个线程中系统是无感知的所以需要在该线程中阻塞某个协程的话就需要手工进行调度。
总结 多进程的出现是为了提升CPU的利用率特别是I/O密集型运算不管是多核还是单核开多个进程必然能有效提升CPU的利用率。而多线程则可以共享同一进程地址空间上的资源为了降低线程创建和销毁的开销又出现了线程池的概念最后为了提升用户线程的最大利用效率又提出了协程的概念。
创建线程的4种方式
共有四种方式可以创建线程分别是继承Thread类、实现runnable接口、实现Callable接口线程池创建线程。
1. 继承Thread类
public class MyThread extends Thread {
Override
public void run() {
System.out.println(MyThread...run...);
}
public static void main(String[] args) {
// 创建MyThread对象
MyThread t1 new MyThread() ;
MyThread t2 new MyThread() ;
// 调用start方法启动线程
t1.start();
t2.start();
}
}2. 实现runnable接口
public class MyRunnable implements Runnable{
Override
public void run() {
System.out.println(MyRunnable...run...);
}
public static void main(String[] args) {
// 创建MyRunnable对象
MyRunnable mr new MyRunnable() ;
// 创建Thread对象
Thread t1 new Thread(mr) ;
Thread t2 new Thread(mr) ;
// 调用start方法启动线程
t1.start();
t2.start();
}
}3. 实现Callable接口
public class MyCallable implements CallableString {
Override
public String call() throws Exception {
System.out.println(MyCallable...call...);
return OK;
④ 线程池创建线程
}
public static void main(String[] args) throws
ExecutionException, InterruptedException {
// 创建MyCallable对象
MyCallable mc new MyCallable() ;
// 创建F
FutureTaskString ft new FutureTaskString(mc) ;
// 创建Thread对象
Thread t1 new Thread(ft) ;
Thread t2 new Thread(ft) ;
// 调用start方法启动线程
t1.start();
// 调用ft的get方法获取执行结果
String result ft.get();
// 输出
System.out.println(result);
}4. 线程池创建
public class MyExecutors implements Runnable{
Override
public void run() {
System.out.println(MyRunnable...run...);
}
public static void main(String[] args) {
// 创建线程池对象ExecutorService threadPool Executors.newFixedThreadPool(3);
threadPool.submit(new MyExecutors()) ;
// 关闭线程池
threadPool.shutdown();
}
}runnable 和 callable 有什么区别
Runnable 接口run方法没有返回值Callable接口call方法有返回值是个泛型和Future、FutureTask配合可以用来获取异步执行的结果Callalbe接口支持返回执行结果需要调用FutureTask.get()得到此方法会阻塞主进程的继续往下执行如果不调用不会阻塞。Callable接口的call()方法允许抛出异常而Runnable接口的run()方法的异常只能在内部消化不能继续上抛
线程的 run()和 start()有什么区别
start(): 用来启动线程通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。
run(): 封装了要被线程执行的代码可以被调用多次。
线程之间的状态变化 notify()和 notifyAll()有什么区别
notifyAll唤醒所有wait的线程 notify只随机唤醒一个 wait 线程
java 中 wait 和 sleep 方法的不同
共同点
wait() wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权进入阻塞状态 不同点方法归属不同 sleep(long) 是 Thread 的静态方法 而 wait()wait(long) 都是 Object 的成员方法每个对象都有醒来时机不同 执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来 wait(long) 和 wait() 还可以被 notify 唤醒wait() 如果不唤醒就一直等下去 它们都可以被打断唤醒锁特性不同重点 wati方法的调用必须先获取wait对象的锁而sleep无此限制 wait 方法执行后会释放对象锁允许其它线程获得该对象锁我放弃cpu但你们还可以用 而 sleep 如果在 synchronized 代码块中执行并不会释放对象锁
(二) 线程中并发锁
1synchronized
synchronized关键字的底层原理
Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有对象锁其他线程再想获取这个对象锁时就会阻塞住。
Synchronized的底层由monitor实现的monitor是jvm级别的对象 C实现线程获得锁需要使用对象锁关联monitor。
在使用了synchornized代码块时需要指定一个对象所以synchornized也被称为对象锁 monitor主要就是跟这个对象产生关联如下图 Monitor内部具体的存储结构
Owner存储当前获取锁的线程的只能有一个线程可以获取EntryList关联没有抢到锁的线程处于Blocked状态的线程WaitSet关联调用了wait方法的线程处于Waiting状态的线程 具体的流程代码进入synchorized代码块先让lock对象锁关联的monitor然后判断Owner是否有线程持有如果没有线程持有则让当前线程持有表示该线程获取锁成功如果有线程持有则让当前线程进入entryList进行阻塞如果Owner持有的线程已经释放了锁在EntryList中的线程去竞争锁的持有权非公平如果代码块中调用了wait方法则会进去waitSet中进行等待
synchronized关键字的底层原理-进阶
Monitor实现的锁属于重量级锁里面涉及到了用户态和内核态的切换、进程的上下文切换成本较高性能比较低。 在JDK 1.6引入了两种新型锁机制偏向锁和轻量级锁它们的引入是为了解决在没有多线程竞争或基本没有竞争的场景下因使用传统锁机制带来的性能开销问题。
对象的内存结构
MarkWord 2) CAS
CAS的全称是 Compare And Swap(比较再交换)它体现的一种乐观锁的思想在无锁情况下保证线程操作共享数据的原子性。 在JUC java.util.concurrent 包下实现的很多类都用到了CAS,
AbstractQueuedSynchronizerAtomicXXX 一个当前内存值V、旧的预期值A、即将更新的值B当且仅当旧的预期值A和内存值V相同时将内存值修改为B并返回true否则什么都不做并返回false。如果CAS操作失败则通过自旋的方式等待并再次尝试知道成功。
CAS 底层实现 CAS 底层依赖于一个 Unsafe 类来直接调用操作系统底层的 CAS 指令 ReentrantLock中的一段CAS代码
乐观锁和悲观锁
CAS 是基于乐观锁的思想最乐观的估计不怕别的线程来修改共享变量就算改了也没关系我吃亏点再重试呗。synchronized 是基于悲观锁的思想最悲观的估计得防着其它线程来修改共享变量我上来锁你们都别想改我改完了解开锁你们才有机会
3) volatile
一旦一个共享变量类的成员变量、类的静态成员变量被volatile修饰之后那 么就具备了两层语义
保证线程间的可见性禁止进行指令重排序
4) AQS
全称是 AbstractQueuedSynchronizer是阻塞式锁和相关的同步器工具的框架它是构建锁和其他同步组件的基础框架 AQS常见的实现类
ReentrantLock 阻塞式锁Semaphore 信号量CountDownLatch 倒计时锁
工作机制 线程0来了以后去尝试修改state属性如果发现state属性是0就修改state状态为1表示线程0抢锁成功线程1和线程2也会先尝试修改state属性发现state的值已经是1了有其他线程持有锁它们都会到FIFO队列中进行等待FIFO是一个双向队列head属性表示头结点tail表示尾结点
5) ReentranLock
ReentrantLock翻译过来是可重入锁相对于synchronized它具备以下特点
可中断可以设置超时时间可以设置公平锁支持多个条件变量与synchronized一样都支持重入
实现原理 ReentrantLock主要利用CASAQS队列来实现。它支持公平锁和非公平锁两者的实现类似
构造方法接受一个可选的公平参数默认非公平锁当设置为true时表示公平锁否则为非公平锁。公平锁的效率往往没有非公平锁的效率高在许多线程访问的情况下公平锁表现出较低的吞吐量。 查看ReentrantLock源码中的构造方法 提供了两个构造方法不带参数的默认为非公平 而Sync的父类是AQS所以可以得出ReentrantLock底层主要实现就是基于AQS来实现的。
工作流程
6) synchronized和Lock的区别
语法层面 synchronized 是关键字源码在 jvm 中用 c 语言实现Lock 是接口源码由 jdk 提供用 java 语言实现使用 synchronized 时退出同步代码块锁会自动释放而使用 Lock 时需要手动调用 unlock 方法释放锁 功能层面 二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能Lock 提供了许多 synchronized 不具备的功能例如获取等待状态、公平锁、可打断、可超时、多条件变量Lock 有适合不同场景的实现如 ReentrantLockReentrantReadWriteLock 性能层面 在没有竞争时synchronized 做了很多优化如偏向锁、轻量级锁性能不赖在竞争激烈时Lock 的实现通常会提供更好的性能
7 ConcurrentHashMap
ConcurrentHashMap 是一种线程安全的高效Map集合 底层数据结构 JDK1.7底层采用分段的数组链表实现 JDK1.8 采用的数据结构跟HashMap1.8的结构一样数组链表/红黑二叉树。 存储流程
先去计算key的hash值然后确定segment数组下标 再通过hash值确定hashEntry数组中的下标存储数据 在进行操作数据的之前会先判断当前segment对应下标位置是否有线程进行 操作为了线程安全使用的是ReentrantLock进行加锁如果获取锁是被会使 用cas自旋锁进行尝试
2 JDK1.8中concurrentHashMap 在JDK1.8中放弃了Segment臃肿的设计数据结构跟HashMap的数据结构是一样的数组红黑树链表 采用 CAS Synchronized来保证并发安全进行实现CAS控制数组节点的添加 synchronized只锁定当前链表或红黑二叉树的首节点只要hash不冲突就不会产生并发问题效率得到提升
8 导致并发出现的根本原因
1原子性
一个线程在CPU中操作不可暂停也不可中断要不执行完成要不不执行
解决方案 1.synchronized同步加锁 2.JUC里面的lock加锁
2内存可见性
内存可见性让一个线程对共享变量的修改对另一个线程可见
解决方案 synchronized volatile LOCK
3有序性
指令重排处理器为了提高程序运行效率可能会对输入代码进行优化它不保证程序中各个语句的执行先后顺序同代码中的顺序一致但是它会保证程序最终执行结果和代码顺序执行的结果是一致的
解决方案 volatile
(三) 线程池
1线程池的核心参数 corePoolSize 核心线程数目maximumPoolSize 最大线程数目 (核心线程救急线程的最大数目)keepAliveTime 生存时间 - 救急线程的生存时间生存时间内没有新任务此线程资源会释放unit 时间单位 - 救急线程的生存时间单位如秒、毫秒等workQueue - 当没有空闲核心线程时新来任务会加入到此队列排队队列满会创建救急线程执行任务threadFactory 线程工厂 - 可以定制线程对象的创建例如设置线程名字、是否是守护线程等handler 拒绝策略 - 当所有线程都在繁忙workQueue 也放满时会触发拒绝策略
工作流程
拒绝策略 1.AbortPolicy直接抛出异常默认策略 2.CallerRunsPolicy用调用者所在的线程来执行任务 3.DiscardOldestPolicy丢弃阻塞队列中靠最前的任务并执行当前任务 4.DiscardPolicy 直接丢弃任务
线程池中有哪些常见的阻塞队列
workQueue - 当没有空闲核心线程时新来任务会加入到此队列排队队列满会创建救急线程执行任务 比较常见的有4个用的最多是ArrayBlockingQueue和LinkedBlockingQueue 1.ArrayBlockingQueue基于数组结构的有界阻塞队列FIFO。 2.LinkedBlockingQueue基于链表结构的有界阻塞队列FIFO。 3.DelayedWorkQueue 是一个优先级队列它可以保证每次出队的任务都是当前队列中执行时间最靠前的 4.SynchronousQueue不存储元素的阻塞队列每个插入操作都必须等待一个移出操作。
2如何确定核心线程数
在设置核心线程数之前需要先熟悉一些执行线程池执行任务的类型
IO密集型任务 一般来说文件读写、DB读写、网络请求等 推荐核心线程数大小设置为2N1 N为计算机的CPU核数CPU密集型任务 一般来说计算型代码、Bitmap转换、Gson转换等 推荐核心线程数大小设置为N1 N为计算机的CPU核数
查看CPU核数
3 线程池的种类
创建使用固定线程数的线程池
不建议用Executors创建线程池 (四) 对ThreadLocal的理解
ThreadLocal是多线程中对于解决线程安全的一个操作类它会为每个线程都分配一个独立的线程副本从而解决了变量并发访问冲突的问题。ThreadLocal 同时实现了线程内的资源共享
案例使用JDBC操作数据库时会将每一个线程的Connection放入各自的ThreadLocal中从而保证每个线程都在各自的 Connection 上进行数据库的操作避免A线程关闭了B线程的连接。 ThreadLocal基本使用
三个主要方法
set(value) 设置值get() 获取值remove() 清除值
ThreadLocal的实现原理源码解析
ThreadLocal本质来说就是一个线程内部存储类从而让多个线程只操作自己内部的值从而实现线程数据隔离 在ThreadLocal中有一个内部类叫做ThreadLocalMap类似于HashMap。ThreadLocalMap中有一个属性table数组这个是真正存储数据的位置
Set方法
get方法和Remove方法
在使用ThreadLocal的时候强烈建议务必手动remove