Java中Synchronized的用法

引出:
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的所有对象用的是同一把锁。


六、参考资料(代码参考)

https://blog.csdn.net/luoweifu/article/details/46613015
http://ifeve.com/concurrency-optimization-reduce-lock/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值