一、加synchronized的两种方法

1. 加在普通方法上

加在普通方法上本质上是synchronized(this),是对调用本方法的对象上锁,即理解为:调用obj.test()方法时,只有拿到obj这个对象的对象锁,才可以执行test方法里的代码,否则就会阻塞在当前位置;

  1. public class Synchronized {
  2. public synchronized void test(){
  3. }
  4. }

等价于:

  1. public class Synchronized {
  2. public void test(){
  3. synchronized (this){
  4. }
  5. }
  6. }

2. 加在静态方法上

synchronized加在静态方法上,相当于对整个类的对象上锁,即当通过”类名.test()”调用方法时,会先看当前线程是否持有”类名.class”对象的对象锁,如果持有才可以进一步访问方法内的代码,否则就会阻塞到当前位置。

  1. public class Synchronized {
  2. public synchronized static void test(){
  3. }
  4. }

等价于:

  1. public class Synchronized {
  2. public static void test(){
  3. synchronized (Synchronized.class){
  4. }
  5. }
  6. }

二、不加synchronized的方法

不加synchronized的方法就好比不遵守规则的人,不去老实排队,就会胡乱访问临界资源,造成临界资源被随意篡改,不能保证原子性。

三、对象锁类设计总结

  • 将共享资源作为该对象锁类的成员变量;
  • 访问该共享资源成员变量的方法都要加上synchronized块;
  • 创建对象锁类对象,在不同线程中通过该对象调用方法以实现分割对临界资源的访问;

四、线程八锁

synchronized作用在方法上的习题,其实是考察synchronized锁住的是哪个对象

情况1:

  1. public class Test8Blocks {
  2. public static void main(String[] args) {
  3. Number n1 =new Number();
  4. //因为是在方法上的synchronized,所以是在锁n1对象;
  5. new Thread(()->{
  6. System.out.println("begin");
  7. n1.a();
  8. }).start();
  9. new Thread(()->{
  10. System.out.println("begin");
  11. n1.b();
  12. }).start();
  13. }
  14. }
  15. class Number{
  16. public synchronized void a(){
  17. System.out.println("1");
  18. }
  19. public synchronized void b(){
  20. System.out.println("2");
  21. }
  22. }

线程1与线程2启动后,比如线程1先执行到n1.a()方法,那么线程1则会占用该n1对象的对象锁,所以当线程2执行到n1.b()时则会阻塞住,当n1.a()方法执行完毕后,锁释放,线程2的b方法才可以继续执行。

情况2:

  1. public class Test8Blocks {
  2. public static void main(String[] args) {
  3. Number n1 =new Number();
  4. //因为是在方法上的synchronized,所以是在锁n1对象;
  5. new Thread(()->{
  6. System.out.println("begin");
  7. n1.a();
  8. }).start();
  9. new Thread(()->{
  10. System.out.println("begin");
  11. n1.b();
  12. }).start();
  13. }
  14. }
  15. class Number{
  16. public synchronized void a(){
  17. Thread.sleep(1);
  18. System.out.println("1");
  19. }
  20. public synchronized void b(){
  21. System.out.println("2");
  22. }
  23. }

同情况1类似,共有两种执行情况:
1)1秒后,打印1和2
2)先打印2,然后1秒后打印1

注意sleep并不会释放锁,线程执行完毕才会释放锁。

情况3:

  1. public class Test8Blocks {
  2. public static void main(String[] args) {
  3. Number n1 =new Number();
  4. //因为是在方法上的synchronized,所以是在锁n1对象;
  5. new Thread(()->{
  6. System.out.println("begin");
  7. n1.a();
  8. }).start();
  9. new Thread(()->{
  10. System.out.println("begin");
  11. n1.b();
  12. }).start();
  13. new Thread(()->{
  14. System.out.println("begin");
  15. n1.c();
  16. }).start();
  17. }
  18. }
  19. class Number{
  20. public synchronized void a(){
  21. System.out.println("1");
  22. }
  23. public synchronized void b(){
  24. System.out.println("2");
  25. }
  26. public void c(){
  27. System.out.println("3");
  28. }
  29. }

加入没有synchronized的c方法,当n1.c()调用的时候则不需要考虑是否互斥,因为只有方法上加了synchronized才会在执行的时候考虑当前线程是否拿到了n1的对象锁,而不加synchronized的方法只是正常方法,随意调用。

