一、内存模型
高速缓存
因为CPU执行速度和内存数据读写速度差距很大,因此CPU往往包含高速缓存
结构。
缓存不一致问题
执行下面的代码:
int i = 0;i = i + 1;复制代码
当线程执行这个语句时,会先从主存当中读取i的值i = 0
,然后复制一份到高速缓存
当中,然后CPU执行指令对i
进行加1操作,然后将数据写入高速缓存,最后将高速缓存中i
最新的值刷新到主存
当中。
可能存在情况:初始时,两个线程分别读取i的值存入各自所在的CPU的高速缓存当中,然后线程1
进行加1操作,然后把i的最新值1写入到内存。此时线程2的高速缓存当中i的值还是0,进行加1操作之后,i的值为1,然后线程2
把i的值写入内存。
也就是说,如果一个变量在多个CPU中都存在缓存(多线程情况),那么就可能存在缓存不一致的问题。
缓存不一致的解决
一般有两种解决办法:
- 总线加锁
因为CPU和其他部件进行通信都是通过总线来进行的,如果对总线加锁的话,也就是说阻塞了其他CPU对其他部件访问(如内存),从而使得只能有一个CPU能使用这个变量的内存。
- 缓存一致性协议
由于在锁住总线期间,其他CPU无法访问内存,导致效率低下。所以就出现了缓存一致性协议。最出名的就是Intel的
MESI协议
,MESI协议
保证了每个缓存中使用的共享变量的副本是一致的。MESI协议
核心思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
二、线程安全问题
产生原因
从前面的分析,在并发编程(多线程编程)中,可能出现线程安全的问题:
-
多个线程在操作共享的数据。
-
操作共享数据的线程代码有多条。
-
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算。
并发的核心概念
三个核心概念:原子性、可见性、顺序性。
- 原子性:跟数据库事务的原子性概念差不多,即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)。
锁和同步
(同步方法和同步代码块)、CAS
(CPU级别的CAS指令cmpxchg
)。
- 可见性:当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到。
volatile
关键字来保证可见性。
- 顺序性:程序执行的顺序按照代码的先后顺序执行。因为处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的-即
指令重排序
。
volatile
在一定程序上保证顺序性,另外还可以通过synchronized
和锁
来保证顺序性。
三、Java对象头的结构
Java对象可以作为并发编程中的锁。而锁实际上存在于Java对象头里。如果对象是数组类型,则虚拟机用 3 个 Word(字宽)存储对象头,如果对象是非数组类型,则用 2 字宽存储对象头。在 64 位虚拟机中,一字宽等于八字节,即 64bit
。
Java 对象头里的 Mark Word
里默认存储对象的 HashCode,分代年龄和锁标记位。32 位 JVM 的 Mark Word 的默认存储结构如下:
|-|25 bit|4bit|偏向锁标志位(1bit)|锁标志位(2bit)| |::|::|::|::|::| |无锁状态|对象的hashCode|对象分代年龄| |01|
64 位JVM的存储结构如下:
锁状态 | 25bit | 31bit | 1bit | 4bit | 1bit | 2bit | ||||||||||||||||||||||||||||||||||
在运行期间 在了解了相关概念后,接下来介绍Java是如何保证并发编程中的安全的。 四、synchronized用法
synchronized(对象){需要被同步的代码 ;}复制代码
修饰符 synchronized 返回值 方法名(){}复制代码
锁对象
实现原理在编译的字节码中加入了两条指令来进行代码的同步。 monitorenter :每个对象有一个
monitorexit:执行
好处和弊端好处:解决了线程的安全问题。 弊端:相对降低了效率,因为同步外的线程的都会判断同步锁。获得锁和释放锁带来性能消耗。 编译器对synchronized优化Java6 为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在Java6 里锁一共有四种状态:无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不能降级。
锁状态对应的Mark Word以32位JVM为例:
五、volatile
被修饰的变量包含两层语义:
**底层实现:**观察加入 六、Lock应用场景如果一个代码块被
如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,会让程序效率很差。 因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过 源码分析与Lock相关的接口和类位于 (1)Lock接口public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition();}复制代码
(2)ReentrantLock类
public ReentrantLock() { sync = new NonfairSync();}public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync();}复制代码
public void lock() { sync.lock();}复制代码 而 abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -5179523762034025860L; abstract void lock();复制代码 其 public ReentrantLock() { sync = new NonfairSync();}//删去一些方法static final class NonfairSync extends Sync { final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }}复制代码 而 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();}复制代码 尝试进一步获取锁(调用继承自父类 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false;}复制代码 首先会判断 如果 即回到: public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();}复制代码
公平锁和非公平锁的释放流程都是一样的:public void unlock() { sync.release(1);}public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) //唤醒被挂起的线程 unparkSuccessor(h); return true; } return false;}//尝试释放锁protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free;} 复制代码 (3)ReadWriteLock接口与ReentrantReadWriteLock类
public interface ReadWriteLock { Lock readLock(); Lock writeLock();}复制代码 在
在 static final int SHARED_SHIFT = 16; static final int SHARED_UNIT = (1 << SHARED_SHIFT); static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; //获取读锁的占有次数 static int sharedCount(int c) { return c >>> SHARED_SHIFT; } //获取写锁的占有次数 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } //线程的id和对应线程获取的读锁的数量 static final class HoldCounter { int count = 0; // Use id, not reference, to avoid garbage retention final long tid = Thread.currentThread().getId(); } //线程变量保存线程和线程中获取的读写的数量 static final class ThreadLocalHoldCounter extends ThreadLocal 其中,包含两个静态内部类: 获取读锁:
获取写锁:
final boolean writerShouldBlock() { return hasQueuedPredecessors(); }//判断是否堵塞public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }复制代码 七、比较Lock和synchronized
synchronized和volatile
七、死锁问题死锁有四个必要条件,打破一个即可去除死锁。 四个必要条件:
死锁的例子同步嵌套时,两个线程互相锁住,都不释放,造成死锁。 举例: 创建两个字符串a和b,再创建两个线程A和B,让每个线程都用synchronized锁住字符串(A先锁a,再去锁b;B先锁b,再锁a),如果A锁住a,B锁住b,A就没办法锁住b,B也没办法锁住a,这时就陷入了死锁。 public class DeadLock { public static String obj1 = "obj1"; public static String obj2 = "obj2"; public static void main(String[] args){ Thread a = new Thread(new Lock1()); Thread b = new Thread(new Lock2()); a.start(); b.start(); } }class Lock1 implements Runnable{ @Override public void run(){ try{ System.out.println("Lock1 running"); while(true){ synchronized(DeadLock.obj1){ System.out.println("Lock1 lock obj1"); Thread.sleep(3000);//获取obj1后先等一会儿,让Lock2有足够的时间锁住obj2 synchronized(DeadLock.obj2){ System.out.println("Lock1 lock obj2"); } } } }catch(Exception e){ e.printStackTrace(); } }}class Lock2 implements Runnable{ @Override public void run(){ try{ System.out.println("Lock2 running"); while(true){ synchronized(DeadLock.obj2){ System.out.println("Lock2 lock obj2"); Thread.sleep(3000); synchronized(DeadLock.obj1){ System.out.println("Lock2 lock obj1"); } } } }catch(Exception e){ e.printStackTrace(); } }}复制代码 八、锁的概念在 java 中锁的实现主要有两类:内部锁
关于JUC包含了两个子包:atomic以及lock,另外在concurrent下的阻塞队列以及executors,以后再深入学习吧,下面这个图很是经典: 参考链接 |