专栏首页渔夫Java中Synchronized的用法

Java中Synchronized的用法

版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons

引出: Java中synchronized修饰符在多线程同步中有所大展拳脚,所以十分有必要对其进行整理、对照和学习


synchronized修饰符的使用场景整理总结、分类

修饰对象

作用范围

作用对象

代码块(称为同步代码块)

大括号{}括起来的代码

调用这个代码块的对象

一般方法(被称为同步方法)

整个方法

调用这个方法的对象

静态的方法

整个静态方法

此类的所有对象

synchronized后面括号括起来的部分

此类的所有对象


一、修饰一个代码块

修饰的结果:

  1. 一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞;
  2. 多个线程访问各子的对象即使有synchronized修饰了同步代码块,但是互不阻塞,但是并不能保证静态变量的线程安全性。
  3. 当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。

1.synchronized修饰的方法使用:

	/**
	 * 同步线程
	 */
	class SyncThread implements Runnable {
   		private static int count;
   		public SyncThread() {
      	count = 0;
   	}

   	public  void run() {
      synchronized(this) {
         for (int i = 0; i < 5; i++) {
            try {
               System.out.println(Thread.currentThread().getName() + ":" + (count++));
               Thread.sleep(100);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }
   	}

   	public int getCount() {
      return count;
   		}
	}

2.验证调用代码(创建俩线程,调用一个对象)

public class codeBlock {
    	public static void main(String[] args) {
        	SyncThread syncThread = new SyncThread();
        	Thread thread1 = new Thread(syncThread, "SyncThread1");
        	Thread thread2 = new Thread(syncThread, "SyncThread2");
        	thread1.start();
        	thread2.start();
    	}
	}

3.控制台输出

SyncThread2:count:0
SyncThread2:count:1
SyncThread2:count:2
SyncThread2:count:3
SyncThread2:count:4
SyncThread2:count:5
SyncThread2:count:6
SyncThread2:count:7
SyncThread2:count:8
SyncThread2:count:9
SyncThread1:count:10
SyncThread1:count:11
SyncThread1:count:12
SyncThread1:count:13
SyncThread1:count:14
SyncThread1:count:15
SyncThread1:count:16
SyncThread1:count:17
SyncThread1:count:18
SyncThread1:count:19

4.修改代码块为俩对象(创建俩线程,分别对应俩对象):

public class codeBlock {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
        Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
        thread1.start();
        thread2.start();
   		 }
	}

5.控制台输出(没有保证到线程安全性, 主要由于count是静态变量)

SyncThread1:count:0
SyncThread2:count:1
SyncThread1:count:2
SyncThread2:count:2
SyncThread2:count:3
SyncThread1:count:4
SyncThread2:count:6
SyncThread1:count:5
SyncThread2:count:7
SyncThread1:count:8
SyncThread1:count:10
SyncThread2:count:9
SyncThread2:count:11
SyncThread1:count:12
SyncThread1:count:13
SyncThread2:count:14
SyncThread1:count:15
SyncThread2:count:15
SyncThread1:count:16
SyncThread2:count:16

6.其他线程可访问同一对象的非synchronized方法的证明思路:

我们对run方法进行一个线程名的选择,如果线程1、2能够不相互阻塞地进行运行,那么证明成功。

7.多个线程访问synchronized和非synchronized代码块:

class Counter implements Runnable{
   	private int count;

   	public Counter() {
      count = 0;
   	}

   	public void countAdd() {
      synchronized(this) {
         for (int i = 0; i < 5; i ++) {
            try {
               System.out.println(Thread.currentThread().getName() + ":" + (count++));
               Thread.sleep(100);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }
   	}
	//非synchronized代码块,未对count进行读写操作,所以可以不用synchronized
   	public void printCount() {
      for (int i = 0; i < 5; i ++) {
         try {
            System.out.println(Thread.currentThread().getName() + " count:" + count);
            Thread.sleep(100);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   	}

   	public void run() {
      String threadName = Thread.currentThread().getName();
      if (threadName.equals("A")) {
         countAdd();
      } else if (threadName.equals("B")) {
         printCount();
      		}
   		}	
	}

8.控制台输出(由于多线程的特性,每次运行的结果可能不同):

Thread2 count:0
Thread1:0
Thread1:1
Thread2 count:1
Thread2 count:2
Thread1:2
Thread1:3
Thread2 count:3
Thread1:4
Thread2 count:4

上面代码中countAdd是一个synchronized的,printCount是非synchronized的。从上面的结果中可以看出一个线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞。因为如果两个线程相互形成阻塞,那么对于静态变量count而言应当在线程1的一次循环中递增完毕,对于线程二而言只会只有4一个值,结果推翻了此假设,所以当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块,互不阻塞。


二、修饰一个代码块(非this,而是指定对象)

修饰的结果(同synchronized(this)):

  1. 一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞;
  2. 多个线程访问各子的对象即使有synchronized修饰了同步代码块,但是互不阻塞,但是并不能保证静态变量的线程安全性;
  3. 当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。

代码块:

1.synchronized修饰的方法使用:

	/**
	 * 同步线程
	 */
	class Food implements Runnable {
    public Food(Vegetables vegetables,Fruits fruits) {
        this.vegetables =vegetables;
        this.fruits=fruits;
    }
    private Vegetables vegetables;
    private Fruits fruits;

    @Override
    public void run() {
        synchronized (vegetables) {
            for (int i = 0; i < 10; i++) {
                vegetables.addVegetables("cabbage" + i);
                System.out.println(Thread.currentThread().getName() + ":" + vegetables.getLastVegetables());

            }
        }

        synchronized (fruits) {
            for (int i = 0; i < 10; i++) {
                fruits.addFruits("apple" + i);
                System.out.println(Thread.currentThread().getName() + ":" + fruits.getLastFruits());
            	}
        	}

    	}
	}

	class Fruits {
    	private String[] list = new String[10];
    	int index;

    	public Fruits() {
    	}

    	public void addFruits(String fruitname) {
        	if (index >= 0 && index <= 9) {
            	list[index++] = fruitname;
        	}
    	}

    	public String getLastFruits() {
        	return list[index-1];
    	}
	}

	class Vegetables {
    	private String[] list = new String[10];
    	int index;

    	public Vegetables() {
    	}

    	public void addVegetables(String Vegetablename) {
        	if (index >= 0 && index <= 9) {
            	list[index++] = Vegetablename;
        	}
    }

    	public String getLastVegetables() {
        	return list[index-1];
    	}
	}

2.验证调用代码(创建俩线程,调用一个对象)

public class codeBlock04 {
    	public static void main(String[] args) {
        	Food food = new Food(new Vegetables(),new Fruits());
	        Thread thread1 = new Thread(food,"Thread1");
        	Thread thread2 = new Thread(food,"Thread2");
        	thread1.start();
        	thread2.start();
	}
	}

3.控制台输出

Thread2:cabbage0
Thread2:cabbage1
Thread2:cabbage2
Thread2:cabbage3
Thread2:cabbage4
Thread2:cabbage5
Thread2:cabbage6
Thread2:cabbage7
Thread2:cabbage8
Thread2:cabbage9
Thread1:cabbage9
Thread2:apple0
Thread1:cabbage9
Thread2:apple1
Thread1:cabbage9
Thread2:apple2
Thread1:cabbage9
Thread2:apple3
Thread1:cabbage9
Thread2:apple4
Thread2:apple5
Thread1:cabbage9
Thread2:apple6
Thread1:cabbage9
Thread2:apple7
Thread2:apple8
Thread1:cabbage9
Thread1:cabbage9
Thread1:cabbage9
Thread2:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9

可以看到对于fruit和vegetable对象而言各自是线程安全的,保证了各自在线程1、2中都是从1递增到9的,另一方面,synchronized控制的分别fruit和vegetable对象的同步,而food对象是可以同时被线程1、2访问并且不互相阻塞。index超过9之后无法加入内置数组。

4.将针对指定对象改为对象的synchronized(this)

/**
	 * 同步线程
	 */
	class Food implements Runnable {
    public Food(Vegetables vegetables,Fruits fruits) {
        this.vegetables =vegetables;
        this.fruits=fruits;
    }
    private Vegetables vegetables;
    private Fruits fruits;

    @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                vegetables.addVegetables("cabbage" + i);
                System.out.println(Thread.currentThread().getName() + ":" + vegetables.getLastVegetables());

            }
        }

        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                fruits.addFruits("apple" + i);
                System.out.println(Thread.currentThread().getName() + ":" + fruits.getLastFruits());
            	}
        	}

    	}
	}

