自旋锁
在使用wait()和notify()来控制代码的执行时,可能会出现假唤醒的情况。
public class Demo{
List<String> list = new ArrayList<>();
public void push(String val){
synchronized (this){
list.add(val);
notify();
}
}
public void pop() throws InterruptedException {
synchronized (this){
if(list.size()<=0){
wait();
}
list.remove(list.size()-1);
}
}
}
若有线程A、B、C
- 线程A首先进入pop()方法时,list的size()<=0,线程A阻塞。
- 线程B调用push()方法,list.size()=1,并且唤醒线程A的pop方法。
- 在线程A被唤醒执行后面的程序之前,线程C先调用pop()方法,此时的list.size()为1,则不用进入if判断,直接调用remove()方法,list.size()为0。
- 然后线程A直接调用remove()方法,但是此时的list.size()为0,程序报错。
为了解决假唤醒,需要将if改为while。这样的一个while循环叫做“自旋锁”
自旋锁是一种无锁状态,是通过不断比较来确保当前值没有被改变。本身cas的内部就是自旋锁。此处的demo并没有解决aba的问题。
ThreadLoacl
当多线程访问的同一个对象时,该对象的成员变量为所有线程共有。当某一个线程修改对象的成员变量时,会对其他线程造成影响。ThreadLocal类用来保证创建的变量在每个线程中独立存在,多个线程间互不影响。虽然不同的线程执行同一段代码时,访问同一个ThreadLocal变量,但是每个线程只能看到私有的ThreadLocal实例。所以不同的线程在给ThreadLocal对象设置不同的值时,他们也不能看到彼此的修改。
- 使用map来保存变量
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
T t = new T(3);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class T implements Runnable{
Map<String,Integer> map = new HashMap<>();
int i;
T(int i){
this.i = i;
}
@Override
public synchronized void run() {
System.out.println(map.get("val"));
map.put("val",i);
}
}
输出结果:t1线程输出为null,而t2线程输出为3。
此时map在t1线程执行时被修改了,所以在t2线程访问map时,map中是有值的。
- 使用ThreadLocal类来保存的变量
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
T t = new T(3);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class T implements Runnable{
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
int i;
T(int i){
this.i = i;
}
@Override
public synchronized void run() {
System.out.println(threadLocal.get());
threadLocal.set(i);
}
}
输出结果:t1线程输出为null,t2线程输出也为null。
由于ThreadLoacl类中保存的变量为每个线程私有的,所以在t1线程中将ThreadLocal中设置值后,t2线程ThreadLocal时仍为null。
初始化ThreadLocal
重新ThreadLocal中的initialValue() 方法,就可以为ThreadLocal对象指定一个初始化值,并对所有的线程都可见。
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
T t = new T(3);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class T implements Runnable{
ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
//初始化ThreaLocal
public Integer initialValue() {
return 0;
}
};
int i;
T(int i){
this.i = i;
}
@Override
public synchronized void run() {
System.out.println(threadLocal.get());
threadLocal.set(i);
}
}
输出结果:t1线程输出为0,t2线程输出也为0。
死锁
有A、B两线程,线程A在持有锁的同时想要获取线程B的锁。而此时线程B也同时持有锁并想要获取线程A的锁,这样的情况会导致两线程一直阻塞,造成死锁。
public class Demo3 {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
T ta = new T(a,b);
T tb = new T(b,a);
//线程持有a的锁并想要获取b的锁
new Thread(()->{ta.test();}).start();
//线程持有b的锁并想要获取a的锁
new Thread(()->{tb.test();}).start();
}
}
class T{
Object a;
Object b;
T(Object a, Object b){
this.a = a;
this.b = b;
}
public void test() {
synchronized (a){
try {
System.out.println("~~~");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b){
System.out.println("...");
}
}
}
}
使用jps查看java进程的情况如下:
14692 Demo3
1432 RemoteMavenServer
72 Launcher
10908 Jps
8716
使用jstack查看某个Java进程内的线程堆栈信息(这里要看Demo3线程情况,所以输入的是jstack 14692)。可以看出显示当前Thread-1和Thread-0为阻塞状态。
"Thread-1" #12 prio=5 os_prio=0 tid=0x0000000019872000 nid=0x36bc waiting for monitor entry [0x000000001a2af000]
java.lang.Thread.State: BLOCKED (on object monitor)
at t01.T.test(Demo3.java:32)
- waiting to lock <0x00000000d5ebfe70> (a java.lang.Object)
- locked <0x00000000d5ebfe80> (a java.lang.Object)
at t01.Demo3.lambda$main$1(Demo3.java:10)
at t01.Demo3$$Lambda$2/1096979270.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Thread-0" #11 prio=5 os_prio=0 tid=0x0000000019888000 nid=0x3334 waiting for monitor entry [0x000000001a1af000]
java.lang.Thread.State: BLOCKED (on object monitor)
at t01.T.test(Demo3.java:32)
- waiting to lock <0x00000000d5ebfe80> (a java.lang.Object)
- locked <0x00000000d5ebfe70> (a java.lang.Object)
at t01.Demo3.lambda$main$0(Demo3.java:9)
at t01.Demo3$$Lambda$1/1324119927.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
嵌套管程锁死
线程1获取A锁后又获取了B锁,却调用了B.wait()的方法去释放B锁,导致程序阻塞,无法释放A锁,需要线程2去唤醒。但线程2需要持有A锁,最终程序无法运行。
class MyLock{
private Object o = new Object();
private boolean isLock = false;
public synchronized void lock() throws InterruptedException {
while(isLock){
synchronized (o){
o.wait();
}
}
isLock = true;
}
public synchronized void unLock(){
isLock = false;
o.notify();
}
}
第一个线程调用lock()方法,在调用unLock()方法前,第二个线程调用了lock()方法,此时第二个线程处于等待状态。此时当一个线程准备调用unLock()方法时去唤醒第二个线程时,却无法获取当前对象的锁,导致程序阻塞。
公平锁
当有多个线程需要获取同一把锁的时候,若其中一个线程获取,则其他线程被阻塞,等待锁的释放。当锁释放时,所有线程重新抢占这把锁,这样可能会出现一种情况,有一个线程总是无法获取到锁,导致线程处于饥饿状态。
java中的synchronized是无法保证线程的公平性。java本身是无法实现百分百线程的公平性,但是可以提高线程的公平性。
- 先实现一个普通的锁
public class Lock{
private boolean isLock = false;
private Thread thread = null;
public synchronized void lock() throws InterruptedException {
//自旋锁的写法
while(isLock){
System.out.println(Thread.currentThread().getName()+"等待");
this.wait();
}
isLock = true;
thread = Thread.currentThread();
}
public synchronized void unLock(){
System.out.println("唤醒");
//防止同一线程多次调用unLock方法唤醒其他线程或是其他
if(Thread.currentThread() != thread){
throw new IllegalMonitorStateException();
}
isLock = false;
thread = null;
this.notify();
}
}
当第一个线程调用lock() 方法时会将isLock 置为true,后续线程调用lock() 方法时,会进入while 判段,调用wait() 方法,释放锁。这样之后所有的线程都可以进入lock() 方法,然后在调用wait() 方法时阻塞。在第一个线程执行unlock() 方法后会唤醒其中的一个线程。
- 出现嵌套管程锁死的公平锁
public class FairLock {
private List<QueueThread> list = new ArrayList<>();
private Thread lockThread = null;
private boolean isLock = false;
public void lock() throws InterruptedException {
QueueThread queueThread = new QueueThread();
synchronized (this){
list.add(queueThread);
while(isLock || queueThread != list.get(0)){
//这个synchronized(queueThread)加不加都一样,因为本身doWait()方法已经是一个同步方法,而且锁的时当前对象。这样是为了代码看起来更清晰
synchronized(queueThread){
queueThread.doWait();
}
}
isLock = true;
lockThread = Thread.currentThread();
list.remove(list.get(0));
}
}
public synchronized void unLock(){
if(Thread.currentThread() != lockThread){
throw new RuntimeException();
}
lockThread = null;
isLock = false;
if(list.size()>0){
//这个同步代码块也是为了代码更清晰
synchronized(list.get(0)){
list.get(0).doNotify();
}
}
}
}
class QueueThread{
public synchronized void doWait() throws InterruptedException {
this.wait();
}
public synchronized void doNotify(){
this.notify();
}
}
这个公平锁会出现嵌套管程锁死,当调用queueThread.doWait();方法时虽然会释放queueThread对象的锁,但是无法释放this这个锁,导致doNotify()方法无法获取锁,程序被阻塞。
- 将嵌套的锁提取出来
public class FairLock {
private List<QueueThread> list = new ArrayList<>();
private boolean isLock = false;
private Thread lockThread = null;
public void lock() throws InterruptedException {
QueueThread queueThread = new QueueThread();
synchronized(this){
list.add(queueThread);
}
while(isLock || queueThread != list.get(0)){
synchronized (queueThread){
queueThread.doWait();
}
}
synchronized (this){
isLock = true;
lockThread = Thread.currentThread();
list.remove(list.get(0));
}
}
public synchronized void unLock(){
if(Thread.currentThread() != lockThread){
throw new RuntimeException();
}
isLock = false;
if(list.size()>0){
list.get(0).doNotify();
}
}
}
class QueueThread{
public synchronized void doWait() throws InterruptedException {
this.wait();
}
public synchronized void doNotify(){
this.notify();
}
}
这样的锁还是有一个缺点。当线程1调用lock()方法,线程2在执行完第一个同步代码块,进入while语句中的第二个同步代码块时,由于一个同步代码块执行完后,当前锁被释放,这时的线程1在线程2执行doWait()前执行了unLock()方法,这时的线程2再执行doWait()方法时会被阻塞,但是由于unLock()方法被调用了,所以就无法唤醒线程2的等待。
为了解决这个问题,需要在 QueueThread类中保存信号
- 最终的公平锁
public class FairLock {
private List<QueueThread> list = new ArrayList<>();
private boolean isLock = false;
private Thread lockThread = null;
public void lock() throws InterruptedException {
QueueThread queueThread = new QueueThread();
synchronized(this){
list.add(queueThread);
}
while(isLock || queueThread != list.get(0)){
synchronized (queueThread){
queueThread.doWait();
}
}
synchronized (this){
isLock = true;
lockThread = Thread.currentThread();
list.remove(list.get(0));
}
}
public synchronized void unLock(){
if(Thread.currentThread() != lockThread){
throw new RuntimeException();
}
isLock = false;
if(list.size()>0){
list.get(0).doNotify();
}
}
}
class QueueThread{
private boolean isLock = false;
public synchronized void doWait() throws InterruptedException {
while(isLock){
this.wait();
}
isLock = true;
}
public synchronized void doNotify(){
isLock = false;
this.notify();
}
}
可重入锁
一个线程在持有一个锁后,那么这个线程则可以进入其他持有这个锁的代码块。这就是可重入锁。例如:
class Test{
public synchronized void outer(){
inner();
}
public synchronized void inner() {
}
}
- 之前写过的一个普通的锁
public class Lock{
private boolean isLock = false;
private Thread thread = null;
public synchronized void lock() throws InterruptedException {
//自旋锁的写法
while(isLock){
this.wait();
}
isLock = true;
thread = Thread.currentThread();
}
public synchronized void unLock(){
//防止同一线程多次调用unLock方法唤醒其他线程或是其他
if(Thread.currentThread() != thread){
throw new IllegalMonitorStateException();
}
isLock = false;
thread = null;
this.notify();
}
}
这种锁是不可重入的,但一个线程重复调用了lock()方法时,后面的线程会被阻塞。
- 将这个锁改为可重入锁
public class Lock {
private boolean isLock = false;
private Thread thread;
private int current = 0;
public synchronized void lock() throws InterruptedException {
//使一个线程可以重复加锁
while(isLock && thread != Thread.currentThread()){
this.wait();
}
isLock = true;
current++;
thread = Thread.currentThread();
}
public synchronized void unLock(){
if(thread != Thread.currentThread()){
throw new RuntimeException();
}
current--;
if(current == 0){
isLock = false;
thread = null;
this.notify();
}
}
}
读/写锁
当一个线程持有读锁时,这个线程无法再去获取写锁,但是可以获取读锁。
当一个线程持有写锁时,这个线程无法去获取写锁,也无法再获取读锁。
- 一个不可重入的读写锁
public class ReadWriterLock {
private boolean isReadLock = false;
private boolean isWriterLock = false;
private Thread writerThread = null;
private int readCount = 0;
public synchronized void readLock() throws InterruptedException {
while(isWriterLock){
wait();
}
readCount++;
isReadLock = true;
}
public synchronized void unReadLock() throws InterruptedException {
readCount--;
if(readCount == 0){
isReadLock = false;
notifyAll();
}
}
public synchronized void writerLock() throws InterruptedException {
while(isReadLock || isWriterLock){
wait();
}
isWriterLock = true;
writerThread = Thread.currentThread();
}
public synchronized void unWriterLock(){
if(writerThread != Thread.currentThread()){
throw new RuntimeException();
}
isWriterLock = false;
notifyAll();
}
}
信号量
信号量是用来控制并发的数量,访问线程若超过所设置的信号量,则会被阻塞。
- 一个简单信号量的实现
public class Semaphore {
//当前信号量
private int singles = 0;
//最大信号量
private int maxSingles = 10;
public Semaphore(){}
public Semaphore(int maxSingles){
this.maxSingles = maxSingles;
}
//获取信号
public synchronized void acquire() throws InterruptedException {
while(singles == maxSingles){
wait();
}
singles++;
notify();
}
//释放信号
public synchronized void release() throws InterruptedException {
while (singles == 0){
wait();
}
singles--;
notify();
}
}
乐观锁
乐观锁本身是非阻塞的,可以通过非阻塞算法来保证一致性。
如
class Demo{
AtomicInteger count = new AtomicInteger();
public void add(){
boolean updated = false;
while(!updated){
int temp = count.get();
//如果更新失败则返回false,则会再次执行compareAndSet方法
updated = count.compareAndSet(temp,temp+1);
}
}
public int get() {
return count.get();
}
}
compareAndSet方法会先去比较当前的值被是否改变,如果没有被改变则更新,否则不进行任何操作。