首先我们了解到Java中的线程同步锁可以是任意对象。

1. synchronized的三种用法和区别

synchronized的三种应用方式:

1.作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;
2.作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;
3.作用于代码块,这需要指定加锁的对象,对所给的指定对象加锁,进入同步代码前 要获得指定对象的锁。

synchronized的三种应用方式

1.synchronized修饰实例方法(普通方法)

使用时,作用范围为整个函数,这里所谓的实例锁就是调用该实例方法(不包括静态方法)的对象。

  1. public class SyncTest implements Runnable{
  2. //共享资源变量
  3. int count = 0;
  4. @Override
  5. public synchronized void run() {
  6. for (int i = 0; i < 5; i++) {
  7. increaseCount();
  8. System.out.println(Thread.currentThread().getName()+":"+count++);
  9. }
  10. }
  11. public static void main(String[] args) throws InterruptedException {
  12. SyncTest syncTest1 = new SyncTest();
  13. // SyncTest syncTest2 = new SyncTest();
  14. Thread thread1 = new Thread(syncTest1,"thread1");
  15. Thread thread2 = new Thread(syncTest1, "thread2");
  16. thread1.start();
  17. thread2.start();
  18. }
  19. }
  1. /**
  2. * 输出结果
  3. thread1:0
  4. thread1:1
  5. thread1:2
  6. thread1:3
  7. thread1:4
  8. thread2:5
  9. thread2:6
  10. thread2:7
  11. thread2:8
  12. thread2:9
  13. */

2.synchronized修饰静态方法

当创建两个对象创建两个线程时,都加的有锁,但是仍会出错
eg:

  1. public class SyncTest implements Runnable{
  2. //共享资源变量
  3. int count = 0;
  4. @Override
  5. public synchronized void run() {
  6. for (int i = 0; i < 5; i++) {
  7. System.out.println(Thread.currentThread().getName()+":"+count++);
  8. }
  9. }
  10. public static void main(String[] args) throws InterruptedException {
  11. SyncTest syncTest1 = new SyncTest();
  12. SyncTest syncTest2 = new SyncTest();
  13. Thread thread1 = new Thread(syncTest1,"thread1");
  14. Thread thread2 = new Thread(syncTest2, "thread2");
  15. thread1.start();
  16. thread2.start();
  17. }
  18. /**
  19. * 输出结果
  20. thread1:0
  21. thread2:0
  22. thread1:1
  23. thread2:1
  24. thread1:2
  25. thread2:2
  26. thread1:3
  27. thread2:3
  28. thread1:4
  29. thread2:4
  30. */
  31. }

