线程 JVM锁整理
1、线程的等待和通知
首先wait()和notify(),notifyAll()方法一定是一般对象方法,他们并不属于线程对象方法,一定是跟synchronized(监视器锁)结伴出现的。wait()方法执行时会释放获取的监视器锁,线程进入休眠等待状态。而notify()执行时,会随机唤醒一个等待状态的线程,并重新获取监视器锁,然后再继续执行。notifyAll()方法是唤醒所有的相同对象的等待线程,再去竞争获取监视器锁。
public class SimpleWN { final static Object object = new Object(); public static class T1 implements Runnable { public void run() { synchronized (object) { System.out.println(System.currentTimeMillis() + ":T1 start!"); try { System.out.println(System.currentTimeMillis() + ":T1 wait for object"); object.wait(); }catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis() + ":T1 end!"); } } } public static class T2 implements Runnable { public void run() { synchronized (object) { try { //让线程T1先执行,自己先睡2秒 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis() + ":T2 start! notify one thread"); object.notify(); System.out.println(System.currentTimeMillis() + ":T2 end!"); } } } public static void main(String[] args) { Thread t1 = new Thread(new T1()); Thread t2 = new Thread(new T2()); t1.start(); t2.start(); } }
执行结果
1538646195634:T1 start!
1538646195635:T1 wait for object
1538646197635:T2 start! notify one thread
1538646197635:T2 end!
1538646197635:T1 end!
如果注释掉Thread.sleep(2000)代码块,则可能T2线程先执行,T1后执行,整个程序进入堵塞状态,无法唤醒!
2、等待线程结束
join()方法是执行一个wait()方法作用于当前线程,进行等待,如果当前线程是主线程则会使主线程等待。
public class JoinMain { public volatile static int i = 0; public static class AddThread implements Runnable { @Override public void run() { for (i = 0;i < 10000000;i++); } } public static void main(String[] args) throws InterruptedException { Thread at = new Thread(new AddThread()); at.start(); at.join(); System.out.println(i); } }
执行结果
10000000
如果注释掉at.join(),主线程输出值为0,主线程执行打印时,线程at还未执行。
3、守护线程
守护线程的作用就是所有用户线程(包含主线程)都结束了,该线程也自然结束了。
public class FIndReady { private static int num; private static boolean ready; private static class ReaderThread extends Thread { public void run() { while (ready) { System.out.println(num); } } } public static void main(String[] args) throws InterruptedException { Thread t = new ReaderThread(); //设置守护线程 t.setDaemon(true); t.start(); num = 45; ready = true; } }
这段代码如果不设置守护线程t.setDaemon(true),则会无限打印45,但设置了守护线程后,主线程结束后,就会停止打印45.
再来说说volatile,volatile本来是设置寄存器到内存的复制到所有线程可见的,不过寄存器到内存的复制以现在的电脑性能实在是太快了,所以我觉得volatile的意义已经不大了。
不过即便是设置了守护线程,如果加入了join()方法,主线程依然会等待守护线程执行完,这样就会无限打印45.
public class FIndReady { private static volatile int num; private static boolean ready; private static class ReaderThread extends Thread { public void run() { while (ready) { System.out.println(num); } } } public static void main(String[] args) throws InterruptedException { Thread t = new ReaderThread(); //设置守护线程 t.setDaemon(true); t.start(); num = 45; ready = true; t.join(); } }
4、重入锁
重入锁指的是当一个线程申请获得一次加锁之后,当释放锁后再次获取该锁将无需再次申请,节省开销。
用加锁来实现多线程累加
public class VolatileQuestion { private static volatile Integer i = 0; private static Lock lock = new ReentrantLock(); public static class PlusTask implements Runnable { public void run() { for (int k = 0; k < 10000; k++) { add(); } } private static void add() { lock.lock(); try { i++; }finally { lock.unlock(); } } } public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[10]; for (int j = 0;j < 10;j++) { threads[j] = new Thread(new PlusTask()); threads[j].start(); } for (int j = 0;j< 10;j++) { threads[j].join(); } System.out.println(i); } }
当然还有两种方式可以达到同样的效果
public class VolatileQuestion { private static volatile Integer i = 0; // private static Lock lock = new ReentrantLock(); public static class PlusTask implements Runnable { public void run() { for (int k = 0; k < 10000; k++) { add(); } } private static synchronized void add() { i++; } } public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[10]; for (int j = 0;j < 10;j++) { threads[j] = new Thread(new PlusTask()); threads[j].start(); } for (int j = 0;j< 10;j++) { threads[j].join(); } System.out.println(i); } }
原子类无锁
public class VolatileQuestion { private static AtomicInteger i = new AtomicInteger(0); // private static Lock lock = new ReentrantLock(); public static class PlusTask implements Runnable { public void run() { for (int k = 0; k < 10000; k++) { i.getAndIncrement(); } } } public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[10]; for (int j = 0;j < 10;j++) { threads[j] = new Thread(new PlusTask()); threads[j].start(); } for (int j = 0;j< 10;j++) { threads[j].join(); } System.out.println(i); } }
运行结果都一样
100000
5、优先中断
优先中断并不是以获取锁为目的,而是以优先获取中断为目标
把一个死锁的例子逐步改成非死锁
public class InLock implements Runnable { public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); private int lock; public InLock(int lock) { this.lock = lock; } public void run() { try { if (lock == 1) { lock1.lock(); // lock1.lockInterruptibly(); // lock1.tryLock(); try { Thread.sleep(500); System.out.println(Thread.currentThread().getId() + "获取锁"); }catch (InterruptedException e) {} lock2.lock(); // lock2.lockInterruptibly(); // lock2.tryLock(); }else { lock2.lock(); // lock2.lockInterruptibly(); // lock2.tryLock(); try { Thread.sleep(500); System.out.println(Thread.currentThread().getId() + "获取锁"); }catch (InterruptedException e) {} lock1.lock(); // lock1.lockInterruptibly(); // lock1.tryLock(); } }catch (Exception e) { e.printStackTrace(); }finally { //lock1是否获取锁 if (lock1.isHeldByCurrentThread()) { lock1.unlock(); } if (lock2.isHeldByCurrentThread()) { lock2.unlock(); } System.out.println(Thread.currentThread().getId() + ":线程退出"); } } public static void main(String[] args) throws InterruptedException { InLock r1 = new InLock(1); InLock r2 = new InLock(2); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); // Thread.sleep(1000); //t2.interrupt(); } }
运行结果
12获取锁
13获取锁
这是一个死锁,t1,t2线程都分别获取了lock1,lock2的锁之后在未解锁的情况下,去获取对方的锁,谁也得不到对方的锁而出现死锁,程序堵塞。
程序修改成中断t2,t1可以获取锁,程序执行完毕,抛出一个中断异常
public class InLock implements Runnable { public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); private int lock; public InLock(int lock) { this.lock = lock; } public void run() { try { if (lock == 1) { // lock1.lock(); lock1.lockInterruptibly(); // lock1.tryLock(); try { Thread.sleep(500); System.out.println(Thread.currentThread().getId() + "获取锁"); }catch (InterruptedException e) {} // lock2.lock(); lock2.lockInterruptibly(); // lock2.tryLock(); }else { // lock2.lock(); lock2.lockInterruptibly(); // lock2.tryLock(); try { Thread.sleep(500); System.out.println(Thread.currentThread().getId() + "获取锁"); }catch (InterruptedException e) {} // lock1.lock(); lock1.lockInterruptibly(); // lock1.tryLock(); } }catch (Exception e) { e.printStackTrace(); }finally { //lock1是否获取锁 if (lock1.isHeldByCurrentThread()) { lock1.unlock(); } if (lock2.isHeldByCurrentThread()) { lock2.unlock(); } System.out.println(Thread.currentThread().getId() + ":线程退出"); } } public static void main(String[] args) throws InterruptedException { InLock r1 = new InLock(1); InLock r2 = new InLock(2); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); Thread.sleep(1000); t2.interrupt(); } }
lock2.lockInterruptibly()会优先响应t2.interrupt()发生中断,抛出中断异常,lock2自动解锁,运行结果
12获取锁
13获取锁
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at com.guanjian.InLock.run(InLock.java:38)
at java.lang.Thread.run(Thread.java:745)
13:线程退出
12:线程退出
再修改成尝试获取锁
public class InLock implements Runnable { public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); private int lock; public InLock(int lock) { this.lock = lock; } public void run() { try { if (lock == 1) { // lock1.lock(); // lock1.lockInterruptibly(); lock1.tryLock(); try { Thread.sleep(500); System.out.println(Thread.currentThread().getId() + "获取锁"); }catch (InterruptedException e) {} // lock2.lock(); // lock2.lockInterruptibly(); if (lock2.tryLock()) { System.out.println("1获取成功"); } }else { // lock2.lock(); // lock2.lockInterruptibly(); lock2.tryLock(); try { Thread.sleep(500); System.out.println(Thread.currentThread().getId() + "获取锁"); }catch (InterruptedException e) {} // lock1.lock(); // lock1.lockInterruptibly(); if (lock1.tryLock()) { System.out.println("2获取成功"); } } }catch (Exception e) { e.printStackTrace(); }finally { //lock1是否获取锁 if (lock1.isHeldByCurrentThread()) { System.out.println(Thread.currentThread().getId() + "解锁"); lock1.unlock(); } if (lock2.isHeldByCurrentThread()) { System.out.println(Thread.currentThread().getId() + "解锁"); lock2.unlock(); } System.out.println(Thread.currentThread().getId() + ":线程退出"); } } public static void main(String[] args) throws InterruptedException { InLock r1 = new InLock(1); InLock r2 = new InLock(2); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); // Thread.sleep(1000); // t2.interrupt(); } }
tryLock()在拿不到锁的时候可以马上返回false,不会堵塞,可以大大减少死锁的可能性,运行结果如下
12获取锁
13获取锁
12解锁
12:线程退出
13解锁
13:线程退出
我们可以看到他们都没有拿到对方的锁,但是没有死锁堵塞。
tryLock()可以设等待时间,等待时间后再返回
public class TimeLock implements Runnable { public static ReentrantLock lock = new ReentrantLock(); public void run() { try { if (lock.tryLock(7, TimeUnit.SECONDS)) { System.out.println(Thread.currentThread().getName()); Thread.sleep(6000); }else { System.out.println("get lock failed"); } }catch (InterruptedException e) { e.printStackTrace(); }finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } public static void main(String[] args) { TimeLock tl = new TimeLock(); Thread t1 = new Thread(tl); Thread t2 = new Thread(tl); t1.start(); t2.start(); } }
一个线程拿到锁以后睡眠6秒解锁,另一个线程等待7秒拿锁,结果2个线程都拿到了锁
运行结果
Thread-0
Thread-1
如果等待时间小于睡眠时间,则拿不到锁
public class TimeLock implements Runnable { public static ReentrantLock lock = new ReentrantLock(); public void run() { try { if (lock.tryLock(5, TimeUnit.SECONDS)) { System.out.println(Thread.currentThread().getName()); Thread.sleep(6000); }else { System.out.println("get lock failed"); } }catch (InterruptedException e) { e.printStackTrace(); }finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } public static void main(String[] args) { TimeLock tl = new TimeLock(); Thread t1 = new Thread(tl); Thread t2 = new Thread(tl); t1.start(); t2.start(); } }
运行结果
Thread-0
get lock failed
6、公平锁
让所有参与的线程都能够依次公平的获取锁,成本高,性能底下
public class FairLock implements Runnable { //设置公平锁 public static ReentrantLock lock = new ReentrantLock(true); public void run() { while (true) { try { lock.lock(); System.out.println(Thread.currentThread().getName() + "获得锁"); }finally { lock.unlock(); //break; } } } public static void main(String[] args) { FairLock r1 = new FairLock(); Thread t1 = new Thread(r1); Thread t2 = new Thread(r1); t1.start(); t2.start(); } }
运行结果(截取部分)
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
从结果可以看出,两个线程之间总是交替获取锁。
取消公平锁
public class FairLock implements Runnable { //设置公平锁 public static ReentrantLock lock = new ReentrantLock(); public void run() { while (true) { try { lock.lock(); System.out.println(Thread.currentThread().getName() + "获得锁"); }finally { lock.unlock(); //break; } } } public static void main(String[] args) { FairLock r1 = new FairLock(); Thread t1 = new Thread(r1); Thread t2 = new Thread(r1); t1.start(); t2.start(); } }
运行结果(截取部分)
Thread-0获得锁
Thread-0获得锁
Thread-0获得锁
Thread-0获得锁
Thread-0获得锁
Thread-0获得锁
Thread-0获得锁
Thread-0获得锁
Thread-0获得锁
Thread-1获得锁
Thread-1获得锁
Thread-1获得锁
Thread-1获得锁
Thread-1获得锁
由结果可以看出,获取过一次锁的线程总是更容易获取下一次锁,是非公平的。
7、与重入锁结伴的等待与通知
await()方法,singal()方法与singalAll()方法类似于Object的wait(),notify(),notifyAll()方法。
public class ReenterLockCondition implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); public static Condition condition = lock.newCondition(); public void run() { try { lock.lock(); condition.await(); System.out.println("Thread is going on"); }catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { ReenterLockCondition tl = new ReenterLockCondition(); Thread t1 = new Thread(tl); t1.start(); System.out.println("唤醒前先嗨2秒"); Thread.sleep(2000); lock.lock(); condition.signal(); lock.unlock(); } }
运行结果
唤醒前先嗨2秒
Thread is going on
8、信号量
信号量的理解就是如果有很多线程需要执行,而每次仅允许几个线程执行,只有其中有线程执行完毕才允许后面的线程进入执行,但总执行线程数不能多于限制数。
public class SemaphoreDemo { private Semaphore smp = new Semaphore(3,true); //公平策略 private Random rnd = new Random(); class Task implements Runnable{ private String id; Task(String id){ this.id = id; } public void run(){ try { //阻塞,等待信号 smp.acquire(); //smp.acquire(int permits);//使用有参数方法可以使用permits个许可 System.out.println("Thread " + id + " is working"); System.out.println("在等待的线程数目:"+ smp.getQueueLength()); work(); System.out.println("Thread " + id + " is over"); } catch (InterruptedException e) { } finally { //释放信号 smp.release(); } } public void work() {//假装在工作,实际在睡觉 int worktime = rnd.nextInt(1000); System.out.println("Thread " + id + " worktime is "+ worktime); try { Thread.sleep(worktime); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args){ SemaphoreDemo semaphoreDemo = new SemaphoreDemo(); ExecutorService se = Executors.newCachedThreadPool(); se.submit(semaphoreDemo.new Task("a")); se.submit(semaphoreDemo.new Task("b")); se.submit(semaphoreDemo.new Task("c")); se.submit(semaphoreDemo.new Task("d")); se.submit(semaphoreDemo.new Task("e")); se.submit(semaphoreDemo.new Task("f")); se.shutdown(); } }
运行结果
Thread b is working
在等待的线程数目:0
Thread b worktime is 860
Thread a is working
Thread c is working
在等待的线程数目:1
在等待的线程数目:1
Thread a worktime is 445
Thread c worktime is 621
Thread a is over
Thread d is working
在等待的线程数目:2
Thread d worktime is 237
Thread c is over
Thread e is working
在等待的线程数目:1
Thread e worktime is 552
Thread d is over
Thread f is working
在等待的线程数目:0
Thread f worktime is 675
Thread b is over
Thread e is over
Thread f is over
结果解读:a,b,c三个线程进入工作,其他线程无法进入,a线程执行完,空出一个线程位,d线程进入工作,c线程执行完,又空出一个线程位,e线程进入工作,d线程执行完,f线程进入工作,b线程执行完,e线程执行完,f线程执行完。
9、读写锁
当所有的写锁释放后,所有的读锁将并行执行,否则读锁和写锁都将进行一一锁定。
public class ReadWriteLockDemo { private static Lock lock = new ReentrantLock(); private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); private static Lock readLock = reentrantReadWriteLock.readLock(); private static Lock writeLock = reentrantReadWriteLock.writeLock(); private volatile int value; public Object handleRead(Lock lock) throws InterruptedException { try { lock.lock(); Thread.sleep(1000); return "读" + Thread.currentThread().getName() + " " + value; }finally { lock.unlock(); } } public void handleWrite(Lock lock,int index) throws InterruptedException { try { lock.lock(); Thread.sleep(1000); value = index; System.out.println("写" + Thread.currentThread().getName() +" " + value); }finally { lock.unlock(); } } public static void main(String[] args) { final ReadWriteLockDemo demo = new ReadWriteLockDemo(); Runnable readRunnable = new Runnable() { public void run() { try { System.out.println(demo.handleRead(readLock)); // System.out.println(demo.handleRead(lock)); } catch (InterruptedException e) { e.printStackTrace(); } } }; Runnable writeRunnable = new Runnable() { public void run() { try { demo.handleWrite(writeLock,new Random().nextInt(100)); // demo.handleWrite(lock,new Random(100).nextInt()); } catch (InterruptedException e) { e.printStackTrace(); } } }; for (int i = 0;i < 2;i++) { new Thread(writeRunnable).start(); } for (int i = 0;i < 18;i++) { new Thread(readRunnable).start(); } } }
运行结果
读Thread-2 0
读Thread-3 0
读Thread-4 0
写Thread-0 82
写Thread-1 5
读Thread-5 5
读Thread-10 5
读Thread-9 5
读Thread-8 5
读Thread-6 5
读Thread-7 5
读Thread-13 5
读Thread-15 5
读Thread-18 5
读Thread-16 5
读Thread-12 5
读Thread-19 5
读Thread-11 5
读Thread-14 5
读Thread-17 5
运行结果解读:在读Thread-5 5之前,每秒出一个结果,从读Thread-5 5开始到读Thread-17 5没有1秒停顿,并行同时执行,说明在读Thread-17 5之后没有锁竞争。
如果把读写锁换成可重入锁
public class ReadWriteLockDemo { private static Lock lock = new ReentrantLock(); private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); private static Lock readLock = reentrantReadWriteLock.readLock(); private static Lock writeLock = reentrantReadWriteLock.writeLock(); private volatile int value; public Object handleRead(Lock lock) throws InterruptedException { try { lock.lock(); Thread.sleep(1000); return "读" + Thread.currentThread().getName() + " " + value; }finally { lock.unlock(); } } public void handleWrite(Lock lock,int index) throws InterruptedException { try { lock.lock(); Thread.sleep(1000); value = index; System.out.println("写" + Thread.currentThread().getName() +" " + value); }finally { lock.unlock(); } } public static void main(String[] args) { final ReadWriteLockDemo demo = new ReadWriteLockDemo(); Runnable readRunnable = new Runnable() { public void run() { try { // System.out.println(demo.handleRead(readLock)); System.out.println(demo.handleRead(lock)); } catch (InterruptedException e) { e.printStackTrace(); } } }; Runnable writeRunnable = new Runnable() { public void run() { try { // demo.handleWrite(writeLock,new Random().nextInt(100)); demo.handleWrite(lock,new Random().nextInt(100)); } catch (InterruptedException e) { e.printStackTrace(); } } }; for (int i = 0;i < 2;i++) { new Thread(writeRunnable).start(); } for (int i = 0;i < 18;i++) { new Thread(readRunnable).start(); } } }
虽然运行结果一样,但是结果是1秒出一个,说明次次都是被锁锁了1秒。
10、倒计时器
倒计时器的作用是让参与的线程挨个执行,其他线程等待,到计时器计时完毕,其他线程才可以继续执行。
public class CountDownLatchDemo implements Runnable { //设定计时器 static final CountDownLatch end = new CountDownLatch(10); private static AtomicInteger i = new AtomicInteger(10); public void run() { try { Thread.sleep(new Random().nextInt(10) * 1000); i.getAndDecrement(); System.out.println("check complete,剩余次数" + i.toString()); //计时器中的一个线程完成,计时器-1 end.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newFixedThreadPool(10); CountDownLatchDemo demo = new CountDownLatchDemo(); for (int i = 0;i < 10;i++) { exec.submit(demo); } //让主线程等待计时器倒数完成才允许继续执行 end.await(); System.out.println("Fire!"); exec.shutdown(); } }
运行结果
check complete,剩余次数9
check complete,剩余次数8
check complete,剩余次数7
check complete,剩余次数6
check complete,剩余次数5
check complete,剩余次数4
check complete,剩余次数3
check complete,剩余次数2
check complete,剩余次数1
check complete,剩余次数0
Fire!
如果我们把static final CountDownLatch end = new CountDownLatch(10);改成小于10的数,比如3
public class CountDownLatchDemo implements Runnable { //设定计时器 static final CountDownLatch end = new CountDownLatch(3); private static AtomicInteger i = new AtomicInteger(10); public void run() { try { Thread.sleep(new Random().nextInt(10) * 1000); System.out.println("check complete,剩余次数" + i.decrementAndGet()); //计时器中的一个线程完成,计时器-1 end.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newFixedThreadPool(10); CountDownLatchDemo demo = new CountDownLatchDemo(); for (int i = 0;i < 10;i++) { exec.submit(demo); } //让主线程等待计时器倒数完成才允许继续执行 end.await(); System.out.println("Fire!"); exec.shutdown(); } }
这里面我们做了一点小小的调整,就是原子类打印System.out.println("check complete,剩余次数" + i.decrementAndGet())而并不是i.getAndDecrement(); System.out.println("check complete,剩余次数" + i.toString());这个改动是为了让打印不会打印出相同的数,否则即便是原子类,这也是两步操作,依然会打印出相同的数,原因可以自己思考。
运行结果
check complete,剩余次数9
check complete,剩余次数7
check complete,剩余次数8
Fire!
check complete,剩余次数6
check complete,剩余次数4
check complete,剩余次数3
check complete,剩余次数5
check complete,剩余次数2
check complete,剩余次数1
check complete,剩余次数0
从结果可以看出,线程demo只并行执行了3次,主线程就继续执行了。而剩余次数混乱说明是并行执行,而不是依次执行。