JAVA多线程

来源:互联网 发布:南京行知实验中学排名 编辑:程序博客网 时间:2024/06/11 02:59

多线程简述

多线程是只多个线程同时对共享资源进行操作。
优点是最大限度的利用CPU的空闲时间来处理其他任务(同一时间内运行不同种任务)。
缺点是当多个线程同时操作共享资源的时候,可能会出现数据不同步的情况。

介绍两个概念
同步:两个或多个线程同时对共享资源进行操作的时候,可能会由于线程间相互干扰出现数据不一致错误,避免错误的方法就是同步。发送一个请求,然后等待响应返回后才能发送下一个请求。
异步:发送一个请求,不需要等待请求结果,可以继续发送下一个请求。

volatile关键字

变量被声明为volatile后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作同其他内存操作进行重排序,volatile不会缓存在寄存器或其他处理器不可见的地方,因此在读取volatile变量的值时总会返回最新写入的值。

多线程通讯的方式

1.sychronized
当A线程进入sychronized修饰的方法或共享代码块时,B线程只有等A线程运行完毕释放锁对象后,才能获得锁对象运行线程,即当出现一中情况如B需要A运行后的值,这就实现了通讯
sychronized工作原理:sychronized内部使用了一个monitor对象来完成对对方法或者代码块的锁定,具体步骤:
1.当monitor的进入数为0,则该线程进入monitor,并将其设置为1,该线程即为monitor的持有者
2.若该线程已经占有monitor,重新进入monitor,monitor+1
3.若其他线程已经占用monitor,则该线程进入阻塞状态,直到monitor的状态为0,再尝试获得monitor的所有权
注意:wait/notify也依赖monitor对象,这就解释了为什么wait/notify要放在同步方法与同步代码块中。
2.while轮询
模拟一个场景,线程A向一个list里添加数据,而线程B的run方法里是轮询一个机制,机制内容是当list的size等于5时进行提示,由于while在不断轮询,相当于线程B在监控线程A,这样也相当于实现了通讯。
需要注意线程不安全问题,共享变量最好用volatile修饰。
3.wait/nodify/notifyAll机制与await/signal/signalAll机制
线程可以使用wait使得线程进行休眠,nodify/notifyAll可以使得线程被唤醒。同理await/signal/signalAll机制也是如此。
nodify与nodifyAll的区别
notify是随机唤醒线程池中的一个线程使其处于可运行状态,而nodifyAll是唤醒线程池中所有线程并让他们去抢夺CPU资源,保证了至少有一个线程可以运行。
wait/nodify/notifyAll机制与await/signal/signalAll机制的区别
await/signal/signalAll可以通过new ReentrantLock().newCondition来创建condition对象休眠或者唤醒指定线程,wait/nodify/notifyAll不可以

多线程工具

ThreadLocal

ThreadLocal是为了解决多线程而提出的,当使用ThreadLocal来维护变量的时候,ThreadLocal会为每个访问该变量的线程提供一个独立的线程副本,所以每个线程可以独立的改变自己的线程副本,而不会影响其他线程。与sychronized相比,一个是锁机制时间换空间,threadLocal是空间换时间。
工作原理:ThreadLocal内部维护了一个ThreadLocalMap映射表,使用本身ThreadLocal对象作为key,value保存要真正存储的Object,每个线程的变量副本是存储在ThreadLocalMap中,会根据访问线程来获得ThreadLocalMap。主要方法是set、get。

 public T get() {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null) {                @SuppressWarnings("unchecked")                T result = (T)e.value;                return result;            }        }        return setInitialValue();    }
public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }

变量副本ThreadLocalMap(threadlocal)是根据getMap方法获得的,初始化是由creatMap方法创建的。

 void createMap(Thread t, T firstValue) {        t.threadLocals = new ThreadLocalMap(this, firstValue);    }

缺点
易出现内存泄漏。
由于ThreadLocal中的threadLocalMap中的key(ThreadLocal对象)是弱引用的Threadlocal,所以当ThreadLocal没有被外部强引用所引用时,当系统GC时,会将弱引用的ThreadLocal进行回收,ThreadLocalMap中的key变为null值,当线程一直不结束时,会一直存在一条null key的强引用链无法回收,造成内存泄漏。
根源是threadLocalMap同Thread的生命周期一样长,如果没有手动删除对应的key就会导致内存泄漏,并不是因为弱应用。
避免内存泄漏的方法
每次使用完ThreadlLocal,都调用他的remove方法,清除数据

