1. 进程与线程
1.1 进程
是程序的一次执行过程, 使系统运行程序的基本单位。运行一个程序就是创建一个线程, 运行到消亡的过程。
1.2 线程
线程是比进程更小的执行单位,也被成为轻量级的进程, 一个进程可以执行多个线程.然后多个线程共享进程的堆和方法区资源。每个线程都有自己的程序计数器,虚拟机栈, 本地方法栈。
// 就是查看java中有哪些线程...public class ViewThreads {public static void main(String[] args) {ViewThreadTest viewThreadTest = new ViewThreadTest();viewThreadTest.start();// 获取java线程管理的MXBeanThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();// 不需要获取同步的monitor和synchronized消息, 仅获取线程和堆栈信息ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);/*** 遍历线程信息,仅打印线程 ID 和线程名称信息*/for (ThreadInfo threadInfo : threadInfos) {System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());}}}class ViewThreadTest extends Thread{@Overridepublic void run() {try {Thread.sleep(3000);System.out.println("休眠完成了...");} catch (InterruptedException e) {e.printStackTrace();}}}打印结果如下:[10] Thread-0[5] Monitor Ctrl-Break[4] Signal Dispatcher[3] Finalizer[2] Reference Handler[1] main
1.2.1 JVM角度图解进程和线程的关系
具体理解java内存模型详见:https://www.yuque.com/wangyanyang/qz7fwa/qs5van
1.3 线程启动的三种方式:
@Test
public void threadTest() throws InterruptedException, ExecutionException {
// 方式1
MyThread myThread = new MyThread();
myThread.start();
//方式2
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
// 方式3
MyCallable myCallable = new MyCallable();
//使用 FutureTask 类来包装 Callable 对象, 该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值
FutureTask<String> futureTask = new FutureTask<>(myCallable);
//使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程
/**
* 如果没有下行代码, 那么程序会一直等待执行, 原因是如果没有启动线程, 程序会执行
* UNSAFE.park(false, 0L); 这个本地方法, 实现线程的阻塞。
*/
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("MyCallable run...");
return "Hello World";
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Mythread run...");
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("MyRunnable run...");
}
}
1.4 线程的中断机制:
1.4.1 解释:
中断机制的含义和中断不太一样, 中断机制就是某一线程接收到了中断指令, 但是不会马上进行中断, 而是在恰当是时候进行中断。在多线程场景中, 有很多线程无法结束任务(自旋). 此时就需要另外一个线程在恰当的时候给一个中断通知, 然后在恰当的时候进行中断。
1.4.2 中断方式:
interrupt(): 每一个线程都有一个中断标志, 该方法就是讲该标志设置为true
isInterrupted() 返回调用线程的中断标志位。
interrupted() 是一个Thread类的静态方法, 也是返回线程的中断状态, 但是有两点要注意, 第一点, 该方法返回的时候当前线程的状态。第二点该方法在调用结束后会清除中断标志位。
上面两个方法调用的都是底层的同一个native方法, 仅仅是入参不一样.
public class EnableThread {
private static class NewThread extends Thread {
public NewThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while (!this.isInterrupted()) {
System.out.println(threadName + "is run");
}
System.out.println(threadName + "interrput flag is " + isInterrupted() + " and now date ->" + System.currentTimeMillis());
}
}
/**
* 中断机制要想很好的发挥功效, 就需要不同线程间相关协调, 某一个线程发起中断, 另外一个线程要去监听中断信息。
*/
public static void main(String[] args) throws Exception {
NewThread newThread = new NewThread("endThread");
newThread.start();
Thread.sleep(20);
newThread.interrupt();
System.out.println("now date ->" + System.currentTimeMillis());
}
}
------------------------------------------------------------------
public class EnableRunnable {
private static class NewRunnable implements Runnable {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while (!Thread.currentThread().isInterrupted()) {
System.out.println(threadName + " is run");
}
System.out.println(threadName + "interrput flag is " + Thread.currentThread().isInterrupted() + " and now date -->" + System.currentTimeMillis());
}
}
/**
* 中断机制要想很好的发挥功效, 就需要不同线程间相关协调, 某一个线程发起中断, 另外一个线程要去监听中断信息。
*/
public static void main(String[] args) throws Exception {
Thread thread = new Thread(new NewRunnable(), "NewRunnable");
thread.start();
Thread.sleep(20);
thread.interrupt();
System.out.println("now date -->" + System.currentTimeMillis());
}
}
----------------------------------------------------------------
public class HasInterruptedException {
private static class NewThread extends Thread {
public NewThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while (!this.isInterrupted()) {
try {
System.out.println("now interrupt flag is " + isInterrupted());
Thread.sleep(300);
} catch (InterruptedException e) {
/**
* sleep 时候抛出异常原因: 当前线程正在sleep(暂停执行), 有线程去中断它, 它就会抛异常!!!
* 在抛出异常的同时, 还会将中断标志初始化。所以要想结束循环, 需要再次去中断线程。
*/
System.out.println("warn -> " + threadName + " interrput flag is " + isInterrupted() + " and now date ->" + System.currentTimeMillis());
interrupt();
e.printStackTrace();
}
}
System.out.println(threadName + " interrput flag is " + isInterrupted() + " and now date ->" + System.currentTimeMillis());
}
}
public static void main(String[] args) throws Exception {
NewThread newThread = new NewThread("HasInterruptedException");
newThread.start();
Thread.sleep(1000);
newThread.interrupt();
System.out.println("now date ->" + System.currentTimeMillis());
}
}
1.5 线程的生命周期和状态
1.5.1 线程状态解释
| 状态名称 | 说明 |
|---|---|
| new | 初始状态, 线程被创建,还没有调start方法。 |
| runnable | 运行状态, java线程将操作系统中就绪(ready)和运行状态(runnable)统称为”运行中”。 线程创建并执行start方法, 线程处于ready状态,获得cpu时间片,处于runnable状态。 |
| blocked | 阻塞状态, 在没有获得锁情况下, 线程进入blocked状态。 |
| waiting | 等待状态, 线程执行wait方法, 线程进入waiting状态.进入等待状态的进程需要别的线程的通知才能返回运行状态。 没有设置timeout参数的Object.wait()方法, 没有设置timeout参数的Thread.join()方法, LockSupport.park()方法 会进入等待状态。 |
| time_waiting | 超时等待状态, 不同于等待状态, 它可以在指定时间内自行返回。线程执行sleep(long)和wait(long)方法可以将线程置于超时等待状态。 |
| terminated | 终止状态,线程在执行run方法之后会进入终止状态 |
1.5.2 补充说明
1.5.2.1 sleep和wait方法的区别:
两个都可以暂停线程, wait方法被调用后, 会释放锁并且让出cpu, 线程不会自动苏醒, 需要别的线程调用notify方法唤醒。sleep方法执行后, 仅仅是让出cpu,但不会释放锁, 等执行完成后, 线程会自动苏醒。
sleep是Thread类的方法, wait是Object类的方法。
sleep在任何地方可以使用, wait方法只可以在synchronized方法或者synchronized类中使用。因为wait方法释放锁, 只有上锁以后才可以释放, 随便释放锁会报错。
/**
* 当前类校验的是sleep方法与wait方法最明显的区别:
* 执行wait方法时候, 会让出cpu并且释放锁
* 执行sleep方法, 仅仅会让出cpu, 不会释放锁
*/
public class WaitSleepTest {
static String value = "hello";
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread A start...");
synchronized (value) {
try {
System.out.println("Thread A get WaitSleepTest class...start");
/**
* 情况1: 线程A执行wait:
* 线程A会释放锁, 所以线程B会执行,但是线程A中(Thread A done没有打印),因为不会自动唤醒,会被阻塞。
*/
//value.wait();
/**
* 情况2: 线程A执行wait(long)方法:
* 线程A会释放锁, 线程B会执行, 隔一秒以后线程A打印(Thread A done),wait(long)会自动唤醒。
*/
value.wait(1000);
/**
* 情况3: 线程A执行sleep方法:
* 线程A线程内容全部打印完以后, 才会打印线程B相关的信息。
*/
//Thread.sleep(5000);
System.out.println("Thread A done");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread B start...");
synchronized (value) {
System.out.println("Thread B get WaitSleepTest class...start");
}
}
}).start();
}
}
1.5.2.2 调用start方法而不是run方法:
调用start方法, 会启动一个线程并使得线程进入就绪状态, 当分配到时间片时候, start方法执行线程的相关准备, 然后去调run方法。而如果直接执行run方法, 只会把run方法当成main线程下的一个普通方法去执行。
1.5.2.3 守护线程
守护线程在主线程结束的时候, 也会结束。正常情况下,新起一个线程A,即使主线程结束了, A线程如果没结束, 程序也不会结束。但是如果我们将A线程设置为守护线程(newThread.setDaemon(true);). 那么A线程会随着主线程结束而结束的。
1.6 线程死锁,如何避免
多个线程同时被阻塞,他们一直在等待某个资源被释放,程序不能正常结束。典型例子, 线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
public class deadThread {
private static Object resource1 = new Object();
private static Object resource2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized(resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
},"线程A").start();
new Thread(() -> {
synchronized(resource2) {
System.out.println(Thread.currentThread() + "get resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resourc1");
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
}
}
},"线程B").start();
}
}
1.6.1 死锁产生的条件:
互斥条件: 共享资源 X 和 Y 只能被一个线程占用。
占有且等待条件: 线程A已经获取了共享资源X, 在等待共享资源Y, 但是不释放共享资源X。
不剥夺条件: 其他线程不能强行抢占线程T1占有的资源.
循环等待条件: 线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。
1.6.2 怎样避免死锁:
使用银行转账作为例子来解决死锁问题:
线程一执行的是:【账户A】给【账户B】转账
线程二执行的是:【账户B】给【账户A】转账
如果两个转账动作同时执行,则会出现线程一会请求对【账户B】进行加锁,线程二会请求对【账户A】进行加锁。由于此时的【账户A】已由线程一进行锁定,【账户B】已由线程二进行锁定, 此时就会产生死锁问题。
解决上述问题, 我们采取了破坏占且等待条件, 来避免死锁: 就是同时获取多个资源X和Y,多个资源只由统一线程获取。这样就不会存在取到了X还要等待Y。避免了死锁的产生。
/**
* 模拟解决银行转账死锁问题
*
* @author halo
*/
public class TransFerTest {
/**
* 账户详细信息
*/
static private List<Account> testAccount = new ArrayList<>();
/**
* 账户数目
*/
static final int testAccountNumber = 100;
/**
* 线程数
*/
static final int testThreadNumber = 1000;
/**
* 线程池
*/
static final ThreadPoolExecutor threadPool =
new ThreadPoolExecutor(10, testThreadNumber,
1L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new ThreadPoolExecutor.AbortPolicy());
/**
* aqs工具
*/
static final CountDownLatch countDownLatch = new CountDownLatch(testThreadNumber);
static public void test() {
/**
* 随机获取两个账户A和B A给B转账。转账余额也是随机的
*/
testAccount.get(ThreadLocalRandom.current().nextInt(testAccountNumber))
.transactionToTarget(ThreadLocalRandom.current().nextInt(100),
testAccount.get(ThreadLocalRandom.current().nextInt(testAccountNumber)));
// 表示一个请求已经被完成
countDownLatch.countDown();
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < testAccountNumber; i++) {
testAccount.add(new Account(i));
}
for (int i = 0; i < testThreadNumber; i++) {
threadPool.execute(() -> {
test();
});
}
// 方法具有阻塞作用,也就是说主线程在这里暂停
countDownLatch.await();
long totalNumber = 0;
for (Account i : testAccount) {
if (i.getBalance() < 0) {
System.out.println("余额小于0: " + i.toString());
exit(-1);
}
totalNumber += i.getBalance();
}
System.out.println("总余额: " + totalNumber);
/**
* 线程池使用完毕要关闭
*/
threadPool.shutdown();
}
}
class Allocator {
/**
* 单例的账号管理对象
*/
private volatile static Allocator singleAllocator;
/**
* 有进行转账的账号都记录到这里。
*/
private Set<Account> accountSet = new HashSet<>();
/**
* 双层锁校验, 确保获得到的锁对象是单例的。
*
* @return
*/
public static Allocator getAllocator() {
if (null == singleAllocator) {
synchronized (Allocator.class) {
if (null == singleAllocator) {
singleAllocator = new Allocator();
}
}
}
return singleAllocator;
}
/**
* @param source 转出账户
* @param target 转入账户
*/
public synchronized void addLock(Account source, Account target) {
while (accountSet.contains(source) || accountSet.contains(target)) {
//如果转出账户 和 转入账户有一方已经得到锁资源, 那么另外一方不获取锁资源。
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
accountSet.add(source);
accountSet.add(target);
}
public synchronized void releaseLock(Account source, Account target) {
accountSet.remove(source);
accountSet.remove(target);
notifyAll();
}
}
class Account {
/**
* 余额
*/
private Integer balance;
/**
* 账户id
*/
private Integer id;
Account(Integer id) {
this.id = id;
balance = 100;
}
public Integer getBalance() {
return balance;
}
public void setBalance(Integer balance) {
this.balance = balance;
}
@Override
public String toString() {
return "账号id " + id.toString() + " 余额 ¥" + balance.toString() + " ";
}
//转账
public void transactionToTarget(Integer money, Account target) {
//转账过程先上锁
Allocator.getAllocator().addLock(this, target);
System.out.println("转账开始--转出账号:" + this.toString() + "|转入账号:" + target.toString());
if (this.balance - money >= 0) {
this.balance -= money;
target.setBalance(target.getBalance() + money);
System.out.println("转账成功--转出账号:" + this.toString() + "|转入账号:" + target.toString());
} else {
System.out.println("转账失败--转出账号:" + this.toString() + "|想转金额:¥" + money.toString());
}
//释放锁
Allocator.getAllocator().releaseLock(this, target);
}
}
2. 线程间的共享:
2.1 synchronized关键字
2.1.1 介绍
使用synchronized关键字后, 被它修饰的方法或者代码块在任何时候只能有一个线程执行。一般我们使用synchronized关键字来修饰实例方法,修饰静态方法,修饰代码块。
synchronized加到static静态方法上以及加到代码块中是synchronized (xxx.class){…} 这种格式, 那么此时加的是类锁。 如果synchronized加到实例方法上以及代码块中是synchronized (this){…}这种格式,那么此时加的是对象锁。
在类内部声明一个私有属性如private Object lock,在需要加锁的代码段synchronized(lock). 此时加的是私有锁。
同一个对象: 不同线程之间, 对象锁之间存在竞争关系, 类锁之间存在竞争关系, 对象锁和类锁不存在竞争关系, 对象锁和私有锁也不存在竞争关系.
不同对象: 不同线程之间, 对象锁不存在竞争关系, 修饰静态方法和修改类(xxx.class)的类锁存在竞争关系, 修饰静态方法的类锁和修饰静态变量的类锁不存在竞争关系。(代码示例如下)
package com.joker.thread.share;
public class ObjectLock {
// 用于类锁的计数
private static int i = 0;
// 私有锁
private Object lock = new Object();
private static Object staticLock = new Object();
/**
* 无锁方法
*
* @param threadId
* @param objThread
*/
public void noSyncLock(long threadId, ObjThread objThread) {
System.out.println("noSyncLock...class is " + objThread + " threadId is " + threadId + " TIME = " + System.currentTimeMillis() + "ms");
}
/**
* 对象锁方法1
*/
public synchronized void synOnMethod() {
System.out.println("synOnMethod begins" + ", time = "
+ System.currentTimeMillis() + "ms");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("synOnMethod ends");
}
/**
* 对象锁方法2,采用synchronized (this)来加锁
*/
public void synInMethod() {
synchronized (this) {
System.out.println("synInMethod begins" + ", time = "
+ System.currentTimeMillis() + "ms");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("synInMethod ends");
}
}
/**
* 对象锁方法3
*/
public void synMethodWithObj() {
synchronized (lock) {
System.out.println("synMethodWithObj begins" + ", time = "
+ System.currentTimeMillis() + "ms");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("synMethodWithObj ends");
}
}
// ------------------------------------------------------------------------------
/**
* 类锁 锁静态变量
*/
public static void staticSynMethodWithObj() {
synchronized (staticLock) {
System.out.println("staticSynMethodWithObj begins" + ", time = "
+ System.currentTimeMillis() + "ms");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("staticSynMethodWithObj ends");
}
}
/**
* 类锁 加载静态方法上
*/
public static synchronized void increament() {
System.out.println("class synchronized. i = " + i + ", time = "
+ System.currentTimeMillis() + "ms");
i++;
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("class synchronized ends.");
}
/**
* 类锁 锁住ObjectLock.class
*/
public static void staticSynInMethod() {
synchronized (ObjectLock.class) {
System.out.println("staticSynInMethod begins" + ", time = "
+ System.currentTimeMillis() + "ms");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("staticSynInMethod ends");
}
}
public static void main(String[] args) {
System.out.println("start time = " + System.currentTimeMillis() + "ms");
ObjectLock test = new ObjectLock();
for (int i = 0; i < 3; i++) {
//ObjectLock test = new ObjectLock();
Thread thread = new ObjThread(test, i);
thread.start();
}
}
}
class ObjThread extends Thread {
ObjectLock lock;
int i = 0;
public ObjThread(ObjectLock lock, int i) {
this.lock = lock;
this.i = i;
}
@Override
public void run() {
//无锁方法
//lock.noSyncLock(this.getId(), this);
//对象锁方法1,采用synchronized synInMethod的方式
//lock.synInMethod();
//对象锁方法2,采用synchronized(this)的方式
//lock.synOnMethod();
//私有锁方法,采用synchronized(object)的方式
//lock.synMethodWithObj();
//类锁方法,采用static synchronized increment的方式
ObjectLock.increament();
ObjectLock.staticSynInMethod();
//ObjectLock.staticSynMethodWithObj();
}
}
2.1.2 synchronized经典例子(单例代码实现):
/**
* 双重校验锁 实现单例
*/
public class Singleton {
/**
* uniqueSingleton = new Singleton(); 可以拆分为三步去执行:
* 1. 为 uniqueSingleton分配内存空间
* 2. 初始化 uniqueSingleton
* 3. 将 uniqueSingleton 指向分配的内存空间
* 正常情况下, 是上述三步, 但是jvm存在指令重排的特性,执行顺序变成了1 -> 3 -> 2. 所以在多线程情况下,
* 会获得没有初始化的实例对象。但是volatile可以禁止jvm指令重排,保证多线程情况也可以使用。
*/
private volatile static Singleton uniqueSingleton;
public static Singleton getSingleton() {
// 先判断对象是否已经实例化, 没有实例化才可以进加锁代码
if (uniqueSingleton == null) {
// class类 上锁
synchronized (Singleton.class) {
if (uniqueSingleton == null) {
uniqueSingleton = new Singleton();
}
}
}
return uniqueSingleton;
}
}
2.1.3 synchronized底层实现原理
补充: java的对象头和monitor
在jvm中, 对象是分为三个部分存在的: 对象头, 实例数据, 对齐填充. 实例数据存放类的属性数据信息, 包括父类的属性信息。对齐填充的作用是如果虚拟机要求对象起始地址必须是8字节整数倍, 对齐填充仅仅是为了使字节对齐。对象头主要结构是由Mark Word 和 Class Metadata Address组成,其中Mark Word存储对象的hashCode、锁信息(一个锁对应一个monitor)或分代年龄或GC标志等信息,Class Metadata Address是类型指针指向对象的类元数据,JVM通过该指针确定该对象是哪个类的实例。
PS: 在jdk6之前, 锁只有两种状态(无锁,重量级锁)。jdk6之后, 有了四种状态:无锁状态、偏向锁、轻量级锁、重量级锁
2.1.3.1 修饰代码块(上述单例的代码即符合)
执行 javap -c -s -v -l Singleton.class
使用synchronized同步代码块的首尾会出现monitorenter和monitorexit指令, 其中monitorenter指令指向代码同步开始的位置, monitorexit指向结尾位置。当执行minitorenter指令,线程就会获取monitor(也就是锁)的持有权。当计数器是0的时候,可以成功获取。获取后计数器+1。当执行monitorexit后, 将计数器置为0。
2.1.3.2 修饰方法:
public class SynchronizedDemo {
public synchronized void method() {
System.out.println("synchronized 代码块");
}
}

JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
2.1.4 synchronized优化
jdk1.6新增了两个锁状态(偏向锁,轻量级锁),通过锁消除,锁粗化,自旋锁等方法使用各种场景,给synchronized性能带来了很大的提升。
2.1.4.1 锁升级:
Synchronized 同步锁初始为偏向锁,随着线程竞争愈发激烈,偏向锁升级到轻量级锁,最终升级到重量级锁。
偏向锁: 一个线程获得锁以后, 那么这个锁进入偏向状态,当该线程再次请求锁时候,只要检查对象是不是偏向锁
并且当前线程是不是和对象头里记录的是不是一致即可。不需要再做任何同步操作(锁申请之类的操作)。
轻量级锁: 当存在第二个线程加入了锁竞争,偏向锁就会立即升级为轻量级锁。在轻量级锁状态下继续锁竞争,没有抢到锁的线程将自旋,即不停地循环判断锁是否能够被成功获取。
PS: 锁竞争 -> 如果多个线程轮流获取一个锁,但是每次获取锁的时候都很顺利,没有发生阻塞,那么就不存在锁竞争。只有当某线程尝试获取锁的时候,发现该锁已经被占用,只能等待其释放,这才发生了锁竞争。
重量级锁: 如果锁竞争情况严重,某个达到最大自旋次数的线程,锁就会被升级成重量级锁,当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是忙等),等待将来被唤醒。
2.1.4.2 锁消除:对运行上下文进行扫描,去除不可能存在竞争的锁
2.1.4.3 锁粗化: 通过扩大锁的范围,避免反复加锁和释放锁
2.1.5 synchronized与ReenTrantLock区别:
1.两者都是可重入锁, 可重入锁概念请见:https://www.yuque.com/wangyanyang/qz7fwa/lqczuy.
2.synchronized依赖于 JVM. 而 ReenTrantLock依赖于API.
3.ReenTrantLock增加了高级功能:
ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。
synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类也可以实现,但是需要借助于Condition接口与newCondition() 方法。在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以**有选择性的进行线程通知**,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”。
2.2. volatile关键字
2.2.1 已有的问题:
在java内存模型中,主内存存放全局变量, 在进行变量的修改的时候, 是当前线程先把数据copy到自己当前的线程中来, 做完一系列操作后再会写到主内存中。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。
2.2.2 解决的问题:
当变量被声明为volatile之后, 就告诉JVM, 这个变量是不稳定的. 每次使用它都到主内存中拿, 换句话说 volatile 关键字的主要作用就是**保证变量的可见性, 还有一个作用是防止指令重排序。

