Java基础---多线程

来源:互联网 发布:学安卓要先学c语言吗 编辑:程序博客网 时间:2024/04/28 00:30


 

一、多线程概述

       现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式


1、进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

2、一个进程至少有一个线程。

3、线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。

4、“同时”执行是人的感觉,在线程之间实际上轮换执行。

 

二、创建线程的方式

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

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方法。

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

程序示例:

/* * 1.继承Thread 步骤:  * 定义类 继承 Thread * 重写 run 方法  : run() 就是你要执行的任务  * 把新线程要做的事写在 run 方法中 * 在测试类当中, 创建线程对象 * 开启新线程, 内部会自动执行 run 方法  *  * 注意: start() 开启.   */package cn.itcast_02_impl;/* * */public class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println(getName() + "--" + i);}}}public class Demo {public static void main(String[] args) {MyThread mt1 = new MyThread();MyThread mt2 = new MyThread();MyThread mt3 = new MyThread();MyThread mt4 = new MyThread();// mt1.run();mt1.start(); // JVM 会自动调用 run() java.lang.IllegalThreadStateExceptionmt2.start(); // JVM 会自动调用 run()mt3.start(); // JVM 会自动调用 run()mt4.start(); // JVM 会自动调用 run()for (int i = 0; i < 10000; i++) {System.out.println("aaaaaaaaaa");}}}


 

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

1、两种创建方式的区别

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

        实现Runnable:线程代码存放在接口子类run方法中,创建自定义的 Runnable 的子类对象 R , 创建 new Thread(R) 对象, 传入 R  ,调用 thread.start()开启新线程, 内部会自动调用 Runnable 的run()方法  。

2、几种状态

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

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

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

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

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

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

图解:

   

四、线程安全问题

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

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

简单的说就两点:

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

        b、线程随机性    。

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

2、解决办法——同步

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

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

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

        a、同步代码块

        用法:

                 synchronized(对象)

                 {需要被同步的代码}

        同步可以解决安全问题的根本原因就在那个对象上。其中对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。

示例:

</pre><pre name="code" class="java">package cn.itcast_07_synchronized;/* * */public class T2 extends Thread {Printer p;T2() {}T2(Printer p) {this.p = p;}@Overridepublic void run() {p.printB();}}package cn.itcast_07_synchronized;/* * */public class T1 extends Thread {Printer p;T1() {}T1(Printer p) {this.p = p;}@Overridepublic void run() {p.printA();}}public class Printer {Object o = new Object();static {}// synchronized( 任意对象 ){////// }public void printA() {synchronized (this) {for (int i = 0; i < 1000; i++) {System.out.print("a");System.out.print("a");System.out.print("a");System.out.print("a");System.out.print("a");System.out.println();}}}public void printB() {synchronized (this) {for (int i = 0; i < 1000; i++) {System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.println();}}}}

        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. }  

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

       

 

七、线程间通信

        其实就是多个线程在操作同一个资源,但是操作的动作不同。

1、使用同步操作同一资源的示例:

[java] view plaincopy

1.  /* 

2.     有一个资源 

3.  一个线程往里存东西,如果里边没有的话 

4. 一个线程往里取东西,如果里面有得话 

5.  */  

6.   

7.  //资源  

8. class Resource  

9.  {  

10.    private String name;  

11.     private String sex;  

12.    private boolean flag=false;  

13.       

14.    public synchronized void setInput(String name,String sex)  

15.     {  

16.        if(flag)  

17.         {  

18.            try{wait();}catch(Exception e){}//如果有资源时,等待资源取出  

19.         }  

20.        this.name=name;  

21.         this.sex=sex;  

22.  

23.         flag=true;//表示有资源  

24.        notify();//唤醒等待  

25.     }  

26.  

27.     public synchronized void getOutput()  

28.    {         

29.         if(!flag)  

30.        {  

31.             try{wait();}catch(Exception e){}//如果木有资源,等待存入资源  

32.        }  

33.         System.out.println("name="+name+"---sex="+sex);//这里用打印表示取出  

34.                  

35.         flag=false;//资源已取出  

36.        notify();//唤醒等待  

37.     }  

38.}  

39.   

40.  

41. //存线程  

42.class Input implements Runnable  

43. {  

44.    private Resource r;  

45.     Input(Resource r)  

46.    {  

47.         this.r=r;  

48.    }  

49.     public void run()//复写run方法  

50.    {  

51.         int x=0;  

52.        while(true)  

53.         {  

54.            if(x==0)//交替打印张三和王羲之  

55.             {  

56.                r.setInput("张三",".....man");  

57.             }  

58.            else  

59.             {  

60.                r.setInput("王羲之","..woman");  

61.             }  

62.            x=(x+1)%2;//控制交替打印  

63.         }  

64.    }  

65. }  

66.  

67. //取线程  

68.class Output implements Runnable  

69. {  

70.    private Resource r;  

71.     Output(Resource r)  

72.    {  

73.         this.r=r;  

74.    }  

75.     public void run()//复写run方法  

76.    {  

77.         while(true)  

78.        {  

79.             r.getOutput();  

80.        }  

81.     }  

82.}  

83.   

84.  

85.   

86.class ResourceDemo2   

87. {  

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

89.     {  

90.        Resource r = new Resource();//表示操作的是同一个资源  

91.   

92.        new Thread(new Input(r)).start();//开启存线程  

93.   

94.        new Thread(new Output(r)).start();//开启取线程  

95.     }  

96.}  

结果:部分截图

 几个小问题:

        1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?

               a,这些方法存在与同步中。

               b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。

               c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。

        2)wait(),sleep()有什么区别?

             wait():释放cpu执行权,释放锁。

             sleep():释放cpu执行权,不释放锁。

        3)为甚么要定义notifyAll?

        因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。

