1、synchronized定义
Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这个段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍然可以访问该object中的非加锁代码块。
注意:synchronized无法修饰单个对象,synchronized修饰的应该是对象+方法或者对象+代码块的组合。
对象+方法:直接修饰方法即可;这种方式中没有体现出对象,但其实该类的实例就是被锁的对象了。
public synchronized void A(){ s("A"); }
对象+代码块:这种方式就比较明显了,this就是指该类的实例,{}中的内容则是代码块。
public void A(){ synchronized (this) { s("A"); } }
上面两个例子是等价的。
2、打个比方来理解
synchronized其实就是一个游戏
游戏场景:一个大房子(对象),里面有多个房间(方法),有些是锁着的,有些是没锁的
游戏人物:管理员(JVM),玩家(线程)
游戏规则:
(1)没有上锁的房间可以随时使用,也可以多个玩家同时使用
(2)上了锁的房间只限一个玩家使用,而且只有一把钥匙可以打开
(3)钥匙可以跟管理员拿,若同时有多个玩家想拿同个钥匙,管理员会随机给其中一个玩家
(4)玩家用完房间后必须把钥匙归还管理员(即使想再次使用,也得先还管理员,再跟管理员要;如果其他玩家也要这个钥匙,则会随机给其中一个)
3、Android用到的地方
首先从定义就可以知道,synchronized只有在多线程的场景下才有存在的意义。
那么Android中用到多线程的地方,举个常见的例子,就是GridView异步加载本地图片。
由于加载图片挺耗时的,所以在同个线程中把多张图片一次加载完是很不明智的,况且本地图片可是很多的。
所以这时我们会考虑到使用线程池来加载图片。
那么线程多了自然也会面临一个问题:可能出现多个线程同时在加载同一张图片,会产生什么问题?
事实上,这也是必然会出现的问题;比如你快速向上滑动图片列表(在图片非常多的情况下),然后又迅速向下滑动,这时如果之前正在加载的图片还没加载完,就会有新的线程来再加载那张图片;两个线程加载同个图片,这就是所谓的内存泄露;内存泄露还不至于报错,但是内存泄露的地方多了,难免会报OOM,所以需要避免内存泄露。
这时,如果使用synchronized来锁住加载图片的代码块,就可以避免了。
注意:锁住加载图片的代码块后GridView是一张一张加载图片的。
提醒:锁住不同对象的同个代码块没有任何意义,类似下面这样的代码都是无意义的!
public class Test implements Runnable{ @Override public void run() { synchronized (this) { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName()); } } } public static void main(String...arg){ new Thread(new Test(),"A").start(); new Thread(new Test(),"B").start(); } }
应该写成这样:
Test test = new Test(); new Thread(test,"A").start(); new Thread(test,"B").start();
4、题外话
上面的例子中用synchronized锁住加载图片的代码后GridView是一张张加载图片的,但是这样做感觉多线程就没什么意义了;确实,毕竟synchronized的定义已经说的很清楚了,就是为了防止多个线程干同件事的;如果你实在受不了一张张的加载图片的话,那就只能放弃synchronized了。
其实,上面我说的GridView加载图片的例子,并没这么简单,我只是为了更好理解才这样描述;实际上在项目中是多线程+缓存+synchronized,多线程+缓存就解决了内存泄露的问题了,而synchronized是为了让图片一张张加载的效果。