nangongyanya 2019-12-07
https://www.cnblogs.com/zlxyt/p/11050346.html 挺好的 感觉这个文章写的 不过想要提高 还是得自己写代码 不写代码 肯定不行.
【概述】
JVM 通过 synchronized 关键字提供锁,用于在线程同步中保证线程安全。
【synchronized 实现原理】
synchronized 可以用于代码块或者方法中,产生同步代码区域,也叫互斥区。互斥区每次只能允许一个线程进入执行同步代码或重新进入执行剩余同步代码(参考线程进入等待状态后会唤醒,然后进入阻塞状态,重新获得锁的情况)。
synchronized 通过与一个对象进行绑定,或者说对一个对象进行加锁,并产生一个监控对象(monitor object)。如下图所示:
【synchronized 加锁对象】
JVM 中使用 synchronized 进行加锁的对象有两种:分别为存储于 Java 堆的实例对象和存储于方法区的类信息对象。
1). synchronized 修饰实例方法,是对该方法所属的具体实例对象加锁;
2). synchronized 修饰静态方法,是对该方法所属的类信息对象加锁;
3). synchronized 同步代码块中的参数为 Class 对象或静态对象,则对 Class 对应的类信息对象加锁;
4). synchronized 同步代码块中的参数非 Class 对象或静态对象,而为其他实例对象,则对具体的实例对象加锁。
对实例对象和类信息对象加锁的区别:
【synchronized 代码实践】
1). 多线程并发执行同一个实例对象的方法, 没有加锁的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /** * 多线程并发执行同一个实例对象的方法, 没有加锁的情况 */ public class Main{ public static void main(String[] args){ Command c = new Command(); int nThreads = 10 ; Thread[] arr = new Thread[nThreads]; for ( int n = 0 ; n < nThreads; n++){ arr[n] = new Thread(c); } for ( int n = 0 ; n < nThreads; n++){ arr[n].start(); } } } class Command implements Runnable{ private int i = 0 ; @Override public void run() { add(); } public void add(){ i++; System.out.println(Thread.currentThread().getName() + ": " + i); } } |
从下面打印结果可以看出:在没有加锁的情况下,多线程并发执行 i++,并没有按照顺序输出,并出现了线程安全的问题。
2). 多线程并发,对实例方法进行加锁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | /** * 多线程并发,对实例方法进行加锁 */ public class Main{ public static void main(String[] args){ Command c = new Command(); int nThreads = 10 ; for ( int n = 0 ; n < nThreads; n++){ new Thread(c).start(); } } } class Command implements Runnable{ private int i = 0 ; @Override public void run() { add(); } public synchronized void add(){ i++; System.out.println(Thread.currentThread().getName() + ": " + i); } } |
打印结果: 对实例方法进行加锁,即对实例对象进行加锁,多线程并发执行实例对象的方法时互斥执行,结果按顺序输出,且解决了线程安全的问题。
3). 多线程并发执行实例对象的方法时,同步代码块锁住实例对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | /** * 多线程并发执行实例对象的方法时,同步代码块锁住实例对象 */ public class Main{ public static void main(String[] args){ Command c = new Command(); int nThreads = 10 ; for ( int n = 0 ; n < nThreads; n++){ new Thread(c).start(); } } } class Command implements Runnable{ private int i = 0 ; @Override public void run() { add(); } public void add(){ synchronized ( this ){ i++; System.out.println(Thread.currentThread().getName() + ": " + i); } } } |
打印结果:可以看出同步代码块锁住实例对象的效果和对实例方法的效果一样,多线程并发执行实例对象的方法时互斥执行,结果按顺序输出,且解决了线程安全的问题。实际上这两种方法都是对实例对象进行加锁,不同的是同步方法(被加锁后的方法)互斥的内容是整个方法体,代码块互斥的内容是整个代码块,相对而言后者影响较小。使用同步代码块替代同步方法,缩小了锁粒度,也是锁优化的一种方式。
4). 多线程并发,对实例对象加锁,其他线程是否可以调用该实例对象的非同步代码区域?是否可以调用该实例对象的同步代码区域?
4.1). 设计了两个方法,一个生产方法(produce),一个消费方法(consume),对生成方法进行加锁,锁住实例对象,对消费方法不加锁,看下执行情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class Main{ public static void main(String[] args){ Command c = new Command(); int nThreads = 5 ; for ( int n = 0 ; n < nThreads; n++){ new Thread(c).start(); } } } class Command implements Runnable{ @Override public void run() { produce(); consume(); } public synchronized void produce(){ System.out.println(Thread.currentThread().getName() + " produce start" ); System.out.println(Thread.currentThread().getName() + " produce finish" ); } public void consume(){ System.out.println(Thread.currentThread().getName() + " consume start" ); System.out.println(Thread.currentThread().getName() + " consume finish" ); } } |
打印结果:可以看出当生产方法执行的时候,消费方法也在执行。由此可见,对实例对象加锁对于调用该实例对象的非同步代码区域,是没有影响的。
4.2). 设计了两个方法,一个生产方法(produce),一个消费方法(consume),对实例对象的生成方法和消费方法同时加锁,看下执行情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class Main{ public static void main(String[] args){ Command c = new Command(); int nThreads = 5 ; for ( int n = 0 ; n < nThreads; n++){ new Thread(c).start(); } } } class Command implements Runnable{ @Override public void run() { produce(); consume(); } public synchronized void produce(){ System.out.println(Thread.currentThread().getName() + " produce start" ); System.out.println(Thread.currentThread().getName() + " produce finish" ); } public synchronized void consume(){ System.out.println(Thread.currentThread().getName() + " consume start" ); System.out.println(Thread.currentThread().getName() + " consume finish" ); } } |
打印结果:可以看出生产方法和消费方法互斥执行。由此可见,对实例对象加锁对于调用该实例对象的同步代码区域,是影响的。不同的线程执行同一实例对象的不同的同步方法,需要竞争同一把锁,互斥执行。
5). 多线程并发执行静态同步方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | /** * 多线程并发执行静态同步方法 */ public class Main{ public static void main(String[] args){ Command c = new Command(); int nThreads = 10 ; for ( int n = 0 ; n < nThreads; n++){ new Thread(c).start(); } } } class Command implements Runnable{ private int i = 0 ; @Override public void run() { add(); } public synchronized void add(){ i++; System.out.println(Thread.currentThread().getName() + ": " + i); } } |
打印结果:
6). 多线程并发,同步代码块锁住类信息对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | /** * 多线程并发执行静态同步方法 */ public class Main{ public static void main(String[] args){ Command c = new Command(); int nThreads = 10 ; for ( int n = 0 ; n < nThreads; n++){ new Thread(c).start(); } } } class Command implements Runnable{ private int i = 0 ; @Override public void run() { add(); } public void add(){ synchronized (Command. class ){ i++; System.out.println(Thread.currentThread().getName() + ": " + i); } } } |
打印结果:
7). 多线程并发,对类信息对象加锁,是否可以调用不同实例对象的同步代码区域?
7.1) 多线程并发执行不同实例对象的同步方法,不对类信息对象加锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public class Main{ public static void main(String[] args){ int nThreads = 5 ; for ( int n = 0 ; n < nThreads; n++){ new Thread( new Command()).start(); } } } class Command implements Runnable{ @Override public void run() { produce(); consume(); } public synchronized void produce(){ System.out.println(Thread.currentThread().getName() + " produce start" ); System.out.println(Thread.currentThread().getName() + " produce finish" ); } public synchronized void consume(){ System.out.println(Thread.currentThread().getName() + " consume start" ); System.out.println(Thread.currentThread().getName() + " consume finish" ); } } |
打印结果:可以看出不同实例对象并发执行同步代码块,不会互斥执行。
7.2) 多线程并发执行不同实例对象的同步方法,对类信息对象加锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public class Main{ public static void main(String[] args){ int nThreads = 5 ; for ( int n = 0 ; n < nThreads; n++){ new Thread( new Command()).start(); } } } class Command implements Runnable{ @Override public void run() { produce(); consume(); } public void produce(){ synchronized (Command. class ){ System.out.println(Thread.currentThread().getName() + " produce start" ); System.out.println(Thread.currentThread().getName() + " produce finish" ); } } public synchronized void consume(){ System.out.println(Thread.currentThread().getName() + " consume start" ); System.out.println(Thread.currentThread().getName() + " consume finish" ); } } |
打印结果:在生产方法(produce)对类信息对象进行加锁,可以看到生成方法(produce)执行时与消费方法(consume)是互斥的。