1.进程是程序加载到内存之后被cpu计算的过程
线程是CPU调度的最小单位
区别:线程是进程的子集,一个进程可以有很多线程 每条程执行不同的任务
线程调度方法:抢占式线程调度 协同式线程调度 优先级
2.在java中实现线程
1)继承Thread类()
2)实现runnable接口(主流 相对于1 jav不支持多继承 但允许调用多个接口)
3)实现callable接口(相对于2 可以有返回值和抛出异常)
4)应用程序可以使用Executor框架创建线程池
3.start()与run()的区别
start方法被用来启动新创建的线程 s内部会调用r方法
和直接调用run方法不同 直接调用run方法时 只是会在原来的线程中调用 没有启动新的线程
wait()和sleep()的区别
wait()会释放锁而sleep会一直持有锁 wait()用于线程交互sleep()用于暂停执行
wait()是object方法 sleep()是Thread方法
4.java内存模型JMM!!!
定义了jvm在计算机内存中的工作方式 JMM隶属JVM
规定和指引Java程序在不同的内存架构 CPU和操作系统间有确定性的行为
线程之间的通信和同步
通信:1)共享内存 2)消息传递 wait()和notify()
同步:指不同线程之间操作发生相对顺序的机制 1)当共享内存是显示 2)消息 传递是隐式
Java并发多个线程或者进程同时访问同一资源
JAVA并发采用的是共享内存模型
同步:发送一个请求,等待返回,然后再发送下一个请求
异步:发送一个请求,不等待返回,随时可以再发送下一个请求
并发:同时发送多个请求
线程之间的共享变量存储在主内存中 每个线程都有一个私有的本地内存,本地内存中存储了该线程读写共享变量的副本
硬件内存架构和java内存模型是一个交叉关系 当对象和变量存储到计算机的内个内存是,必然会面临一些问题 1)共享对象对各个线程的可见性 2)共享对象的竞争现象
1)共享对象的可见性 多个线程同时操作同一个共享对象是,没有合理使用volatile和synchronization关键字 会导致对象对象的更新导致其他线程不可见
2)竞争现象 多个线程共享一个对象 同时修改这个共享对象 产生竞争现象
支撑Java内存模型的基础原理
1)指令重排序 2)内存屏障 3)happens-before
5.线程安全
如果代码所在的进程有多个线程同时运行 如果每次运行结果和单线程运行的结果是一样的 而且其他的变量的值也和预期是一样的 就是线程安全 Vector是同步方法来实现线程安全的
6.停止一个线程
jdk1.0本来有stop() suspend() resume() 但是由于潜在的思索威胁被弃用了 没有提供兼容且线程安全的方法来停止一个线程 当run()或者call()方法执行完线程会自动结束 如果手动结束一个线程 使用vilatile布尔变量退出run()或取消任务来终端线程
7.线程运行时发生异常
如果没有捕获该线程会停止执行
8.notify()和notifyAll()区别
notify()不能唤醒某个具体的线程 notifyAll()唤醒所有线程并允许争夺锁
9.wait,notify和notifyAll不在thread类的原因:他们都是锁级别的操作 锁操作的是对象
10.ThreadLocal变量
每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量 如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。首先,通过复用减少了代价高昂的对象的创建个数。其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。
11.死锁
线程互相等待
避免死锁
1)加锁顺序 确保所有线程都是按照相同的顺序获得锁
2)加锁时限 尝试获取锁的时候加一个超时时间
3)死锁检测 当一个线程获得锁 会在线程和锁相关的数据结构中将其记下
当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生
①释放所有锁②给线程设置优先级
12.锁的类型
内存可见性 原子性(互斥性) 可重入性
1)自旋锁 当前线程不停的在循环体内执行实现
由于自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。
2)阻塞锁
改变了线程的运行状态
阻塞锁,可以说是让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争,进入运行状态。
阻塞锁的优势在于,阻塞的线程不会占用cpu时间, 不会导致 CPu占用率过高,但进入时间以及恢复时间都要比自旋锁略慢。
在竞争激烈的情况下 阻塞锁的性能要明显高于 自旋锁。
理想的情况则是; 在线程竞争不激烈的情况下,使用自旋锁,竞争激烈的情况下使用,阻塞锁。
3)读写锁
4)互斥锁
5)悲观锁:悲观锁悲观认为每次查询都会造成更新丢失,所以在查询时手动加排他锁,从而防止其他并发事务的查询/修改,从而防止更新丢失问题。
6)乐观锁:乐观锁乐观的认为,每次更新都不会造成更新丢失,正常的查询和更新,只有检测到发生了更新丢失,才进行补救。乐观锁通常通过数据库中增加一个版本字段来工作。
如果查询比较多,更新比较少,用乐观锁。如果更新比较多,而查询比较少用悲观锁。
7)共享锁:在非Serializable隔离级别下,查询不加任何锁,在Serializable隔离级别下查询加共享锁。
8)
排他锁:在任何隔离级别下进行增删改都会加排他锁
排他锁和任意锁都不能共存。
13.线程调度策略
1)抢占式调度策略
2)时间片轮转策略
14.Synchronized的实现原理
!Java对象头和monitor是实现synchronized的基础
monitorenter和monitorexit指令
同步代码块是使用monitorenter和monitorexit指令实现的,同步方法(在这看不出来需要看JVM底层实现)依靠的是方法修饰符上的ACC_SYNCHRONIZED实现。
Java对象头:Mark Word(标记字段) 存储对象自身的运行时数据 如哈希码 GC分代年龄 锁状态 对象头两个机器码 32位虚拟机 1机器码=4字节=32bit
Klass Pointer(类型指针) 对象指向他的类元数据的指针
!monitor理解为一个同步工具
15.ReentrantLock锁
ReentrantLock是根据AQS实现的独占锁
AQS内部维护了一个双向链表
state 是同步状态位,具体是否能够获取锁就是通过修改state来实现
state的改变是AQS基于CAS操作的
CAS --compareAndSetState操作
16.线程池
当提交一个新任务到线程池时,线程池的处理流程如下:
首先线程池判断基本线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。
http://ifeve.com/java-threadpool/
网友评论