ReentrantLock

ReentrantLock是一个可重入锁,基于AQS(AbstarctQueuedSychronizer),AQS基于CAS(compareAndSet).
特性
1.可重入:同一线程可以多次获得同一把锁(synchronized也是可重入锁),实现原理是是为每一个锁关联一个线程持有者和程序计数器,当计时器为0时代表该锁没有被任何线程占用,任何线程都有机会占有该锁;当线程占有这个锁后,会记录下锁的持有线程,并将程序计数器设为1,其他线程不能占用该锁;当持有该锁的线程再次请求这个锁时,可以拿到这个锁并且程序计数器加1;当线程退出同步代码块时,计数器减1,当计数器为0时,代表锁被释放。
2.可中断:线程在尝试获取锁的过程中,可以响应中断,lockInterruptibly()
3.非公平锁与公平锁
非公平锁:当一个线程请求非公平锁时该锁变为可用状态,这个线程会跳过队列获得锁(ReentrantLocak默认是非公平锁,sychronized也是非公平锁)
公平锁:线程按照请求顺序来获得锁(即刚释放的锁不可以获得同步资源,ReentrantLock特有)

介绍四个概念
1.AQS(AbstractQueuedSychronizer):
是一个构建锁与同步容器的框架,使用一个FIFO的队列来表示排队等待锁的线程,包含next指针与pre指针,每个节点维护一个等待状态waitStatues,以及一个表示状态的字段state(ReentrantLock保存线程存入锁次数,FutureTask表示任务状态)。
2.CLH自旋锁
提供先来先服务的公平性,本质上也是一中独占锁,为了实现保护共享资源而提出的一种机制,同一时间内只有一个锁来对共享资源进行操作。但在调度机制上略有不同,对于互斥锁,当共享资源被占用,资源请求者会陷入睡眠状态;而对于自旋锁,若自旋锁已经被别的执行单元所保持,调用者就会一直循环在那里看是否该自旋锁的保持着已经释放了锁。
3.CAS
原理:存在3个值,当前内存值V,期待值A,修改至B,当V=A时,将V修改为B,否则轮询
底层实现:利用CPU调用指令,单一处理器,采用总线加锁或缓存加锁来保证原子性
总线加锁:i++时,A、B读取到i值,处理器提供Lock#信号,总线加锁后,处理器A执行时处理器B堵塞(Lock#下其他线程无法操作内存,性能差)
缓存加锁:A、B同时读取i值到缓存,若A提前完成则数据立马写入内存,并让处理器B失效,处理器
4.unsafe
介绍:由于JAVA不能访问操作系统底层,而是通过本地方法来访问,unsafe类提供硬件级别的原子操作(分配的内存GC不会进行回收,需要手动free)
特性:
1.分配、释放内存:用于管理内存。
2.操作类、对象、变量:获取对象指针,通过对指针进行偏移,直接修改指针指向的数据
3.挂起与恢复:park与unpark操作,调用park后线程将一直阻塞直到超时或者中断等条线的出现
4.多线程同步(锁机制与CAS操作):compareAndSet,原子操作,避免了繁重的锁机制,提高代码效率。是一种乐观锁,通常不出现竞态条件,若操作失败则会不断重复直至成功。

AQS、CAS与unsafe的关系
ReentrantLock是基于AQS的,AQS是基于CLH(自旋锁)与CAS的,AQS使用unsafe直接操作内存来对字段进行CAS操作和设置值。

方法原理
1.lock方法原理
1.首先判断锁是否被占用,若state=0表示锁没有被占用,线程进行占用并将state设置成为1,若锁被占用则线程去进行排队。
2.执行排队的线程再次查看state,若state=0则占用锁,若sttae=1查看用该锁的线程是否是自己,若是的话占用该锁并且state+1,否则返回false
3.若锁占用失败,则将这个线程放入等待队列
4.初始化等待队列并且创建节点,已插入队列的线程会尝试获得锁,失败则挂起
5.线程能挂起的前提是前驱节点为SINGAL(<0),符合返回true,不符合看前驱节点是否为CANCELLED(>0),若是向前遍历直到第一个符合要求的前驱,若不是则将前驱节点设置成为SIGNAL

