前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java中Synchronized的用法

Java中Synchronized的用法

作者头像
Fisherman渔夫
发布2019-07-31 15:45:12
3.2K1
发布2019-07-31 15:45:12
举报
文章被收录于专栏:渔夫渔夫
知识共享许可协议
知识共享许可协议

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

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


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

修饰对象

作用范围

作用对象

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

大括号{}括起来的代码

调用这个代码块的对象

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

整个方法

调用这个方法的对象

静态的方法

整个静态方法

此类的所有对象

synchronized后面括号括起来的部分

此类的所有对象


一、修饰一个代码块

修饰的结果:
  1. 一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞;
  2. 多个线程访问各子的对象即使有synchronized修饰了同步代码块,但是互不阻塞,但是并不能保证静态变量的线程安全性。
  3. 当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。
1.synchronized修饰的方法使用:
代码语言:javascript
复制
	/**
	 * 同步线程
	 */
	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.验证调用代码(创建俩线程,调用一个对象)
代码语言:javascript
复制
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.控制台输出
代码语言:javascript
复制
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.修改代码块为俩对象(创建俩线程,分别对应俩对象):
代码语言:javascript
复制
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是静态变量)
代码语言:javascript
复制
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代码块:
代码语言:javascript
复制
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.控制台输出(由于多线程的特性,每次运行的结果可能不同):
代码语言:javascript
复制
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修饰的方法使用:
代码语言:javascript
复制
	/**
	 * 同步线程
	 */
	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.验证调用代码(创建俩线程,调用一个对象)
代码语言:javascript
复制
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.控制台输出
代码语言:javascript
复制
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)
代码语言:javascript
复制
/**
	 * 同步线程
	 */
	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.控制台输出
代码语言:javascript
复制
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修饰一个方法:
代码语言:javascript
复制
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关键字
代码语言:javascript
复制
//1.子类重写父类方法,且也用synchronized修饰
class Parent {
   public synchronized void method() { }
}
class Child extends Parent {
   public synchronized void method() { }
}

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

代码语言:javascript
复制
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也可修饰一个静态方法,用法如下:

代码语言:javascript
复制
public synchronized static void method() {
   // todo
}

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

1.synchronized修饰静态方法
代码语言:javascript
复制
/**
 * 同步线程
 */
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方法:
代码语言:javascript
复制
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.控制台输出:
代码语言:javascript
复制
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还可作用于一个类,用法如下:

代码语言:javascript
复制
class ClassName {
   public void method() {
      synchronized(ClassName.class) {
         // todo
      }
   }
}
1.synchronized修饰类
代码语言:javascript
复制
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的所有对象用的是同一把锁。


本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年07月23日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • synchronized修饰符的使用场景整理总结、分类
  • 一、修饰一个代码块
    • 修饰的结果:
      • 1.synchronized修饰的方法使用:
        • 2.验证调用代码(创建俩线程,调用一个对象)
          • 3.控制台输出
            • 4.修改代码块为俩对象(创建俩线程,分别对应俩对象):
              • 5.控制台输出(没有保证到线程安全性, 主要由于count是静态变量)
                • 6.其他线程可访问同一对象的非synchronized方法的证明思路:
                  • 7.多个线程访问synchronized和非synchronized代码块:
                    • 8.控制台输出(由于多线程的特性,每次运行的结果可能不同):
                    • 二、修饰一个代码块(非this,而是指定对象)
                      • 修饰的结果(同synchronized(this)):
                        • 代码块:
                          • 1.synchronized修饰的方法使用:
                            • 2.验证调用代码(创建俩线程,调用一个对象)
                              • 3.控制台输出
                                • 4.将针对指定对象改为对象的synchronized(this)
                                  • 5.控制台输出
                                  • 三、修饰一个代码块
                                    • 代码块:
                                      • 1.synchronized修饰一个方法:
                                        • 2. synchronized关键字不能继承
                                          • 3.synchronized修饰方法的注意事项
                                          • 四、修饰一个修饰一个静态的方法
                                            • 1.synchronized修饰静态方法
                                              • 2.调用main方法:
                                                • 3.控制台输出:
                                                  • 4.分析:
                                                  • 五、修饰一个类
                                                    • 1.synchronized修饰类
                                                    领券
                                                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档