引出:
Java中synchronized修饰符在多线程同步中有所大展拳脚,所以十分有必要对其进行整理、对照和学习
synchronized修饰符的使用场景整理总结、分类
修饰对象 | 作用范围 | 作用对象 |
---|---|---|
代码块(称为同步代码块) | 大括号{}括起来的代码 | 调用这个代码块的对象 |
一般方法(被称为同步方法) | 整个方法 | 调用这个方法的对象 |
静态的方法 | 整个静态方法 | 此类的所有对象 |
类 | synchronized后面括号括起来的部分 | 此类的所有对象 |
一、修饰一个代码块
修饰的结果:
- 一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞;
- 多个线程访问各自的对象即使有synchronized修饰了同步代码块,但是互不阻塞,但是并不能保证静态变量的线程安全性。
- 当一个线程访问对象的一个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)):
- 一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞;
- 多个线程访问各子的对象即使有synchronized修饰了同步代码块,但是互不阻塞,但是并不能保证静态变量的线程安全性;
- 当一个线程访问对象的一个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关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。这两种方式的例子代码如下:
- 在子类方法中加上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修饰方法的注意事项
- synchronized修饰接口的定义方法;
- 构造方法不能使用synchronized关键字,但可以synchronized来进行对象的初始化。(解释:由于锁即对象,构造函数用于创建对象,无对象何来锁,锁的安全性也不用顾及);
- 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/