Java多线程

来源:互联网 发布:linux 桌面 市场份额 编辑:程序博客网 时间:2024/06/08 11:01

进程与线程

          在多任务系统中,每个独立执行的程序成为进程,也就是 “正在进行的程序”。

          一个进程中又可以包含一个或多个线程,一个线程就是一个程序内部的一条执行线索,如果在一个程序中实现多段代码同时交替 运行,就需要产生多个线程,并指定每个线程上所要运行的程序代码段,这就是多线程。

单线程与多线程对比

         

 

创建线程

      一、用 Thread 类创建多线程 (继承自 Thread 类,并重写 run 方法)

  • 要将一段代码在一个新的线程上运行,该代码应该在一个类的 run 函数中,并且 run 函数所在的类是 Thread 的子类。倒过来看,我们要实现多线程,必须编写一个类继承了 Thread 类,子类覆盖Thread类中的 run 函数,在子类的 run 函数中调用想在新线程上运行的程序代码。
  • 启动一个新的线程,我们不是直接调用 Thread 的雷子对象中的 run 方法,而是调用 Thread 子类对象的 start 方法(从 Thread 继承到的),Thread 类对象的 start 方法将产生一个新的线程,并在该线程上运行 run 方法,根据面向对象的多态性,在该线程上实际运行的是 Thread 子类对象中的 run 方法。
  • 由于线程的代码段在 run 方法中,那么该方法执行完成以后,该线程也就相应的结束了,因而我们可以通过控制 run 方法中循环的条件来控制线程的结束。

      例:

 

         

 

      二、用 Runnable 接口创建多线程 (实现 run 方法)

  • 适合多个相同程序代码的线程去处理同一资源的情况,较好地体现了面向对象的设计思想。
  • 可以避免由于 Java 的单继承特性带来的局限性。
  • 当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了 Runnable 接口的类的实例。
  • 事实上,几乎所有多线程应用都可用 Runnable 接口方式实现

      例:

 

        

 

后台线程与联合线程

       如果我们对某个线程对象在启动(调用 start 方法)之前就调用了 setDaemon(true) 方法,这个线程就变成了后台线程反之,如果我们没有调用 setDaemon(true) 或者 setDaemon(false),这  个线程就是前台线程。

      在Java中,只要还有一个前台线程在运行,这个进程就不会结束,如果一个进程中只有后台线程在运行,这个进程就会结束。

      thread.join() 的作用是把 thread 对象所对应的线程合并到调用 thread.join() 语句的线程中,thread.join() 中 join() 方法也可以指定合并时间(毫秒)

 

      上代码!

 

     

 

      下面写一个实例,模拟4个售票点共同发售100张火车票。此时注意一个问题,到底是继承 Thread 还是实现 Runnable 接口,很显然应该使用接口,因为4个售票点发售的是同100张火车票,当然是同一个对象,如果我们使用继承的话,不能保证对同一对象的引用。

    

      继续上代码!

 

     

 

      貌似上面代码没有问题,但是有一个很严重的潜在问题,存在这样一种情况,当1号售票点正准备售出第98张票的时候,cpu切换到了2号售票点那个线程,此时1号售票点并没有执行99-1,2号售票点得到的剩余票数仍然是99,所以也会执行售第98张票,当cpu再次切换到线程1时,线程1继续执行它先前的操作,导致了第98张票被1号售票点和2号售票点售出了两次!这是个很严重的问题,为了解决这个问题,我们引入了线程安全。

 

线程安全

     一、同步代码块

     

              synchronized 修饰的同步代码块 synchronized(Object){},其中 Object 为任意对象,它的作用就是一个标志位,有0,1两个状态,其开始状态为1,当一个线程执行到 synchronized 语句时,线程会检查对象标志位的状态,如果为1,则继续执行,同时会将对象的标志位转为0,如果对象的标志位为0,线程会发生阻塞,同时此线程让出cpu。但是,计算机真正的运行不是这样的,我们不能控制cpu的状态,即使一个线程进入到了 synchronized 代码块中,cpu仍然还是有可能切换到其它线程,但是当cpu切换到其它线程的时候,再次检查标志位,发现不能继续,则线程会继续阻塞。当cpu切换到先前那个线程的时候,它才继续执行 synchronized 代码块中的语句。因此上面的代码应该改为这样:

 

           又上代码啦!

 

          

 

           因为4个线程都是引用的 Sale 对象,因此我们可以将 Sale 对象作为标志。

 

     二、同步函数

 

            synchronized 修饰的函数。public synchronized void Sale(){}。当一个线程进入到了由 synchronized 修饰的方法体内时,它就得到了监视器,只有当该方法执行完以后,其它线程才可以调用该方法。上面代码又可以改了

 

         

 

      三、代码块与函数同步

 

              线程同步靠的就是 synchronized 中标志位的检查,如果让代码块的标志对象和函数的标志对象相同,那么就能实现代码块与函数的线程同步

 

 

线程的生命周期

 

          新建  --->   就绪  --->   运行  --->   阻塞  --->   死亡

 

多线程间的通信

 

          所有Object都有 wait()、notify()、notifyAll() 方法

  • wait :告诉当前线程放弃监视器并进入睡眠状态直到其他线程进入同一监视器并调用 notify() 为止。
  • notify:唤醒同一对象监视器中调用 wait 的第一个线程。
  • notifyAll:唤醒同一对象监视器中调用 wait 的所有线程。具有最高优先级的线程首先被唤醒并执行。

 

              下面仍然还是用一个实际应用来理解多线程间的通信

 

              

 

               我们用两个线程,其中一个线程将信息存入缓冲区,另一个线程从缓冲区读取数据,并保证两个线程交替进行

 

              

 

               上面代码虽然完成了所需的功能,但是看起来很凌乱,甚至有些不妥,继续修改!

 

              

 

               大功告成!

 

终止线程

 

          通过控制 run 方法中的执行语句,来控制线程的结束。如果一个线程中的 run 方法语句都执行完了,那么这个线程也就销毁了。例子,让主线程执行了50次之后,让另一个线程终止

 

            最后一个例子  ^_^