黑马程序员-- 六、多线程

来源:互联网 发布:centos怎么下载软件 编辑:程序博客网 时间:2024/06/04 19:00

----------------------   ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ---------------------

线程和进程的区别与联系:

 进程:是一个正在执行中的程序,

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

 线程就是进程中的一个独立的控制单元。

 两者之间的关系: 线程在控制着进程的执行

两者之间的关系:

       进程有独立的进程空间,进程中的数据存放空间(堆空间与栈空间)是独立存在的。

       线程的堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小,相互之间可以影响。

线程的五种状态

 

1.new新建状态

       当程序程序使用new关键字创建一个线程后,该线程就处于新建状态,此线程还没有启动,当线程对象调用start方法时,线程启动,就进入Runnable状态。

2.Runnable可运行(就绪)状态

       当线程处于Runnable状态时,表示线程准备就绪,当获取到CPU执行权时就进入Running状态。

3.Running运行(正在运行)状态

       当线程获取到了CPU执行权时,表示线程进入到Running状态,开始执行线程体(run方法中的内容)。执行完成后进入dead状态

4.Block阻塞状态

       当线程在Running状态时,调用了sleep()方法,则表示该线程主动放弃CPU资源。进入Block阻塞状态。

       当线程在Running状态时,调用了阻塞式的方法(比如IO中的键盘输入,网络编程中的等待反馈信息的方法),在该方法返回前,该线程被进入到Block状态中。

注意:当阻塞状态结束时,该线程是回到到Runnable状态而不是直接进入Running状态。

5.Dead死亡状态

       当线运行完run中的方法时,线程进入dead状态。

注意:死亡状态的线程无法被start方法调用。

创建线程的两种方式

 继承Thread类

步骤:

       1.定义一个类继承Thedad

       2.复写Thread中的run方法

       3.调用线程中的Start方法来启动线程与调用run方法。

       4.线程启动成功。

  演示:

class Demo extends Thread 
{
       public void run()
       {
System.out.println("第一个线程");   
       }
      public static void main(String[] args) 
      {
Demo d = new Demo();//创建好一个线程。
d.start();//开启线程并执行该线程的run方法。

      }
}


    实现Runnable接口

步骤:

  

1,定义类实现Runnable接口
2,覆盖Runnable接口中的run方法。
将线程要运行的代码存放在该run方法中。
3,通过Thread类建立线程对象。
4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
为什么要将Runnable接口的子类对象传递给Thread的构造函数。
因为,自定义的run方法所属的对象是Runnable接口的子类对象。
所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。
5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法

  演示:
class Demo implements Runnable  
{
       public void run()
       {
System.out.println("Runnable第一个线程");   
       }
     public static void main(String[] args) 
    {
Demo d = new Demo();
Thread t1 =new Thread(d);
t1.start();
   }
}



实现方式和继承方式有什么区别呢?

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

两种方式区别:
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable,线程代码存在接口的子类的run方法。

死锁

一般造成死锁必须同时满足如下4个条件:

  1,互斥条件:线程使用的资源必须至少有一个是不能共享的;

  2,请求与保持条件:至少有一个线程必须持有一个资源并且正在等待获取一个当前被其它线程持有的资源;

  3,非剥夺条件:分配资源不能从相应的线程中被强制剥夺;

  4,循环等待条件:第一个线程等待其它线程,后者又在等待第一个线程。

避免死锁:      

因为要产生死锁,这4个条件必须同时满足,所以要防止死锁的话,只需要破坏其中一个条件即可。


线程间的通信

一般而言,在一个应用程序中(即进程),一个线程往往不是孤立存在的,常常需要和其它线程通信,以执行特定的任务。如主线程和次线程,次线程与次线程,工作线程和用户界面线程等。这样,线程与线程间必定有一个信息传递的渠道。这种线程间的通信不但是难以避免的,而且在多线程编程中也是复杂和频繁的。简单的说,其实就是多个线程在操作同一个资源,但是操作的动作不同。

其实,Java提供了3个非常重要的方法来巧妙地解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。它们都是Object类的最终方法,因此每一个类都默认拥有它们。 

虽然所有的类都默认拥有这3个方法,但是只有在synchronized关键字作用的范围内,并且是同一个同步问题中搭配使用这3个方法时才有实际的意义。 



线程同步

同步的前提:

        (1)必须保证有两个以上线程

        (2)必须是多个线程使用同一个锁,即多条语句在操作线程共享数据

        (3)必须保证同步中只有一个线程在运行

同步的好处和弊端

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

        弊端:多线程都需要判断锁,比较消耗资源

 

同步的两种表现形式:

 (1)同步代码块:

     可以指定需要获取哪个对象的同步锁,使用synchronized的代码块同样需要锁,但他的锁可以是任意对象考虑到安全问题,一般还是使用同一个对象,相对来说效率较高。

 

注意:

**虽然同步代码快的锁可以使任何对象,但是在进行多线程通信使用同步代码快时,必须保证同步代码快的锁的对象和,否则会报错。

**同步函数的锁是this,也要保证同步函数的锁的对象和调用wait、notify和notifyAll的对象是同一个对象,也就是都是this锁代表的对象。

  语法格式:

      synchronized(对象)

      {

         需同步的代码;

      }

 (2)同步函数

    同步方法是指进入该方法时需要获取this对象的同步锁,在方法上使用synchronized关键字,使用this对象作为锁,也就是使用了当前对象,因为锁住了方法,所以相对于代码块来说效率相对较低。

PS:静态同步函数的锁是该方法所在的类的字节码文件对象,即类名.class文件

                格式:

                修饰词synchronized 返回值类型 函数名(参数列表)

                {

                        需同步的代码;

                }

 
















----------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------

0 0