java多线程(一)

-----------android培训java培训、java学习型技术博客、期待与您交流!------------ 

 

一、多线程概述

        要理解多线程,就必须理解线程。而要理解线程,就必须知道进程。

1 进程

        是一个正在执行的程序。

        每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。

2、线程

         就是进程中的一个独立的控制单元。线程在控制着进程的执行。只要进程中有一个线程在执行,进程就不会结束。

        一个进程中至少有一个线程。

3、多线程

        在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。像种在一个进程中有多个线程执行的方式,就叫做多线程。

4、多线程存在的意义

        多线程的出现能让程序产生同时运行效果。可以提高程序执行效率。

         例如:在java.exe进程执行主线程时,如果程序代码特别多,在堆内存中产生了很多对象,而同时对象调用完后,就成了垃圾。如果垃圾过多就有可能是堆内存出现内存不足的现象,只是如果只有一个线程工作的话,程序的执行将会很低效。而如果有另一个线程帮助处理的话,如垃圾回收机制线程来帮助回收垃圾的话,程序的运行将变得更有效率。

5、计算机CPU的运行原理

         我们电脑上有很多的程序在同时进行,就好像cpu在同时处理这所以程序一样。但是,在一个时刻,单核的cpu只能运行一个程序。而我们看到的同时运行效果,只是cpu在多个进程间做着快速切换动作。

         而cpu执行哪个程序,是毫无规律性的。这也是多线程的一个特性:随机性。哪个线程被cpu执行,或者说抢到了cpu的执行权,哪个线程就执行。而cpu不会只执行一个,当执行一个一会后,又会去执行另一个,或者说另一个抢走了cpu的执行权。至于究竟是怎么样执行的,只能由cpu决定。

 

二、创建线程的方式

        创建线程共有两种方式:继承方式和实现方式(简单的说)。

1 继承方式

        通过查找java的帮助文档API,我们发现java中已经提供了对线程这类事物的描述的类——Thread类。这第一种方式就是通过继承Thread类,然后复写其run方法的方式来创建线程。

创建步骤:

        a,定义类继承Thread。

        b,复写Thread中的run方法。

             目的:将自定义代码存储在run方法中,让线程运行。

        c,创建定义类的实例对象。相当于创建一个线程。

        d,用该对象调用线程的start方法。该方法的作用是:启动线程,调用run方法。

注:如果对象直接调用run方法,等同于只有一个线程在执行,自定义的线程并没有启动。

覆盖run方法的原因:

        Thread类用于描述线程。该类就定义了一个功能,用于存储线程要执行的代码。该存储功能就run方法。也就是说,Thread类中的run方法,用于存储线程要运行的代码。

程序示例:

[java] view plaincopy

1. /* 

2. 小练习 

3. 创建两线程,和主线程交替运行。 

4. */  

5.   

6. //创建线程Test  

7. class Test extends Thread  

8. {  

9. //  private String name;  

10.     Test(String name)  

11.     {  

12.         super(name);  

13. //      this.name=name;  

14.     }  

15.     //复写run方法  

16.     public void run()  

17.     {  

18.         for(int x=0;x<60;x++)  

19.         System.out.println(Thread.currentThread().getName()+"..run..."+x);  

20. //      System.out.println(this.getName()+"..run..."+x);  

21.     }  

22. }  

23.   

24. class  ThreadTest  

25. {  

26.     public static void main(String[] args)   

27.     {  

28.         new Test("one+++").start();//开启一个线程  

29.   

30.         new Test("tow———").start();//开启第二线程  

31.   

32.         //主线程执行的代码  

33.         for(int x=0;x<170;x++)  

34.         System.out.println("Hello World!");  

35.     }  

36. }  

结果:

      如图,执行是随机、交替执行的,每一次运行的结果都会不同。

       

2 实现方式

        使用继承方式有一个弊端,那就是如果该类本来就继承了其他父类,那么就无法通过Thread类来创建线程了。这样就有了第二种创建线程的方式:实现Runnable接口,并复习其中run方法的方式。

创建步骤:

        a,定义类实现Runnable的接口。

        b,覆盖Runnable接口中的run方法。目的也是为了将线程要运行的代码存放在该run方法中。

        c,通过Thread类创建线程对象。

        d,将Runnable接口的子类对象作为实参传递给Thread类的构造方法。

       为什么要将Runnable接口的子类对象传递给Thread的构造函数?

        因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定对象的run方法,就必须明确该run方法所属对象。

        e,调用Thread类中start方法启动线程。start方法会自动调用Runnable接口子类的run方法。

实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。 

程序示例:

[java] view plaincopy

1. /* 

2. 需求:简单的卖票程序。 

3. 多个窗口卖票。 

4. */  

5. class Ticket implements Runnable//extends Thread  

6. {  

7.     private  int tick = 100;  

8.     public void run()  

9.     {  

10.         while(true)  

11.         {  

12.             if(tick>0)  

13.             {  

14.                 //显示线程名及余票数  

15.                 System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);  

16.             }  

17.         }  

18.     }  

