java多线程小结:
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,线程是指进程中的一个执行流程,一个进程可以启动多个线程,线程总是属于某个进程,进程中的多个线程共享进程的内存。
在java中,“线程”指两件不同的事情:1,java.lang.Thread类的一个实例 2,线程的执行。
一个Thread实例只是一个对象,像java中的任何其他对象一样,具有变量和方法,生死于堆上。
线程栈:指某时刻内存中线程调度的栈信息,当前调用的方法总是位于栈顶。
多线程是为了减少cpu空闲时间,支持多任务并发处理;微观串行,宏观并行。
只有乱序的代码才有必要设计为多线程。
Thread类的API是进行多线程的基础;
熟悉java中数据结构及其应用场景:
例如HashMap比CurrentHashMap执行效率要高,但线程不安全,而后者线程安全。
多线程编程的注意事项:
1,明确目的,为什么要使用多线程?如果是单线程读写(例如http访问互联网)出现性能瓶颈,可以考虑使用线程池;对不同资源(例如socket连接)进行管理,可以考虑使用多个线程;
2,如何控制线程的调度和阻塞,例如利用事件的触发来控制,也可以利用消息;
3,保障共享资源的线程安全,一般用Lock锁机制来控制,一定要保证不要有死锁;
4,合理使用sleep,减少线程对CPU的抢夺,每次线程的就绪和激活都会占用一定资源,过多使用sleep会导致性能下降;
5,一般要使线程体在完成一件工作的情况下终止,尽量不要直接使用抛出线程异常的方式终止;
6,线程的优先级一定根据程序的需要有个整体的规划。
线程五态:
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
频繁创建和销毁线程会大大降低系统的效率;
JVM运行时刻内存分配原理:当线程访问某一个对象的时候,首先通过对象的引用找到对应堆内存的值,然后把堆内存变量的具体值load到线程本地内存,建立一个变量副本,之后线程就不再和对象在堆内存值有任何关系,而是直接修改副本变量的值,并在线程退出前,自动把线程变量副本的值回写到对象在堆中变量,这样在堆中的对象值就产生了变化。
线程池原理:java.util.concurrent.ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
public interface Executor{
void execute(Runnable command);
}
继承关系:
ThreadPoolExecutor ext AbstractExecutorService imp ExecutorService ext Executor
ThreadPoolExecutor几个重要的方法:execute() submit() shutdown() shutdownNow()
还有其他一些比较重要的成员变量:private final BlockingQueue<Runnable> workQueue;
private final ReentrantLock mainLock = new ReentrantLock();
private final HashSet<Worker> workers = new HashSet<Worker>();
深究execute()方法实现原理(代码逐层解剖):
$execute$
public void execute(Runnable command){
if(command ==null){
throw new NullPointerException();
//corePoolSize是线程池大小
if(poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)){
//当线程池处于RUNNING状态就将任务放入缓存队列
if(runState == RUNNING && workQueue.offer(command){
//当其他线程突然调用shutdown或shutdownNow关闭线程池时
if(runState != RUNNING || poolSize == 0)
//确保任务添加到缓存队列
ensureQueuedTaskHandler(command);
}
//maximumPoolSize是线程池的一种补救措施
else if(!addIfUnderMaximumPoolSize(command))
reject(command);
}
}
$addIfUnderCorePoolSize$
private boolean addIfUnderCorePoolSize(Runnable firstTask){
Thread t = null;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try{
//加锁前的那段时间可能有其他线程提交了任务,所以这里再次判断
if(poolSize < corePoolSize && runState == RUNNING){
t = addThread(firstTask);
//创建线程去执行firstTask任务
}finally{
mainLock.unlock();
}
}
if(t == null) //当线程池满或者不处于RUNNING状态,则创建线程失败
return false;
t.start(); //执行任务
return true;
}
$addThread$
private Thread addThread(Runnable firstTask){
Worker w = new Work(firstTask);
Thread t = threadFactory.newThread(w); //真正创建一个线程
if(t != null){
w.thread = t;
workers.add(w);
//将Worker对象添加到工作集中
int nt = ++poolSize;
if(nt > largestPoolSize)
largestPoolSize = nt;
//记录池中线程数历史新高
}
return t;
}
$Worker$
注意: volatile变量,JVM只保证从堆load到栈的值是最新的,仍存在并发问题,线程不安全。
private final class Worker implements Runnable{
private final ReentrantLock runLock = new ReentrantLock();
private Runnable firstTask;
volatile long completedTasks;
Thread thread;
Worker(Runnable firstTask){
this.firstTask = firstTask;
}
boolean isActive(){
return runLock.isLocked();
}
void interruptIfIdle(){
final ReentrantLock runLock = this.runLock;
if(runLock.tryLock()){
//如果成功获取锁,说明worker空闲,反之正在执行
try{
if(thread != Thread.currentThread())
thread.interrupt();
}finally{
runLock.unlock();
}
}
}
void interruptNow(){
thread.interrupt();
}
private void runTask(Runnable task){
final ReentrantLock runLock = this.runLock;
runLock.lock();
try{
if(runState < STOP && Thread.interrupted() &&runState >= STOP)
boolean ran = false;
beforeExecute(thread,task);
try{
task.run();
ran = true;
afterExecute(task,null);
++completedTasks;
}catch(RuntimeException ex){
if(!ran)
afterExecute(task,ex);
throw ex;
}
}finally{
runLock.unlock();
}
}
public void run(){
try{
Runnable task = firstTask;
firstTask = null;
while(task != null || (task = getTask()) != null){
runTask(task);
task = null;
}
}finally{
workerDone(this);
//当任务队列没有任务时,进行清理工作
}
}
}
$getTask$
注意:getTask是ThreadPoolExecutor类中的方法
Runnable getTask(){
for(;;){
try{
int state = runState;
if(state > SHUTDOWN)
return null;
Runnable r;
if(state == SHUTDOWN)
r = workQueue.poll();
//耗尽队列中任务
else if(poolSize > corePoolSize || allowCoreThreadTimeOut)
r = workQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS);
else
r = workQueue.take();
//线程在这里阻塞,等待任务队列中有任务
if(r !=null)
return r;
if(workerCanExit()){
//若没取到任务,即r为null,则判断当前worker是否可以退出
if(runState >= SHUTDOWN)
//即STOP或TERMINATED
interruptIdleWorkers();
//中断处于空闲状态的worker
return null;
}
}catch(InterruptedException ie){
}
}
}
$workerCanExit$
private boolean workCanExit(){
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
boolean canExit;
try{
canExit = runState >= Stop || workQueue.isEmpty() || (allowCoreThreadTimeOut && poolSize > Math.max(1,corePoolSize));
}finally{
mainLock.unlock();
}
return canExit;
}
$interruptIdleWorkers$
void interruptIdleWorkers(){
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try{
for(Worker w : workers) //实际上调用的是worker的interruptIfIdle()方法
w.interruptIfIdle();
}finally{
mainLock.unlock();
}
}
以上代码就是线程池原理核心源码。
Thread的run方法的实现:
public void run(){
if(target !=null){
target.run();
//这里的target实际保存的是一个Runnable接口的实现的引用
}
}
线程互斥:synchronized修饰的普通方法相当于synchronized(this){…}代码块
synchronized修饰的静态方法相当于synchronized(类对象:类名.class){…}代码块
线程要想同步就必须使用同一个对象的锁;
经验:要用到共同数据(包括同步锁)的若干方法应该归在同一个资源类中,这样设计能保障高内聚和程序的健壮性。
线程范围内的共享数据:ThreadLocal,线程内共享,线程外独立,key是当前线程,一个threadLocal只能存一个共享变量;
JVM虚拟机进程相关方法在Runtime中;
多个线程共享数据的方式:
方式一:如果代码相同,则把代码写入Runnable实现类中,new多个线程共享该类中资源;
方式二:代码不同,则分布在不同Runnable中,共享数据作为外部类的成员变量;
方式三:将共享数据封装到另一个对象中,每个线程对共享数据的操作方法也分配到那个对象中完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。
内部类的作用:1,实现隐藏;2,访问外部类所有元素;3,多重继承;4,父类和接口同名方法调用。
java.util.concurrent.atomic.*:保证基本类型数据操作的原子性;
java.util.concurrent.Executors:线程池;
如何实现线程死掉后重新启动:创建单一线程池;
线程池定时器:Executors.newScheduledThreadPool(3).scheduleAtFixedRate(command,delay,interval,unit);
java.util.concurrent.locks.*:锁,更面向对象,必须在finally块释放锁;
读写锁:只读不互斥,有写就互斥;
设计一个缓存系统:
public class MyCache{
private Map<String,Object> cache = new HashMap<String,Object>();
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public Object getData(String key){
rwl.readLock().lock();
Object value = null;
try{
value = cache.get(key);
if(value == null){
rwl.readLock().unlock();
rwl.writeLock().lock();
//假如多个线程同时走到这里,后边value非空判断
try{
if(value == null)
value = queryDB();
}
}finally{
rwl.writeLock().unlock();
}
rwl.readLock().lock();
}finally{
rwl.readLock().unlock();
}
return value;
}
}
java.util.concurrent.locks.Condition:功能类似于object.wait()和notify(),但能用于多路线程;
设计一个缓冲区(阻塞队列):对应java中的BlockingQueue<E>数据结构
class BoundedBuffer{
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr,takeptr,count;
public void put(Object x) throws InterruptedException{
lock.lock();
try{
while(count == items.length)
notFull.await();
items[putptr] = x;
if(++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
}finally{
lock.unlock();
}
}
public Object take() throws InterruptedException{
lock.lock();
try{
while(count == 0)
notEmpty.await();
Object x = items[takeptr];
if(++takeptr == items.length) takeptr =0;
--count;
notFull.signal();
return x;
}finally{
lock.unlock();
}
}
}
Semaphore(信号灯):控制访问资源线程的个数,例如允许一个文件的并发访问,单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个获得了锁,再由另一个线程释放锁,这可应用于死锁恢复的一些场合。
CyclicBarrier(同步工具类):保证规定数目的线程都执行完后再统一出发;
CountDownLatch(同步工具类):通过countDown方法将计数器减1,可以实现一个线程通知多个线程的效果以及一个(或多个)线程等待其他线程来通知它;
Exchanger(同步工具类):当两个线程都到达时,交换数据。
常用同步集合类:例如CopyOnWriteArrayList,允许迭代过程中增减数据;
如果在一个死去的线程上调用start(),会抛出java.lang.IllegalThreadStateException异常。
sleep注意事项:
1,线程睡眠是帮助所有线程获得运行机会的最好方法。
2,线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间,因此,sleep()方法不能保证该线程睡眠到期后就开始执行。
3,sleep()是静态方法,只能控制当前正在运行的线程。
线程总是存在优先级,优先级范围在1~10之间。
yield()从未导致线程转到等待/睡眠/阻塞状态,在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但可能没有效果。
假定线程a正在执行,某一时刻调用了b.join(),相当于线程b被加入到了a的线程栈,b执行完后a才继续执行。
关于同步:防止多个线程访问同一个数据对象时对数据造成破坏;只能同步方法,不能同步变量和类;如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入该对象的任何一个同步方法;线程睡眠时,它所持的任何锁都不会释放;线程可以获得多个锁,例如:在一个对象的同步方法里调用另一个对象的同步方法,则获得两把锁;同步损害并发,应该尽可能缩小同步范围;在同一对象上进行同步的线程将彼此阻塞,进入该对象的锁池中,直到锁释放,再次变为可运行或运行为止。
与每个对象具有锁一样,每个对象可以有一个线程列表。
线程必须是对象锁的拥有者才能调用该对象wait()或notify()方法,所以通常和同步代码块配合使用;调用wait(),执行该代码的线程立即放弃该对象的锁,然而调用notify(),线程在移出同步代码块之前不会放弃锁,所以并不意味着这时该锁变得可用。
多数情况下,最好notifyAll(),让等待线程返回可运行状态。
本文深入探讨Java多线程的基本概念、线程池原理、执行过程、生命周期及优化策略,结合实例解析多线程编程注意事项,旨在帮助开发者理解和掌握高效、安全的多线程编程技巧。
3万+

被折叠的 条评论
为什么被折叠?