5.控制台输出

Thread1:cabbage0
Thread1:cabbage1
Thread1:cabbage2
Thread1:cabbage3
Thread1:cabbage4
Thread1:cabbage5
Thread1:cabbage6
Thread1:cabbage7
Thread1:cabbage8
Thread1:cabbage9
Thread2:cabbage9
Thread2:cabbage9
Thread2:cabbage9
Thread2:cabbage9
Thread2:cabbage9
Thread2:cabbage9
Thread2:cabbage9
Thread2:cabbage9
Thread2:cabbage9
Thread2:cabbage9
Thread2:apple0
Thread2:apple1
Thread2:apple2
Thread2:apple3
Thread2:apple4
Thread2:apple5
Thread2:apple6
Thread2:apple7
Thread2:apple8
Thread2:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9
Thread1:apple9

我们可以看到两相互阻塞了,线程1线程2其中之一将synchronized(this){}中括号内语句执行完毕后才可能执行另一线程的语句。


三、修饰一个代码块

Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,public synchronized void method(){//todo}; synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。

代码块:

1.synchronized修饰一个方法:

public synchronized void run() {
   for (int i = 0; i < 5; i ++) {
      try {
         System.out.println(Thread.currentThread().getName() + ":" + (count++));
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
}

2. synchronized关键字不能继承

虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。这两种方式的例子代码如下:

  1. 在子类方法中加上synchronized关键字
//1.子类重写父类方法,且也用synchronized修饰
class Parent {
   public synchronized void method() { }
}
class Child extends Parent {
   public synchronized void method() { }
}

2.在子类方法中调用父类的同步方法

class Parent {
   public synchronized void method() {   }
}
class Child extends Parent {
   public void method() { super.method();   }
} 

3.synchronized修饰方法的注意事项

  1. synchronized修饰接口的定义方法;
  2. 构造方法不能使用synchronized关键字,但可以synchronized来进行对象的初始化。(解释:由于锁即对象,构造函数用于创建对象,无对象何来锁,锁的安全性也不用顾及);
  3. synchronized方法不能继承其synchronized关键字。

四、修饰一个修饰一个静态的方法

Synchronized也可修饰一个静态方法,用法如下:

public synchronized static void method() {
   // todo
}

我们知道静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。我们对Demo1进行一些修改如下:

1.synchronized修饰静态方法

/**
 * 同步线程
 */
class SyncThread implements Runnable {
   private static int count;

   public SyncThread() {
      count = 0;
   }

   public synchronized static void method() {
      for (int i = 0; i < 5; i ++) {
         try {
            System.out.println(Thread.currentThread().getName() + ":" + (count++));
            Thread.sleep(100);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }

   public synchronized void run() {
      method();
   }
}

2.调用main方法:

SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();

3.控制台输出:

SyncThread2:0
SyncThread2:1
SyncThread2:2
SyncThread2:3
SyncThread2:4
SyncThread1:5
SyncThread1:6
SyncThread1:7
SyncThread1:8
SyncThread1:9

4.分析:

可以看到在对静态方法使用synchronized修饰之后,即使线程1、2调用俩个不同对象,但还是相互有阻塞,仍然保持了线程的同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。


五、修饰一个类

Synchronized还可作用于一个类,用法如下:

class ClassName {
   public void method() {
      synchronized(ClassName.class) {
         // todo
      }
   }
}

1.synchronized修饰类

class SyncThread3 implements Runnable {
    private static int count;

    public SyncThread3() {
        count = 0;
    }

    public static void method() {
        synchronized(SyncThread.class) {
            for (int i = 0; i < 5; i ++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public synchronized void run() {
        method();
    }
}

其效果和synchronized修饰一个静态方法所达到的效果是一样的,synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁。


本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 算法-反转一个单向链表

    版权声明: ...

    Fisherman渔夫
  • 算法-求最大子序列和

    转载来自于Rui用户解题思路 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

    Fisherman渔夫
  • 算法-最后一个单词的长度

    版权声明: ...

    Fisherman渔夫
  • 解决原子性问题?你首先需要的是宏观理解

    上一篇文章 可见性有序性,Happens-before来搞定,解决了并发三大问题中的两个,今天我们就聊聊如何解决原子性问题

    用户4172423
  • 解决原子性问题?你首先需要的是宏观理解

    上一篇文章 可见性有序性,Happens-before来搞定,解决了并发三大问题中的两个,今天我们就聊聊如何解决原子性问题

    码农小胖哥
  • 可读代码编写炸鸡三 - 审美

    在上一篇 可读代码炸鸡二(下篇) - 命名的歧义 的结尾处,提到了接下来的炸鸡会围绕 多行代码,多个函数 的代码范围来讨论代码可读性的优化。

    syy
  • 生存分析就是一个任人打扮的小姑凉

    我这里选择最方便的 网页工具:https://xenabrowser.net/heatmap/ 选择合适的数据集及样本信息还有基因来演示一下,随便选择一个基因...

    生信技能树
  • mybatis$和#的区别

    select * from everstar.Questions where ProductType = 'productType' select * fr...

    陈灬大灬海
  • Pre-training到底有没有用?何恺明等人新作:Rethinking ImageNet Pre-training

    使用基于ImageNet预训练(Pre-training)的网络已成为计算机视觉任务中一种常规的操作。何恺明等人在新作Rethinking ImageNet P...

    昱良
  • 预训练后性能反而变差,自训练要取代预训练了吗?

    早在2018年底,FAIR的研究人员就发布了一篇名为《Rethinking ImageNet Pre-training》的论文 ,这篇论文随后发表在ICCV20...

    AI科技评论

扫码关注云+社区

领取腾讯云代金券