2、JDK1.5中提供了多线程升级解决方案。

        将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。

升级解决方案的示例:

[java] view plaincopy

1.  /* 

2. 生产者生产商品,供消费者使用 

3.  有两个或者多个生产者,生产一次就等待消费一次 

4. 有两个或者多个消费者,等待生产者生产一次就消费掉 

5.   

6. */  

7.    

8. import java.util.concurrent.locks.*;  

9.    

10.class Resource   

11. {     

12.    private String name;  

13.     private int count=1;  

14.    private boolean flag = false;  

15.       

16.    //多态  

17.     private Lock lock=new ReentrantLock();  

18.  

19.     //创建两Condition对象,分别来控制等待或唤醒本方和对方线程  

20.    Condition condition_pro=lock.newCondition();  

21.     Condition condition_con=lock.newCondition();  

22.  

23.     //p1p2共享此方法  

24.    public void setProducer(String name)throws InterruptedException  

25.     {  

26.        lock.lock();//  

27.         try  

28.        {  

29.             while(flag)//重复判断标识,确认是否生产  

30.                condition_pro.await();//本方等待  

31.   

32.            this.name=name+"......"+count++;//生产  

33.             System.out.println(Thread.currentThread().getName()+"...生产..."+this.name);//打印生产  

34.            flag=true;//控制生产\消费标识  

35.             condition_con.signal();//唤醒对方  

36.        }  

37.         finally  

38.        {  

39.             lock.unlock();//解锁,这个动作一定执行  

40.        }  

41.           

42.    }  

43.   

44.    //c1c2共享此方法  

45.     public void getConsumer()throws InterruptedException  

46.    {  

47.         lock.lock();  

48.        try  

49.         {  

50.            while(!flag)//重复判断标识,确认是否可以消费  

51.                 condition_con.await();  

52.  

53.             System.out.println(Thread.currentThread().getName()+".消费."+this.name);//打印消费  

54.            flag=false;//控制生产\消费标识  

55.             condition_pro.signal();  

56.        }  

57.         finally  

58.        {  

59.             lock.unlock();  

60.        }  

61.   

62.    }  

63. }  

64.  

65. //生产者线程  

66.class Producer implements Runnable   

67. {  

68.    private Resource res;  

69.     Producer(Resource res)  

70.    {  

71.         this.res=res;  

72.    }  

73.     //复写run方法  

74.    public void run()  

75.     {  

76.        while(true)  

77.         {  

78.            try  

79.             {  

80.                res.setProducer("商品");  

81.             }  

82.            catch (InterruptedException e)  

83.             {  

84.            }  

85.         }  

86.    }  

87. }  

88.  

89. //消费者线程  

90.class Consumer implements Runnable  

