1. <span style="font-family:KaiTi_GB2312;font-size:18px;">一、同步问题提出</span>
1. <span style="font-family:KaiTi_GB2312;font-size:18px;"> 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。</span>
1. <span style="font-family:KaiTi_GB2312;font-size:18px;"> 1、锁的原Jva中每个对象都有一个内置锁当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。</span>
1. <span style="font-family:KaiTi_GB2312;font-size:18px;">获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。一个对象只</span>
1. <span style="font-family:KaiTi_GB2312;font-size:18px;">有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的</span>
1. <span style="font-family:KaiTi_GB2312;font-size:18px;">synchronized方法或代码块,直到该锁被释放。释放锁是指持锁线程退出了synchronized同步方法或代码块。 </span>
1. <span style="font-family:KaiTi_GB2312;font-size:18px;">Phone
2. {</span>
1. <span style="font-family:KaiTi_GB2312;font-size:18px;"> public synchronized void getIOS() throws
2. {
3. 4000);
4. "-----getIOS");
5. }
6. public <span style="font-family: FangSong_GB2312;">synchronized </span>void getAndroid() throws
7. {
8. "-----getAndroid");
9. }
10. public void hello() throws
11. {
12. "-----hello");
13. }
14. }
15.
16. public class
17. {
18. public static void
19. {
20. final Phone phone = new
21. final Phone phone2 = new
22.
23. new Thread(new
24. @Override
25. public void
26. {
27. for (int i = 0; i < 1; i++)
28. {
29. try{
30. phone.getIOS();
31. catch
32. e.printStackTrace();
33. }
34. }
35. }
36. "AA").start();
37.
38. new Thread(new
39. @Override
40. public void
41. {
42. for (int i = 0; i < 1; i++)
43. {
44. try{
45. //phone.getAndroid();
46. //phone.hello();
47. phone2.getAndroid();
48. catch
49. e.printStackTrace();
50. }
51. }
52. }
53. "BB").start();
54. }
55. }</span>
关于锁和同步,有一下几个要点:
1)只能同步方法,而不能同步变量和类;
2)每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?
4)如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
3)类中可以同时拥有同步和非同步方法。
5)如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
6)线程睡眠时,它所持的任何锁都不会释放。
7)线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
8)同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
9)在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如:
1. <span style="font-family:KaiTi_GB2312;font-size:18px;">public <span style="font-family: FangSong_GB2312;">void
2. "code" class="java" style="font-size: 18px;"> synchronized(this){
3. 1000);
4. "-----getIOS");
5. }</span>
}
三、静态方法同步 静态方法同步,需要一个用于整个类对象的锁,这个对象是就是这个类。 相当于synchronized(XXX.class)例如:
<span style="font-family:KaiTi_GB2312;font-size:18px;">public static synchronized void getIOS() throws
1. {
2. 1000);
3. "-----getIOS");
4. }</span>
等价于
1. <span style="font-family:KaiTi_GB2312;font-size:18px;">public static void getIOS() throws
2. {
3. synchronized(Phone.class){
4. 1000);
5. "-----getIOS");
6. }
7. }
8. </span>
四、如果线程不能不能获得锁会怎么样
如果线程试图进入同步方法,而其锁已经被占用,则线程在该对象上被阻塞。实质上,线程进入该对象的的一种池中,必须在哪里等待,直到其锁被释放,该线程再次变为可运行或运行为止。
当考虑阻塞时,一定要注意哪个对象正被用于锁定:
1、调用同一个对象中非静态同步方法的线程将彼此阻塞。不同对象,则每个线程有自己的对象的锁,线程间彼此互不干预。
2、调用同一个类中的静态同步方法的线程将彼此阻塞,它们都是锁定在相同的Class对象上。
3、静态同步方法和非静态同步方法将永远不会彼此阻塞,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。
4、对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将彼此阻塞,在不同对象上锁定的线程将永远不会彼此阻塞。
五、何时需要同步
在多个线程同时访问互斥(可交换)数据时,应该同步以保护数据,确保两个线程不会同时修改更改它。
对于非静态字段中可更改的数据,通常使用非静态方法访问。
对于静态字段中可更改的数据,通常使用静态方法访问。
如果需要在非静态方法中使用静态字段,或者在静态字段中调用非静态方法,问题将变得非常复杂。已经超出SJCP考试范围了。
六、线程安全类
当一个类已经很好的同步以保护它的数据时,这个类就称为“线程安全的”。即使是线程安全类,也应该特别小心,因为操作的线程间仍然不一定安全。
举个形象的例子,比如一个集合是线程安全的,有两个线程在操作同一个集合对象,当第一个线程查询集合非空后,删除集合中所有元素的时候。第二个线程也来执行与第一个线程相同的操作,也许在第一个线程查询后,第二个线程也查询出集合非空,但是当第一个执行清除后,第二个再执行删除显然是不对的,因为此时集合已经为空了。
看个代码:
1. <span style="font-family:KaiTi_GB2312;font-size:18px;">public class
2. private List nameList = Collections.synchronizedList(new
3.
4. public void
5. nameList.add(name);
6. }
7.
8. public
9. if (nameList.size() > 0) {
10. return (String) nameList.remove(0);
11. else
12. return null;
13. }
14. }
15. }
16.
17. public class
18. public static void
19. final NameList nl = new
20. "aaa");
21. class NameDropper extends
22. public void
23. String name = nl.removeFirst();
24. System.out.println(name);
25. }
26. }
27.
28. new
29. new
30. t1.start();
31. t2.start();
32. }
33. }</span>
虽然集合对象
private List nameList = Collections.synchronizedList(new LinkedList());
是同步的,但是程序还不是线程安全的。出现这种事件的原因是,上例中一个线程操作列表过程中无法阻止另外一个线程对列表的其他操作。
解决上面问题的办法是,在操作集合对象的NameList上面做一个同步。改写后的代码如下:
1. <span style="font-family:KaiTi_GB2312;font-size:18px;">public class
2. private List nameList = Collections.synchronizedList(new
3.
4. public synchronized void
5. nameList.add(name);
6. }
7.
8. public synchronized
9. if (nameList.size() > 0) {
10. return (String) nameList.remove(0);
11. else
12. return null;
13. }
14. }
15. }</span>
这样,当一个线程访问其中一个同步方法时,其他线程只有等待。
七、线程死锁
死锁对Java程序来说,是很复杂的,也很难发现问题。当两个线程被阻塞,每个线程在等待另一个线程时就发生死锁。
还是看一个比较直观的死锁例子:
1. <span style="font-family:KaiTi_GB2312;font-size:18px;">public class
2. private static class
3. public int
4. }
5.
6. private Resource resourceA = new
7. private Resource resourceB = new
8.
9. public int
10. synchronized
11. synchronized
12. return
13. }
14. }
15. }
16.
17. public void write(int a, int
18. synchronized
19. synchronized
20. resourceA.value = a;
21. resourceB.value = b;
22. }
23. }
24. }
25. }</span>
假设read()方法由一个线程启动,write()方法由另外一个线程启动。读线程将拥有resourceA锁,写线程将拥有resourceB锁,两者都坚持等待的话就出现死锁。
实际上,上面这个例子发生死锁的概率很小。因为在代码内的某个点,CPU必须从读线程切换到写线程,所以,死锁基本上不能发生。
但是,无论代码中发生死锁的概率有多小,一旦发生死锁,程序就死掉。有一些设计方法能帮助避免死锁,包括始终按照预定义的顺序获取锁这一策略。已经超出SCJP的考试范围。
八、线程同步小结
1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。