前面一节的案例提出了多线程数据同步所存在的问题,当多个Author线程修改同一个共享数据时,会发生数据覆盖或丢失的问题。
为了解决多个线程修改同一数据而发生数据覆盖或丢失的问题,Java提供了synchronized关键字来加保护伞,以保证数据的安全。synchronized关键字对共享数据的保护有两种方式,一种是用于修饰代码块,一种是用于修饰方法。
修饰代码块是把线程体内执行的方法中会涉及到修改共享数据时的操作,通过{}封装起来,然后用synchronized关键字修饰这个代码块。代码如下:
package com.milihua.bookwriter;
public class Author implements Runnable {
Book docBook;
// 作者姓名
String name;
public Author(Book inBook, String inName) {
docBook = inBook;
name = inName;
}
public void setDocBook(Book inBook) {
docBook = inBook;
}
@Override
public void run() {
// TODO Auto-generated method stub
// 模拟创作,让线程等待1000毫秒
// 添加synchronized关键字
try {
Thread.sleep(1000);
// 编辑内容并添加到docBook对象
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (this) {
String str = docBook.getContent() + "\n" + name + ":我的创作内容";
docBook.setContent(str);
}
}
}上面的代码把获取和修改docBook对象内容的语句封装在{}内,并用synchronized关键字修饰,synchronized关键字要求传入对象的引用。需要注意的是,一定要传入需要保护的数据所在的对象。在本案例中,保护的数据为Book对象,Author线程类的docBook属性值是由主线程类实例化Author线程类时传入的对象引用,因此synchronized关键字后面的对象参数应传入主线程对象引用。
package com.milihua.bookwriter;
public class BookWriter {
public static void main(String[] args) {
BookWriter book = new BookWriter();
Book docBook = new Book();
//创建5个作者
Thread t1 = new Thread(new Author(docBook,"李某",book));
t1.start();
Thread t2 = new Thread(new Author(docBook,"王新",book));
t2.start();
Thread t3 = new Thread(new Author(docBook,"张某",book));
t3.start();
Thread t4 = new Thread(new Author(docBook,"赵三",book));
t4.start();
Thread t5 = new Thread(new Author(docBook,"李四",book));
t5.start();
try {
Thread.sleep(10000);
System.out.println("共同创作的图书内容:\n" + docBook.getContent());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}在主线程类中,实例化了BookWriter对象,并在实例化Author线程类时,将实例化的BookWriter对象传入到Author线程类。Author线程类的synchronized关键字将使用传入的BookWriter对象。
程序经过改造后,输出结果如下图所示:

图 15-12 使用synchronized关键字解决数据同步问题输出结果
从输出结果可以看出,程序输出了正确的结果。其原理是当A和B两个并发Author线程执行synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。也就是说A和B是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。
synchronized关键字对共享数据的保护,实际是利用了锁技术。锁技术又分为对象锁和内置锁。
Java内置锁是一个互斥锁,最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
对象锁和内置锁在概念上是一致的。区别是内置锁用于对象内部的代码块和方法,而对象锁用于静态方法或者一个类的实例化对象。
■ 知识点拨
在java中,每一个实例对象有且仅有一个同步锁。当用synchronized关键字修饰代码块时,要求传入一个对象引用,synchronized关键字为这个传入的对象加同步锁。如果修饰一个方法,synchronized关键字为当前对象加同步锁。