19. }  

20.   

21.   

22. class  TicketDemo  

23. {  

24.     public static void main(String[] args)   

25.     {  

26.         //创建Runnable接口子类的实例对象  

27.         Ticket t = new Ticket();  

28.   

29.         //有多个窗口在同时卖票,这里用四个线程表示  

30.         Thread t1 = new Thread(t);//创建了一个线程  

31.         Thread t2 = new Thread(t);  

32.         Thread t3 = new Thread(t);  

33.         Thread t4 = new Thread(t);  

34.   

35.         t1.start();//启动线程  

36.         t2.start();  

37.         t3.start();  

38.         t4.start();  

39.     }  

40. }  

 

三、两种方式的区别和线程的几种状态

1、两种创建方式的区别

        继承Thread:线程代码存放在Thread子类run方法中。

        实现Runnable:线程代码存放在接口子类run方法中。      

2、几种状态

        被创建:等待启动,调用start启动。

         运行状态:具有执行资格和执行权。

         临时状态(阻塞):有执行资格,但是没有执行权。

         冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。

         消忙状态:stop()方法,或者run方法结束。

注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常。

图解:


   

四、线程安全问题

1、导致安全问题的出现的原因:

        当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。

简单的说就两点:

        a、多个线程访问出现延迟。

        b、线程随机性    。

注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

2、解决办法——同步

        对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

        java中对于多线程的安全问题提供了专业的解决方式——synchronized(同步)

        这里也有两种解决方式,一种是同步代码块,还有就是同步函数。都是利用关键字synchronized来实现。

         a、同步代码块

        用法:

                  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()+"..tick="+tick--);  

28.                 }  

29.             }     

30.         }  

31.     }  

32. }  

        b,同步函数

        格式:

                在函数上加上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. }  

3、同步的前提

        a,必须要有两个或者两个以上的线程。

        b,必须是多个线程使用同一个锁。

4、同步的利弊

        好处:解决了多线程的安全问题。

        弊端:多个线程需要判断锁,较为消耗资源。

5、如何寻找多线程中的安全问题

        a,明确哪些代码是多线程运行代码。

        b,明确共享数据。

        c,明确多线程运行代码中哪些语句是操作共享数据的。

 

五、静态函数的同步方式

        如果同步函数被静态修饰后,使用的锁是什么呢?

        通过验证,发现不在是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如:

        类名.class 该对象的类型是Class

这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class

经典示例:

[java] view plaincopy

1. /* 

2. 加同步的单例设计模式————懒汉式 

3. */  

4. class Single  

5. {  

6.     private static Single s = null;  

7.     private Single(){}  

8.     public static void getInstance()  

9.     {  

10.         if(s==null)  

11.         {  

12.             synchronized(Single.class)  

13.             {  

14.                 if(s==null)  

15.                     s = new Single();  

16.             }  

17.         }  

18.         return s;  

19.     }  

20. }  

 

六、死锁

        当同步中嵌套同步时,就有可能出现死锁现象。

示例:

[java] view plaincopy

1. /* 

2. 写一个死锁程序 

3. */  

4.   

5. //定义一个类来实现Runnable,并复写run方法  

6. class LockTest implements Runnable  

7. {  

8.     private boolean flag;  

9.     LockTest(boolean flag)  

10.     {  

11.         this.flag=flag;  

12.     }  

13.     public void run()  

14.     {  

15.         if(flag)  

16.         {  

17.             while(true)  

18.             {  

19.                 synchronized(LockClass.locka)//a锁  

20.                 {  

21.                     System.out.println(Thread.currentThread().getName()+"------if_locka");  

22.   

23.                     synchronized(LockClass.lockb)//b锁  

24.                     {  

25.                     System.out.println(Thread.currentThread().getName()+"------if_lockb");  

26.                     }  

27.                 }  

28.             }  

29.         }  

30.         else  

31.         {  

32.             while(true)  

33.             {  

34.                 synchronized(LockClass.lockb)//b锁  

35.                 {  

36.                   System.out.println(Thread.currentThread().getName()+"------else_lockb");  

37.   

38.                     synchronized(LockClass.locka)//a锁  

39.                     {  

40.                    System.out.println(Thread.currentThread().getName()+"------else_locka");  

41.                     }  

42.                 }  

43.             }  

44.         }  

45.     }  

46. }  

47.   

48. //定义两个锁  

49. class LockClass  

50. {  

51.     static Object locka = new Object();  

52.     static Object lockb = new Object();  

53. }  

54.   

55. class DeadLock  

56. {  

57.     public static void main(String[] args)  

58.     {  

59.         //创建2个进程,并启动  

60.         new Thread(new LockTest(true)).start();  

61.         new Thread(new LockTest(false)).start();  

62.     }  

63. }  

结果:程序卡住,不能继续执行

       

-----------android培训java培训、java学习型技术博客、期待与您交流!------------ 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值