这个时候我们就要使用synchronized修饰静态方法来解决

  1. public class SyncTest implements Runnable {
  2. //共享资源变量
  3. static int count = 0;
  4. @Override
  5. public synchronized void run() {
  6. increaseCount();
  7. }
  8. private synchronized static void increaseCount() {
  9. for (int i = 0; i < 5; i++) {
  10. System.out.println(Thread.currentThread().getName() + ":" + count++);
  11. try {
  12. Thread.sleep(1000);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }
  18. public static void main(String[] args) throws InterruptedException {
  19. SyncTest syncTest1 = new SyncTest();
  20. SyncTest syncTest2 = new SyncTest();
  21. Thread thread1 = new Thread(syncTest1, "thread1");
  22. Thread thread2 = new Thread(syncTest2, "thread2");
  23. thread1.start();
  24. thread2.start();
  25. }
  26. /**
  27. * 输出结果
  28. thread1:0
  29. thread1:1
  30. thread1:2
  31. thread1:3
  32. thread1:4
  33. thread2:5
  34. thread2:6
  35. thread2:7
  36. thread2:8
  37. thread2:9
  38. */
  39. }

3.synchronized修饰代码块

在某些情况下,我们编写的方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了。所以他的作用范围为synchronizd(obj){}的这个大括号中

  1. public class SyncTest implements Runnable {
  2. //共享资源变量
  3. static int count = 0;
  4. private byte[] mBytes = new byte[0];
  5. @Override
  6. public synchronized void run() {
  7. increaseCount();
  8. }
  9. private void increaseCount() {
  10. //假设省略了其他操作的代码。
  11. //……………………
  12. synchronized (this) {
  13. for (int i = 0; i < 5; i++) {
  14. System.out.println(Thread.currentThread().getName() + ":" + count++);
  15. try {
  16. Thread.sleep(1000);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }
  22. }
  23. public static void main(String[] args) throws InterruptedException {
  24. SyncTest syncTest1 = new SyncTest();
  25. SyncTest syncTest2 = new SyncTest();
  26. Thread thread1 = new Thread(syncTest1, "thread1");
  27. Thread thread2 = new Thread(syncTest2, "thread2");
  28. thread1.start();
  29. thread2.start();
  30. }
  31. /**
  32. * 输出结果
  33. thread1:0
  34. thread2:0
  35. thread1:1
  36. thread2:2
  37. thread2:4
  38. thread1:3
  39. thread2:5
  40. thread1:5
  41. thread2:7
  42. thread1:6
  43. */
  44. }

总结:
1.修饰普通方法 一个对象中的加锁方法只允许一个线程访问。但要注意这种情况下锁的是访问该方法的实例对象, 如果多个线程不同对象访问该方法,则无法保证同步。
2.修饰静态方法 由于静态方法是类方法, 所以这种情况下锁的是包含这个方法的类,也就是类对象;这样如果多个线程不同对象访问该静态方法,也是可以保证同步的。
3.修饰代码块 其中普通代码块 如Synchronized(obj) 这里的obj 可以为类中的一个属性、也可以是当前的对象,它的同步效果和修饰普通方法一样;Synchronized方法 (obj.class)静态代码块它的同步效果和修饰静态方法类似。

2.synchronized和lock的区别?

主要相同点:Lock 能完成synchronized所实现的所有功能
主要不同点:Lock有比 synchronized更精确的线程语义和更好的性能。synchronized 会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally 从句中释放。

3.synchronized实现原理?

前提:每个对象都有一个对象头,对象头中有一个和synchronized相关的字段,该字段是一个指向monitor对象的指针,synchronized通过获取monitor对象的方式获取锁。(可以去这个链接查看一下更加详细的解释链接)

synchronized修饰代码块的底层原理

依赖于monitor对象的enter和exit操作,对应于monitorenter和monitorexit字节码指令,这两个指令分别指明了同步代码块的起始和结束位置,执行monitorenter时尝试获取对monitor的持有权,如果此时monitor的计数器为0,则获取到这个锁,并将计数器设置为1。

synchronized修饰方法的底层原理

方法级的同步是隐式的,不需要通过字节码指令来控制,JVM通过方法常量池中的方发表结构中的ACC_SYNCHRONIZED访问标志区分一个方法是否是同步方法,然后根据标志位的设置区获取monitor对象。

4.CAS和BAB?

4.1 简介

CAS原子操作是一条CPU指令,是解决多线程并发过程中加锁操作带来的性能损耗的一种方式 。
每个CAS原子操作都有三个相关的参数,分别是V/E/N,V表示要更新的变量,E表示期望的变量的值,N表示要修改成为的值。如果发现要更新的变量的值等于E则更新,否则不更新,可以直接退出,也可以执行若干个空循环后再判断是否满足要求。

4.2 优点

保证数据原子性,保证线程安全,代替加锁的性能损耗 ;
自旋锁的基础。

4.3 缺点

ABA问题,可以使用带有时间戳的对象引用或布尔类型的原子操作解决

image.png

循环时间长开销大的问题
只能保证一个共享变量的原子操作,可以通过原子操作类将多个变量合并为一个对象解决

5.对象的锁升级过程?