Java多线程学习小记

这是一个非常经典的知识点,不仅仅是Java这种高级语言特有,毕竟线程和进程是现代操作系统非常经典的概念。这里我们不讨论Java线程和操作系统线程有何异同,事实上,现在的Java中线程的本质,其实就是操作系统中的线程,Linux下是基于pthread库实现的轻量级进程,Windows下是原生的系统Win32 API提供系统调用从而实现多线程。今天主要是来学习总结一下Java多线程。
首先什么是线程就不多说了。在Java中线程有以下几种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMEED_WAITING和TERMINATED。盗图一张,展示Java线程状态转换和条件:
Java线程状态转换图
简单总结一下,一个线程创建后在调用.start()方法前,属于NEW状态;调用之后进入RUNNABLE状态,皇帝轮流做,马上到我家,坐等被OS选中,当然这个过程会有各种调度算法,有兴趣可以去了解一下;一旦选中,成为天选之子,也就了进入RUNNING状态,走上线生巅峰;当然人红是非多,毕竟彼可取而代也,于是面临各方势力的挑战:(1)自己作死,调动了.yeild()方法,于是懒政一段时间进入RUNNAABLE状态,当然还可以重执牛耳;(2)杀出一匹黑马,另一线程调度.join()方法,横插一脚,只能冷眼旁观;又抑或累了sleep一下;又抑或坐等文武百官唇枪舌剑结束,总之这段时间心有余而力不足只能进入BLOCKED状态;(3)等黑马扬长而去、或者自己醒了又或者辩论结束,才发现自己大势似乎已去,又要坐等OS调度,于是再次进入RUNNABLE状态;(4)线生苦短或者天有不测风云,run()方法结束或者内忧外患只能驾崩,于是线程一生结束,进入了TERMINATED状态;(5)最后一种情况较为复杂,天下大乱,线程出逃,各地线程诸侯虎视眈眈,刚逃出都城的线程大难不死可是已经不是RUNNING状态了,而今形势不明,只能调用.wait(),藏器待时,枕戈待旦,进入WAITING状态;不日,忽闻组建勤王大军,这可不是什么阿猫阿狗都能参与,广发英雄帖,一番选拔考核,再notify()或者notifyAll()参与组建大军,而总有蝇营狗苟之辈调用synchronize方法风闻大军将至故背主求荣加入大军,总之各地诸侯摩拳擦掌,大有十八路诸侯讨伐董卓之势;又或者wait()时间到,振臂高呼组建靖难大军;于是线程大军进入了TIMEED_WAITING状态,士气空前高涨,磨刀霍霍。然而问题来了,谁是统帅?毕竟吾等不是四世三公,唯有公平竞争,于是随机一人执掌帅印,也就是拿到对象的锁标记,至此进入RUNNABLE状态,对RUNNING地位再度发起冲锋。。。这就是一个线程的前世今生,颠沛流离。
相信大家对基本的run()或者start()等基本线程方法有了较多的了解,故不多做介绍。主要是针对o.wait()、synchronized、o.notify()/o.notifyAll()方法的介绍。在 Java 中可以用 wait、notify 和 notifyAll 来实现线程间的通信,并配合synchronized加锁来实现同步。线程在运行的时候,如果发现某些条件没有被满足,可以调用.wait()方法暂停自己的执行,并且放弃已经获得的锁,然后进入等待状态。当该线程被其他线程唤醒并获得锁后,可以沿着之前暂停的地方继续向后执行,而不是再次从同步代码块开始的地方开始执行。但是需要注意的一点是,对线程等待的条件的判断要使用while而不是if来进行判断。这样在线程被唤醒后,会再次判断条件是否正真满足。以经典的消费者生产者问题为例:

   //生产者生产产品
  public synchronized void produce()
  {
      while(this.product >= MAX_PRODUCT)//缓冲池最大值MAX_PRODUCT
      {
          try
          {
              wait();  
              System.out.println("产品已满,请稍候再生产");
          }
          catch(InterruptedException e)
          {
              e.printStackTrace();
          }
          return;
      }

      this.product++;//产品数量+1
      System.out.println("生产者生产了第" + this.product + "个产品.");
      notifyAll();   //通知等待区的消费者可以消费产品
  }
  // 消费者消费产品
  public synchronized void consume()
  {
     while(this.product <= MIN_PRODUCT)//缓冲池最小值MIN_PRODUCT
      {
          try 
          {
              wait(); 
              System.out.println("产品已罄,稍候再消费");
          } 
          catch (InterruptedException e) 
          {
              e.printStackTrace();
          }
          return;
      }

      System.out.println("消费者消费了第" + this.product + "个产品.");
      this.product--;//产品数量-1
      notifyAll();   //通知等待区的生产者可以生产产品
  }

相信通过这个例子大家会对o.wait()、synchronized、o.notify()/o.notifyAll()方法有一个基本的理解。当然,介绍的还是比较浅显,九牛一毛,而且还是个人愚见。学习过程中我觉得莫名熟悉,这让我想起了操作系统中的P、V操作,也就是实现进程的同步与互斥,这不就是低配版的o.wait()、synchronized、o.notify()/o.notifyAll()方法,Java多线程同步与互斥机制。其中,PV操作由P操作原语和V操作原语组成(原语是不可中断的过程),对信号量进行操作,具体定义如下:
P(S):①将信号量S的值减1,即S=S-1;
②如果S>=0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。
V(S):①将信号量S的值加1,即S=S+1;
②如果S>0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。
还是以生产者和消费者这个经典问题为例:

//一个生产者,一个消费者,公用一个缓冲区。
//empty——表示缓冲区是否为空,初值为1。
//full——表示缓冲区中是否为满,初值为0。

生产者进程:

p(empty);//empty-1,初值为1,若等于0说明缓冲区已满,阻塞。否则为空,正常执行。

向buffer放产品;//缓冲区+1

v(full);//full+1,而初值为0 ,故full为1>0,故释放等待队列中的消费者进程开始执行,进入消费者进程,同理。

消费者进程:

p(full);

从buffer取产品;

v(empty);

知识还是有共同的地方。这篇博客主要介绍了Java多线程的状态转换、Java多线程实现同步互斥以及操作系统P、V操作,限于篇幅和水平,疏漏之处还望批评指正。

猜你喜欢

转载自blog.csdn.net/qq_29688997/article/details/81778077