情况4:

  1. public class Test8Blocks {
  2. public static void main(String[] args) {
  3. Number n1 =new Number();
  4. Number n2 = new Number();
  5. //因为是在方法上的synchronized,所以是在锁n1对象;
  6. new Thread(()->{
  7. System.out.println("begin");
  8. try {
  9. n1.a();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }).start();
  14. new Thread(()->{
  15. System.out.println("begin");
  16. n2.b();
  17. }).start();
  18. }
  19. }
  20. class Number{
  21. public synchronized void a() throws InterruptedException {
  22. Thread.sleep(1);
  23. System.out.println("1");
  24. }
  25. public synchronized void b(){
  26. System.out.println("2");
  27. }
  28. }

因为拥有两个对象n1和n2,所以实际上两个线程互不干扰,因为不会存在两个线程抢占一把锁的情况,线程1独占n1对象锁,线程2独占n2对象锁。注意只有多个线程共用同一把对象锁,才会达到互斥的目的,因为调用方法时会考虑当前线程是否拿到当前对象的锁。

情况5:

  1. public class Test8Blocks {
  2. public static void main(String[] args) {
  3. Number n1 =new Number();
  4. Number n2 = new Number();
  5. //因为是在方法上的synchronized,所以是在锁n1对象;
  6. new Thread(()->{
  7. System.out.println("begin");
  8. try {
  9. n1.a();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }).start();
  14. new Thread(()->{
  15. System.out.println("begin");
  16. n2.b();
  17. }).start();
  18. }
  19. }
  20. class Number{
  21. public static synchronized void a() throws InterruptedException {
  22. Thread.sleep(1);
  23. System.out.println("1");
  24. }
  25. public synchronized void b(){
  26. System.out.println("2");
  27. }
  28. }

注意改动点是:a方法变成static synchronized方法,故调用该方法时会查看当前线程是否拥有“类对象锁”,而调用b方法还是查看当前线程是否拥有“实例n1对象锁”
所以实际上两个线程是拥有两个不同的对象锁,互不干扰。

情况6:

  1. public class Test8Blocks {
  2. public static void main(String[] args) {
  3. Number n1 =new Number();
  4. Number n2 = new Number();
  5. //因为是在方法上的synchronized,所以是在锁n1对象;
  6. new Thread(()->{
  7. System.out.println("begin");
  8. try {
  9. n1.a();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }).start();
  14. new Thread(()->{
  15. System.out.println("begin");
  16. n2.b();
  17. }).start();
  18. }
  19. }
  20. class Number{
  21. public static synchronized void a() throws InterruptedException {
  22. Thread.sleep(1);
  23. System.out.println("1");
  24. }
  25. public static synchronized void b(){
  26. System.out.println("2");
  27. }
  28. }

较情况5的改动是,将b方法也变成了static synchronized方法,则线程1和线程2会抢占”类对象锁”,会出现两线程互斥的情况,达到了控制资源访问的目的。

情况7:

  1. public class Test8Blocks {
  2. public static void main(String[] args) {
  3. Number n1 =new Number();
  4. Number n2 = new Number();
  5. //因为是在方法上的synchronized,所以是在锁n1对象;
  6. new Thread(()->{
  7. System.out.println("begin");
  8. try {
  9. n1.a();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }).start();
  14. new Thread(()->{
  15. System.out.println("begin");
  16. n2.b();
  17. }).start();
  18. }
  19. }
  20. class Number{
  21. public static synchronized void a() throws InterruptedException {
  22. Thread.sleep(1);
  23. System.out.println("1");
  24. }
  25. public synchronized void b(){
  26. System.out.println("2");
  27. }
  28. }

创建了两个对象,n1和n2,n1.a()实际上锁住的是类对象,n2.b()锁住的是n2对象,所以两个线程仍然是锁住的不同对象,故可以看出两个线程仍然不互斥。

情况8:

  1. public class Test8Blocks {
  2. public static void main(String[] args) {
  3. Number n1 =new Number();
  4. Number n2 = new Number();
  5. //因为是在方法上的synchronized,所以是在锁n1对象;
  6. new Thread(()->{
  7. System.out.println("begin");
  8. try {
  9. n1.a();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }).start();
  14. new Thread(()->{
  15. System.out.println("begin");
  16. n2.b();
  17. }).start();
  18. }
  19. }
  20. class Number{
  21. public static synchronized void a() throws InterruptedException {
  22. Thread.sleep(1);
  23. System.out.println("1");
  24. }
  25. public static synchronized void b(){
  26. System.out.println("2");
  27. }
  28. }

两个方法都经static synchronized修饰,故调用方法时n1.a()和n2.b()锁住的是同一个类对象Number.CLASS,所以即使是两个对象在访问,仍然可以实现互斥。