对象在内存中的内存布局
用sychronized锁住对象后该对象的锁状态升级过程:new - 无锁态 - 偏向锁 - 轻量级锁/自旋锁/无锁 (CAS)- 重量级锁 - GC标记信息
线程的几个状态
NEW(新建状态)
Runnable
Ready(就绪状态,线程被放在等待队列中,等着被CPU执行)
Running(运行状态,被扔到CPU中执行)
Blocked
Waiting
TimedWaiting
Terminated(终止态)
三种新建线程的方法
实现Thread类
实现Runnable接口
线程池
线程的常用方法:
sleep(),沉睡一段时间(当前线程回到就绪状态),这段时间CPU执行其它线程
yield(),和sleep()类似,让出CPU,当前线程回到就绪状态。使用很少见
join(),通知其它线程获得CPU执行,比如在t1线程内运行t2.join(),意思就是t1线程通知t2线程执行,自己回到就绪状态。
Synchronized讲解
synchronized实现过程:(不能禁止指令重排)
Java代码:synchronized
monitorenter、moniterexit
执行过程中自动升级(偏向锁、自旋锁、重量级锁)
更底层的实现lock comxchg
volatile讲解:
保证变量的各线程可见性/数据一致性 (多个线程要用到变量时,重新去内存拿)
禁止CPU指令重排(在单线程没问题,多线程就会出现问题。为什么要指令重排,其实就是因为CPU太快了,而访问内存比访问缓存又慢了太多)
举个例子:对象的初始化三个步骤Person p = new Person("zeng", 24);
申请对象Person的内存,这个时候给实例变量设置了默认值,比如name = null; age = 0;
调用该对象的构造函数进行真正的初始化实例变量name = "zeng"; age = 24;
返回对象Person给p
volatile不能实现synchronized的原子性操作
比如定义一个变量volatile int count = 0;10个线程分别count++加1000次,最终的count不一定会是10000,因为这里的count++并不是一个原子性操作,它包含好几个指令,所以为了要实现整个的count++原子性操作,也就是必须要使用sychronized对count++加锁。
再注意一些问题:
在用synchronized锁住一个对象时,这个时候不能将这个引用去指向另一个对象
不要用synchronized去锁一个String、Integer等基本数据类型的封装类的对象
ThreadLocal讲解
ThreadLocal可以作为每个线程存放属于自己变量的容器。
存放的容器是ThreadLocalMap。其中ThreadLocalMap属于每一个线程,也就是说每一个线程都有自己的ThreadLocalMap。
ThreadLocalMap中的Entry的key为什么要设置为虚引用?(这个是重点)
首先ThreadLocalMap的底层存储格式为Entry的key-value数组,其中的key就是当前的引用型变量ThreadLocal(如下图所示)。试想,如果我们在线程运行过程中,将ThreadLocal置为null,那么这个时候Entry中的ThreadLocal理应被回收了,但如果Entry的key被设置成强引用则该ThreadLocal就不能被回收,所以需要设置为弱引用,设置为弱引用之后,垃圾回收线程只要发现有弱引用指向的对象,那么就会回收这个对象,这样在多线程使用中可以避免内存泄漏。
CAS(无锁优化/自旋):
CompareAndSwap
Java里面java.util.concurrnet.atomic.AtomicXXX开头的类都是使用CAS自旋锁实现的。内部都是使用UnSafe这个类的compareAndSet等操作实现线程安全地修改值
举个例子:AtomicInteger count = new AtomicInteger(0);在上面的volatile的讨论中,count++如果不加sychronized锁会导致非原子性操作,但这里直接使用AtomicInteger即可实现线程可见、原子性操作,将count++到10000。并且不需要volatile、synchronized。
ABA问题(1变为2又被变为1),加版本号version
所有的Java中CAS的操作基本上都是用的UnSafe这个类,这个UnSafe使Java语言有了像C++的直接操作JVM内存的能力。
ReentrantLock(可重入锁,公平锁(默认是非公平锁))本身底层也是CAS
可以替代synchronized,替换方法:lock.lock();
可以通过lock.interupt的方法将该锁设置为可以通过interup方法唤醒正在wait的线程
相比上个特点,synchronized的线程,wait之后必须通过其它线程的notify()才能唤醒
如果设置为公平锁,那么线程在抢一个资源时,会进入优先队列排队按先后顺序等待
synchronized是非公平锁
synchronized自动加锁解锁,ReentrantLock手动加锁解锁lock.lock()
底层实现:ReentrantLock是CAS的实现,synchronized底层是有锁的升级过程(4种)
CountDownLatch锁(倒计时完了继续执行(门栓))
CyclicBarrier锁(当线程数目到达某个数目(栅栏值)时,继续执行后面的事物)
Phase锁(阶段锁,CyclicBarrier的升级版本,有多个阶段,比如结婚现场有7个人,先7人到达现场,再7人吃完饭,再xxxxx)
ReadWriteLock(共享锁、排他锁、多个线程可以一起执行)
Semaphore(信号量,用于限流(仅允许几个线程同时工作))
Exchanger(两个线程运行时交换值)
LockSupport(可以通过park()方法随时将线程停止,并通过unpark()方法随时让某线程就绪)
面试题1:定义两个线程,A线程往容器里放数据,B线程监测容器容量为5时,停止运行
有3种方法
使用wait()与notify()方法的组合。这个很重要
使用门栓锁CountDownLatch
使用LockSupport直接park()与unpark()
面试题2:顺序打印A1B2C3……
面试题3:生产者消费者问题
版本1 通过synchronized、wait()、notify()实现
package zr.thread;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
/*
生产者与消费者实现1
写一个固定容量同步容器,拥有put和get方法, 以及getCount方法
能够支持2个生产者线程以及10个消费者线程的阻塞调用
使用wait()和notifyAll()来实现
这个方法是有瑕疵的,因为使用notifyAll()会唤醒所有的其它等待队列的线程,包括生产者、消费者
有没有办法只唤醒生产者,或者只唤醒消费者?
*/
/**
* @author ZR
* @Classname MyContainer1
* @Description 生产者消费者最简单写法
* @Date 2020/9/12 21:02
*/
public class MyContainer1 {
final private LinkedList lists = new LinkedList<>();
// 最多10个元素
final private int MAX = 10;
private int count = 0;
// 因为++count所以要加synchronized
public synchronized void put(T t){
// 想想为什么用while而不是if
while(lists.size() == MAX){
try{
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
lists.add(t);
++count;
// 通知所有消费者线程消费
// 这个方法其实是有点小瑕疵的,因为notifyAll()会叫醒所有的其它wait()线程,也包括了另一个生产者
this.notifyAll();
}
// 因为--count所以要加synchronized
public synchronized T get(){
T t = null;
while(lists.size() == 0){
try{
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
t = lists.removeFirst();
--count;
// 通知生产者进行生产
// 这个方法其实是有点小瑕疵的,因为notifyAll()会叫醒所有的其它wait()线程,也包括了其它消费者
this.notifyAll();
return t;
}
public static void main(String[] args){
MyContainer1 c = new MyContainer1<>();
// 启动消费者线程
for(int i = 0; i < 10; i++){
new Thread(()->{
for(int j = 0; j < 5; j++)
System.out.println(c.get());
}, "customer" + i).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 启动生产者线程
for(int i = 0; i < 2; i++){
new Thread(()->{
for(int j = 0; j < 25; j++)
c.put(Thread.currentThread().getName() + " " + j);
}, "producer" + i).start();
}
}
}
版本2 通过ReentrantLock实现
package zr.thread;
import com.sun.org.glassfish.external.statistics.CountStatistic;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author ZR
* @Classname MyContainer2
* @Description TODO
* @Date 2020/9/12 21:27
*/
public class MyContainer2 {
final private LinkedList lists = new LinkedList<>();
// 最多10个元素
final private int MAX = 10;
private int count = 0;
private Lock lock = new ReentrantLock();
// Condition的本质就是等待队列,在这里生产者在生产者的队列,消费者在消费者的队列
// 在Container1例中,等待队列只有一个,生产者和消费者都在里边儿
private Condition producer = lock.newCondition();
private Condition customer = lock.newCondition();
public void put(T t){
try {
// 需要手动加锁
lock.lock();
while(lists.size() == MAX)
producer.await();
lists.add(t);
++count;
// 通知消费者线程进行消费
customer.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 手动解锁
lock.unlock();
}
}
public T get(){
T t = null;
try {
lock.lock();
while(lists.size() == 0)
customer.await();
t = lists.removeFirst();
--count;
// 通知生产者线程生产
producer.signalAll();
} catch (InterruptedException e){
e.printStackTrace();
} finally {
lock.unlock();
}
return t;
}
public static void main(String[] args){
MyContainer2 c = new MyContainer2<>();
// 启动消费者线程
for(int i = 0; i < 10; i++){
new Thread(()->{
for(int j = 0; j < 5; j++)
System.out.println(c.get());
}, "customer" + i).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 启动生产者线程
for(int i = 0; i < 2; i++){
new Thread(()->{
for(int j = 0; j < 25; j++)
c.put(Thread.currentThread().getName() + " " + j);
}, "producer" + i).start();
}
}
}
AQS(CLH)队列
注意这里面的state根据不同的同步锁取不同的意义,比如:
ReentrantLock,state = 0代表unlock,state = 1代表lock
CountDownLatch,state = 5代表需要倒计数5个数,才继续下面的操作
下面的Node表示一个双向链表,这里面存储的就是线程Thread!!!!!注意看下图!!!!!!