在多线程中synchronized应该是我们运用的最多的,很多人会称呼它为重量级锁。java中的每一个对象都可以作为锁。具体表现为以下三种形式。
对于普通同步方法,锁是当前实例对象。
//图书
class Books {
private int id;// 图书ID
private String name; // 图书名称
private int number; // 图书数量
//存书
public void stockBooks(int count) {
number+=count;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//借书
public void taskBooks(int count) {
number-=count;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
我们先声明一个图书的对象,然后再模拟一下图书馆存书借书的场景;
// 图书馆
class bibliotheca implements Runnable {
private Books books;
public bibliotheca(Books books) {
super();
this.books = books;
}
public Books getBooks() {
return books;
}
public void setBooks(Books books) {
this.books = books;
}
@Override
public void run() {
books.stockBooks(1000); // 存入1000本书
books.taskBooks(1000);
System.out.println(Thread.currentThread().getName() + ":"
+ books.getNumber());
}
}
先尝试一下不加锁是什么结果;
public static void main(String[] args) {
Books books = new Books("金瓶梅", 1000); // 初始化的时候先存入1000本金瓶梅
bibliotheca bibliotheca = new bibliotheca(books); // 把图书注入到图书馆里
Thread threads[] = new Thread[10]; // 模拟10个线程
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(bibliotheca, "Thread" + i);
threads[i].start();
}
}
我运行了5次,发现每次执行完之后每个线程的最终结果都是一样的,比如都是10个1000,或者都是10个2000,包括3000,4000,5000,都有。那么这个结果肯定是不正确的,我们在这个对象上加个锁再试试;
@Override
public void run() {
synchronized (books) {
books.stockBooks(1000); // 存入1000本书
books.taskBooks(1000);
System.out.println(Thread.currentThread().getName() + ":"
+ books.getNumber());
}
}
最终结果都为1000,所以这个才是我们想要的结果;
当多个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。在run方法中我们用了synchronized(books)方法给这个对象加了锁,
当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:
@Override
public void run() {
synchronized (this) {
books.stockBooks(1000); // 存入1000本书
books.taskBooks(1000);
System.out.println(Thread.currentThread().getName() + ":"
+ books.getNumber());
}
}
比如上面这样,是不是很常见;或者如下面这样
private byte[] lock = new byte[0]; // 特殊的instance变量
public void method()
{
synchronized(lock) {
// todo 同步代码块
}
}
public void run() {
}
零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
对于静态同步方法,锁是当前类的Class对象。
我们知道静态方法不属于某个对象,而是属于一个具体的类,所以得像下面这样去锁;
class CountThread extends Thread{
private static int count;
public CountThread() {
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 void run() {
method();
}
}
请注意我没有对run方法加同步锁,是因为,我要同步的只是method()这个具体的方法;
public static void main(String[] args) {
CountThread countThread=new CountThread();
Thread threads[] = new Thread[10]; // 模拟10个线程
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(countThread, "Thread" + i);
threads[i].start();
}
}
现在我们改造一下这个例子:
class CountThread extends Thread{
private static int count;
public CountThread() {
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 static void method1() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void run() {
method();
method1();
}
}
run方法里面有method()和method1();这个方法结果是加上同步锁的会保持同步,而没有加上同步锁的则会出现乱数据;
如果我们method()和method1()都没有加上同步,而想让这个两个方法同步怎么办?这就是我们要说的第三种形式;
对于同步方法块,锁是Synchonized括号里配置的对象。
public synchronized void run() {
method();
method1();
}
当然你也可以这样:
public void run() {
synchronized (this) {
method();
method1();
}
}
是不是很神奇!
写法一修饰的是一个方法,写法二修饰的是一个代码块,但写法一与写法二是等价的,都是锁定了整个方法时的内容。
当一个线程视图访问同步代码块,它首先必须得到锁,退出或抛出异常时必须释放锁,那么锁到底存在哪里?锁里面会存储什么信息啦?请听下次详解啊!