91. {  

92.    private Resource res;  

93.     Consumer(Resource res)  

94.    {  

95.         this.res=res;  

96.    }  

97.     //复写run  

98.    public void run()  

99.     {  

100.        while(true)  

101.         {  

102.            try  

103.             {  

104.                res.getConsumer();  

105.             }  

106.            catch (InterruptedException e)  

107.             {  

108.            }  

109.         }  

110.    }  

111.   

112.}  

113.   

114.class  ProducerConsumer  

115. {  

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

117.     {  

118.        Resource res=new Resource();  

119.   

120.        new Thread(new Producer(res)).start();//第一个生产线程 p1  

121.         new Thread(new Consumer(res)).start();//第一个消费线程 c1  

122.  

123.         new Thread(new Producer(res)).start();//第二个生产线程 p2  

124.        new Thread(new Consumer(res)).start();//第二个消费线程 c2  

125.     }  

126.}  

运行结果:部分截图

       

 

八、停止线程

        在JDK 1.5版本之前,有stop停止线程的方法,但升级之后,此方法已经过时。

那么现在我们该如果停止线程呢?

        只有一种办法,那就是让run方法结束。

1、开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。

      如:run方法中有如下代码,设置一个flag标记。  

[java] view plaincopy

1.  public  void run()  

2. {  

3.      while(flag)  

4.     {     

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

6.     }  

7.  }  

        那么只要在主函数或者其他线程中,在该线程执行一段时间后,将标记flag赋值false,该run方法就会结束,线程也就停止了。

2、上面的1方法可以解决一般情况,但是有一种特殊情况:就是当线程处于冻结状态。就不会读取到标记。那么线程就不会结束。

        当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法interrupt();

如:

[java] view plaincopy

1.  class StopThread implements Runnable  

2. {  

3.      private boolean flag =true;  

4.     public  void run()  

5.      {  

6.         while(flag)  

7.          {  

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

9.          }  

10.    }  

11.     public void changeFlag()  

12.    {  

13.         flag = false;  

14.    }  

15. }  

16.  

17. class  StopThreadDemo  

18.{  

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

20.    {  

21.         StopThread st = new StopThread();  

22.        Thread t1 = new Thread(st);  

23.         Thread t2 = new Thread(st);   

24.        t1.start();  

25.         t2.start();   

26.  

27.         int num = 0;  

28.        while(true)  

29.         {  

30.            if(num++ == 60)  

31.             {  

32.                t1.interrupt();//清除冻结状态  

33.                 t2.interrupt();  

34.                st.changeFlag();//改变循环标记  

35.                 break;  

36.            }  

37.             System.out.println(Thread.currentThread().getName()+"......."+num);  

38.        }  

39.         System.out.println("over");  

40.    }  

41. }  

结果:

       

 

九、什么时候写多线程?

        当某些代码需要同时被执行时,就用单独的线程进行封装。

示例:

[java] view plaincopy

1.  class  ThreadTest  

2. {  

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

4.     {  

5.          //一条线程  

6.         new Thread()  

7.          {  

8.             public void run()  

9.              {  

10.                for (int x=0;x<100 ;x++ )  

11.                 {  

12.                    System.out.println(Thread.currentThread().toString()+"....."+x);  

13.                 }  

14.            }  

15.         }.start();  

16.      

17.         //又是一条线程  

18.        Runnable r= new Runnable()  

19.         {  

20.            public void run()  

21.             {  

22.                for (int x=0;x<100 ;x++ )  

23.                 {  

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

25.                 }  

26.            }  

27.         };  

28.        new Thread(r).start();  

29.           

30.        //可以看作主线程  

31.         for (int x=0;x<1000 ;x++ )  

32.        {  

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

34.        }  

35.           

36.    }  

37. }  

 

扩展小知识:

1、join方法

        当A线程执行到了b线程的.join()方法时,A线程就会等待,等B线程都执行完,A线程才会执行。(此时B和其他线程交替运行。)join可以用来临时加入线程执行。

2、setPriority()方法用来设置优先级

       MAX_PRIORITY 最高优先级10

       MIN_PRIORITY   最低优先级1

       NORM_PRIORITY 分配给线程的默认优先级

3、yield()方法可以暂停当前线程,让其他线程执行。

 




0 1