线程 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次,主线程就继续执行了。而剩余次数混乱说明是并行执行,而不是依次执行。
