版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons)
引出: Java中synchronized修饰符在多线程同步中有所大展拳脚,所以十分有必要对其进行整理、对照和学习
修饰对象 | 作用范围 | 作用对象 |
---|---|---|
代码块(称为同步代码块) | 大括号{}括起来的代码 | 调用这个代码块的对象 |
一般方法(被称为同步方法) | 整个方法 | 调用这个方法的对象 |
静态的方法 | 整个静态方法 | 此类的所有对象 |
类 | 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;
}
}
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();
}
}
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
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();
}
}
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
我们对run方法进行一个线程名的选择,如果线程1、2能够不相互阻塞地进行运行,那么证明成功。
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();
}
}
}
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)同步代码块,互不阻塞。
/**
* 同步线程
*/
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];
}
}
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();
}
}
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之后无法加入内置数组。
/**
* 同步线程
*/
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());
}
}
}
}
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修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。
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();
}
}
}
虽然可以使用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(); }
}
Synchronized也可修饰一个静态方法,用法如下:
public synchronized static void method() {
// todo
}
我们知道静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。我们对Demo1进行一些修改如下:
/**
* 同步线程
*/
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();
}
}
SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();
SyncThread2:0
SyncThread2:1
SyncThread2:2
SyncThread2:3
SyncThread2:4
SyncThread1:5
SyncThread1:6
SyncThread1:7
SyncThread1:8
SyncThread1:9
可以看到在对静态方法使用synchronized修饰之后,即使线程1、2调用俩个不同对象,但还是相互有阻塞,仍然保持了线程的同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。
Synchronized还可作用于一个类,用法如下:
class ClassName {
public void method() {
synchronized(ClassName.class) {
// todo
}
}
}
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的所有对象用的是同一把锁。