Java对于多线程的安全问题,提供了专业的解决方式,就是同步代码块。
synchronized(对象)
{
需要被同步的代码//那些语句在操作共享数据,就进行同步
}
对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程,即使获取了cpu的执行权也进不去。
[java] view plaincopy
1. /*
2. 给卖票程序示例加上同步代码块。
3. */
4. class Ticket implements Runnable
5. {
6. private int tick=100;
7. Object obj = new Object();
8. public void run()
9. {
10. while(true)
11. {
12. //给程序加同步,即锁
13. synchronized(obj)
14. {
15. if(tick>0)
16. {
17. try
18. {
19. //使用线程中的sleep方法,模拟线程出现的安全问题
20. //因为sleep方法有异常声明,所以这里要对其进行处理
21. Thread.sleep(10);
22. }
23. catch (Exception e)
24. {
25. }
26. //显示线程名及余票数
27. System.out.println(Thread.currentThread().getName()+"..sale="+tick--);
28. }
29. }
30. }
31. }
32. }
同步的前提:
1. 必须要有两个或者两个以上的线程
2. 必须是多个线程使用同一个锁,比如输入输出都要进行同步,使用同一把锁(比如类的字符码文件对象,保证唯一性)
必须保证同步中只能有一个线程在运行
好处:解决了多线程的安全问题
弊端:多个线程都需要判断锁,较为消耗资源
如何找多线程安全问题并进行同步:
1. 明确哪些代码是多线程运行代码
2. 明确共享数据
3. 明确多线程运行代码中哪些语句是操作共享数据的(共享数据不能被独立语句操作的就会出现安全问题)
同步函数
函数和同步代码块都是封装,两者结合就是同步函数
把synchronized关键字作为修饰符放在函数上就实现了同步函数,无需对象锁
将需要同步的代码单独封装为一个函数,并加上synchronized关键字就实现了同步函数
函数需要被对象调用,那么函数都有一个所属对象的引用就是this,所以同步函数使用的锁是this
[java] view plaincopy
1. class Ticket implements Runnable
2. {
3. private int tick=100;
4. Object obj = new Object();
5. public void run()
6. {
7. while(true)
8. {
9. show();
10. }
11. }
12. //直接在函数上用synchronized修饰即可实现同步
13. public synchronized void show()
14. {
15. if(tick>0)
16. {
17. try
18. {
19. //使用线程中的sleep方法,模拟线程出现的安全问题
20. //因为sleep方法有异常声明,所以这里要对其进行处理
21. Thread.sleep(10);
22. }
23. catch (Exception e)
24. {
25. }
26. //显示线程名及余票数
27. System.out.println(Thread.currentThread().getName()+"..tick="+tick--);
28. }
29. }
30. }
静态同步函数的锁
同步函数被静态static修饰后,使用的锁不是this,因为静态方法中不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象:类名.class,该对象的类型是Class
静态的同步方法,使用的锁是该方法所在类的字节码文件对象,也就是类名.class
参阅单例设计模式-懒汉式
[java] view plaincopy
1. class Single
2. {
3. private static Single s = null;
4. private Single(){}
5. public static Single getInstance()
6. {
7. if(s==null)
8. {
9. synchronized(Single.class)
10. {
11. if(s==null)
12. s = new Single();
13. }
14. }
15. return s;
16. }
17. }//同步代码块加入判断的原因:如果第一个线程进入到第一个if代码块,此时并还没获取锁,此时虚拟机切换给第二个线程,它也进来了if代码块,并且此时获取锁,new一个对象,然后返回。接着虚拟机切换给第一个线程,它这时候才获取锁,此时如果没有第二个if判断,就会再new 一个对象,这就不能保证类的对象唯一性。
死锁
同步中嵌套同步,两个同步锁不相同,两个线程都持有锁但是不释放,程序就会发生死锁
[java] view plaincopy
1. /*
2. 一个死锁程序
3. */
4. //定义一个类来实现Runnable,并复写run方法
5. class LockTest implements Runnable
6. {
7. private boolean flag;
8. LockTest(boolean flag)
9. {
10. this.flag=flag;
11. }
12. public void run()
13. {
14. if(flag)
15. {
16. while(true)
17. {
18. synchronized(LockClass.locka)//a锁
19. {
20. System.out.println(Thread.currentThread().getName()+"------if_locka");
21.
22. synchronized(LockClass.lockb)//b锁
23. {
24. System.out.println(Thread.currentThread().getName()+"------if_lockb");
25. }
26. }
27. }
28. }
29. else
30. {
31. while(true)
32. {
33. synchronized(LockClass.lockb)//b锁
34. {
35. System.out.println(Thread.currentThread().getName()+"------else_lockb");
36.
37. synchronized(LockClass.locka)//a锁
38. {
39. System.out.println(Thread.currentThread().getName()+"------else_locka");
40. }
41. }
42. }
43. }
44. }
45. }
46.
47. //定义两个锁
48. class LockClass
49. {
50. static Object locka = new Object();
51. static Object lockb = new Object();
52. }
53.
54. class DeadLock
55. {
56. public static void main(String[] args)
57. {
58. //创建2个线程,并启动
59. new Thread(new LockTest(true)).start();
60. new Thread(new LockTest(false)).start();
61. }
62. }
运行结果是两个线程都会停在这里等待对方释放锁
线程间的通信
线程间的通讯其实就是多个线程在操作同一个资源,但是操作的动作不同。
为了保证该资源的唯一性,可以考虑单力设计模式(麻烦),或者在每个线程类中通过构造函数传入资源的实例引用,例:
[java] view plaincopy
1. class Res {
2. String name;
3. String sex;
4. }
5.
6. class Input implements Runnable{
7. private Res r;
8. Input(Res r){
9. this.r = r;
10. }
11. public void run(){
12. int flag = 0;
13. while(true){
14. if(flag==0){
15. r.name = "张三";
16. r.sex = "男";
17. }else{
18. r.name = "李四";
19. r.sex = "女";
20. }
21. flag = (flag+1)%2;//交替打印
22. }
23. }
24. }
25.
26. class Output implements Runnable{
27. private Res r;
28. Output(Res r){
29. this.r = r;
30. }
31. public void run(){
32. while(true){
33. System.out.println(r.name+"..."+r.sex);
34. }
35. }
36. }
37.
38. public class InputOutputDemo {
39. public static void main(String[] args) {
40. Res r = new Res();
41.
42. Input in = new Input(r);
43. Output out = new Output(r);
44.
45. Thread t1 = new Thread(in);
46. Thread t2 = new Thread(out);
47.
48. t1.start();
49. t2.start();
50. }
|
|