2.unlock操作原理
释放锁,若当前释放的线程并没有持有锁,抛出异常,若持有锁,将其释放后看state是否为0,若是则代表释放成功,清空独占线程,返回free,若释放成功,查看头结点状态是否为SIGNAL,若是唤醒头结点的下个节点关联的线程

3.tryLock工作原理
线程进入等待队列,开始自旋获得锁,若成功返回,失败则在队列中找一个安全点把自己挂起直到时间超时或时间过期

ReentrantLock与sychronized的区别
1.ReentrantLock具有sychronized同步互斥机制,需要显示的获得释放锁
2.ReentrantLock具有等待可中断锁tryLock(long time),在time时间段内等待锁的释放,若没有释放则去执行其他操作
3.ReentrantLock提供公平锁机制,即先进先出,对队列中的线程按顺序执行
4.ReentrantLock提供可轮训请求,若成功则继续处理,不成功等下次运行时处理;sychronized请求要么成功,要么阻塞,易产生死锁

ReentrantReadWriteLock

ReentrantReadWriteLock是一个读写锁,同ReentrantLock(独占锁)一样,都是基于AQS,包括一个继承AQS的抽象类Sync,即同样包含公平锁与非公平锁(默认),内部维护了一个“读取锁”(ReaderLock)与一个“写入锁”(WriteLock).
1.ReadLock:用于只读操作,属于“共享锁”,可以同时被多个线程锁获取。
2.WriteLock:用于只写操作,属于“独占锁”,同一时刻只能被一个线程锁共享。
注意:不能同时存在读取锁与写入锁。

工作原理:
获取共享锁:
1.开始尝试获得锁(tryAcquireShared),若尝试获得成功则直接返回,
2.若获取不成功,开始通过doAcquireShared去不断尝试获得锁,若判断出此时锁的数目小于最大值max且该锁并不需要阻塞等待时,使用CAS更新锁的操作“读取锁的共享次数”,以及当前线程“获取读取锁的次数+1”
3.若发生阻塞的话,使用CLH队列来管理获取锁的等待线程的队列

释放共享锁
1.使用tryReleaseShared来尝试放弃共享锁,若放弃成功,则通过
doReleadeShared来唤醒其他等待获取共享锁的线程

写入锁同ReentrantLock原理也是一样的,独属于独占锁。

线程池

作用:使用线程来执行任务时,线程的创建以及销毁都需要一定的开销,若为每一个任务创建一个新线程,那么将会消耗大量资源。线程池的出现可以根据系统情况调整线程数量,防止消耗过多内存。
常用的四个线程池:ScheduledThreadPool、FixedThreadPool、CacheThreadPool、SingleThreadExecutor.
1.ScheduledThreadPool:常用,相当于一个具有周期性的定时器,多个线程周期性的执行任务。项目用于定时执行任务
创建及使用:
ScheduledThreadService scheduledThreadPool=Executors.newScheduledThreadPool(5);
scheduledThreadPool.scheduledAtFixRate(method,delay,period,unit);
2.FixedThreadPool:使用固定数目的线程数目来执行任务,若任务数多于线程数,会将任务放入等待队列中
3.CacheThreadPool:缓冲线程池,核心线程数目为0,最大线程数目为Integer.max,可灵活回收空闲线程,空闲线程最多等待60s后销毁。
4.SingleThreadExecutors:与FixedThreadPool类似,不过线程数目为1,每次只能运行一个任务,多任务运行时会将其放入等待队列中。

参考文章:
http://www.cnblogs.com/hapjin/p/5492619.html
http://blog.csdn.net/imzoer/article/details/8262101
http://blog.csdn.net/aigoogle/article/details/29893667
http://www.jb51.net/article/105762.htm
http://www.cnblogs.com/skywang12345/p/3505809.html
http://www.360doc.com/content/13/0620/10/9437165_294220354.shtml

原创粉丝点击