zoukankan      html  css  js  c++  java
  • java中的多线程和锁

      当多个线程并发执行的时候,其实是对处理机资源的轮转调度,当然也包括其他的资源(如打印机等),这样就很容易的产生死锁(多个线程对同一资源的竞争,占有这个资源的线程又在等待其他的资源而不能得到)。因此,引入wait()/notify()(or notifyAll())是很有必要的:当条件不满足的的时候(注意wait()方法要在synchronized块中),调用wait()方法解锁,使其进入“等待”状态,以便资源释放给其他线程使用;当有另外的“动作”导致系统状态发生改变(使刚刚的条件满足),对应的需要使用notify()或者notifyAll()方法来唤醒等待的线程,将唤醒的线程放入就绪队列(notify()也需要放在synchronized块中,与wait()相对应,如果不用notify的话线程将永远处于等待状态导致死锁)。

      具体应用如下:

      管程(就绪状态)--> 运行 --> 完成;

      管程(就绪状态)--> 运行 --> 等待(条件不满足)--> 唤醒(条件满足)--> 重新进入管程;

      管程(就绪状态)--> 运行 --> 休眠(sleep)-- > 管程(休眠时间到);

      管程(就绪状态)--> 运行 --> 管程(时间片到,但是还没有执行完毕);

      管程(就绪状态)--> 运行 --> 阻塞(发出IO操作请求)--> 管程(IO操作请求结束);

      

      下面是一个很好的例子(建一个很简单的线程池):(转自http://www.gbsou.com/2010/01/27/1971.html

      本示例程序由三个类构成,第一个是TestThreadPool类,它是一个测试程序,用来模拟客户端的请求,当你运行它时,系统首先会显示线程池的初始化信息,然后提示你从键盘上输入字符串,并按下回车键,这时你会发现屏幕上显示信息,告诉你某个线程正在处理你的请求,如果你快速地输入一行行字符串,那么你会发现线程池中不断有线程被唤醒,来处理你的请求,在本例中,我创建了一个拥有10个线程的线程池,如果线程池中没有可用线程了,系统会提示你相应的警告信息,但如果你稍等片刻,那你会发现屏幕上会陆陆续续提示有线程进入了睡眠状态,这时你又可以发送新的请求了。
      第二个类是ThreadPoolManager类,顾名思义,它是一个用于管理线程池的类,它的主要职责是初始化线程池,并为客户端的请求分配不同的线程来进行处理,如果线程池满了,它会对你发出警告信息。
      最后一个类是SimpleThread类,它是Thread类的一个子类,它才真正对客户端的请求进行处理,SimpleThread在示例程序初始化时都处于睡眠状态,但如果它接受到了ThreadPoolManager类发过来的调度信息,则会将自己唤醒,并对请求进行处理。
       首先我们来看一下TestThreadPool类的源码:
      //TestThreadPool.java
      1 import java.io.*;
      2
      3
      4 public class TestThreadPool
      5 {
      6 public static void main(String[] args)
      7 {
      8 try{
      9 BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
      10 String s;
      11 ThreadPoolManager manager = new ThreadPoolManager(10);
      12 while((s = br.readLine()) != null)
      13 {
      14 manager.process(s);
      15 }
      16 }catch(IOException e){}
      17 }
      18 }
      由于此测试程序用到了输入输入类,因此第1行导入了JAVA的基本IO处理包,在第11行中,我们创建了一个名为manager的类,它给ThreadPoolManager类的构造函数传递了一个值为10的参数,告诉ThreadPoolManager类:我要一个有10个线程的池,给我创建一个吧!第12行至15行是一个无限循环,它用来等待用户的键入,并将键入的字符串保存在s变量中,并调用ThreadPoolManager类的process方法来将这个请求进行处理。
      下面我们再进一步跟踪到ThreadPoolManager类中去,以下是它的源代码:
      //ThreadPoolManager.java
      1 import java.util.*;
      2
      3
      4 class ThreadPoolManager
      5 {
      6
      7 private int maxThread;
      8 public Vector vector;
      9 public void setMaxThread(int threadCount)
      10 {
      11 maxThread = threadCount;
      12 }
      13
      14 public ThreadPoolManager(int threadCount)
      15 {
      16 setMaxThread(threadCount);
      17 System.out.println("Starting thread pool…");
      18 vector = new Vector();
      19 for(int i = 1; i <= 10; i++)
      20 {
      21 SimpleThread thread = new SimpleThread(i);
      22 vector.addElement(thread);
      23 thread.start();
      24 }
      25 }
      26
      27 public void process(String argument)
      28 {
      29 int i;
      30 for(i = 0; i < vector.size(); i++)
      31 {
      32 SimpleThread currentThread = (SimpleThread)vector.elementAt(i);
      33 if(!currentThread.isRunning())
      34 {
      35 System.out.println("Thread "+ (i+1) +" is processing:" +
      argument);
      36 currentThread.setArgument(argument);
      37 currentThread.setRunning(true);
      38 return;
      39 }
      40 }
      41 if(i == vector.size())
      42 {
      43 System.out.println("pool is full, try in another time.");
      44 }
      45 }
      46 }//end of class ThreadPoolManager
      我们先关注一下这个类的构造函数,然后再看它的process()方法。第16-24行是它的构造函数,首先它给ThreadPoolManager类的成员变量maxThread赋值,maxThread表示用于控制线程池中最大线程的数量。第18行初始化一个数组vector,它用来存放所有的SimpleThread类,这时候就充分体现了JAVA语言的优越性与艺术性:如果你用C语言的话,至少要写100行以上的代码来完成vector的功能,而且C语言数组只能容纳类型统一的基本数据类型,无法容纳对象。好了,闲话少说,第19-24行的循环完成这样一个功能:先创建一个新的SimpleThread类,然后将它放入vector中去,最后用thread.start()来启动这个线程,为什么要用start()方法来启动线程呢?因为这是JAVA语言中所规定的,如果你不用的话,那这些线程将永远得不到激活,从而导致本示例程序根本无法运行。
       下面我们再来看一下process()方法,第30-40行的循环依次从vector数组中选取SimpleThread线程,并检查它是否处于激活状态(所谓激活状态是指此线程是否正在处理客户端的请求),如果处于激活状态的话,那继续查找vector数组的下一项,如果vector数组中所有的线程都处于激活状态的话,那它会打印出一条信息,提示用户稍候再试。相反如果找到了一个睡眠线程的话,那第35-38行会对此进行处理,它先告诉客户端是哪一个线程来处理这个请求,然后将客户端的请求,即字符串argument转发给SimpleThread类的setArgument()方法进行处理,并调用SimpleThread类的setRunning()方法来唤醒当前线程,来对客户端请求进行处理。
      可能你还对setRunning()方法是怎样唤醒线程的有些不明白,那我们现在就进入最后一个类:SimpleThread类,它的源代码如下:
      //SimpleThread.java
      1 class SimpleThread extends Thread
      2 {
      3 private boolean runningFlag;
      4 private String argument;
      5 public boolean isRunning()
      6 {
      7 return runningFlag;
      8 }
      9 public synchronized void setRunning(boolean flag)
      10 {
      11 runningFlag = flag;
      12 if(flag)
      13 this.notify();
      14 }
      15
      16 public String getArgument()
      17 {
      18 return this.argument;
      19 }
      20 public void setArgument(String string)
      21 {
      22 argument = string;
      23 }
      24
      25 public SimpleThread(int threadNumber)
      26 {
      27 runningFlag = false;
      28 System.out.println("thread " + threadNumber + "started.");
      29 }
      30
      31 public synchronized void run()
      32 {
      33 try{
      34 while(true)
      35 {
      36 if(!runningFlag)
      37 {
      38 this.wait();
      39 }
      40 else
      41 {
      42 System.out.println("processing " + getArgument() + "… done.");
      43 sleep(5000);
      44 System.out.println("Thread is sleeping…");
      45 setRunning(false);
      46 }
      47 }
      48 } catch(InterruptedException e){
      49 System.out.println("Interrupt");
      50 }
      51 }//end of run()
      52 }//end of class SimpleThread
      如果你对JAVA的线程编程有些不太明白的话,那我先在这里简单地讲解一下,JAVA有一个名为Thread的类,如果你要创建一个线程,则必须要从Thread类中继承,并且还要实现Thread类的run()接口,要激活一个线程,必须调用它的start()方法,start()方法会自动调用run()接口,因此用户必须在run()接口中写入自己的应用处理逻辑。那么我们怎么来控制线程的睡眠与唤醒呢?其实很简单,JAVA语言为所有的对象都内置了wait()和notify()方法,当一个线程调用wait()方法时,则线程进入睡眠状态,就像停在了当前代码上了,也不会继续执行它以下的代码了,当调用notify()方法时,则会从调用wait()方法的那行代码继续执行以下的代码,这个过程有点像编译器中的断点调试的概念。以本程序为例,第38行调用了wait()方法,则这个线程就像凝固了一样停在了38行上了,如果我们在第13行进行一个notify()调用的话,那线程会从第38行上唤醒,继续从第39行开始执行以下的代码了。
      通过以上的讲述,我们现在就不难理解SimpleThread类了,第9-14行通过设置一个标志runningFlag激活当前线程,第25-29行是SimpleThread类的构造函数,它用来告诉客户端启动的是第几号进程。第31-50行则是我实现的run()接口,它实际上是一个无限循环,在循环中首先判断一下标志runningFlag,如果没有runningFlag为false的话,那线程处理睡眠状态,否则第42-45行会进行真正的处理:先打印用户键入的字符串,然后睡眠5秒钟,为什么要睡眠5秒钟呢?如果你不加上这句代码的话,由于计算机处理速度远远超过你的键盘输入速度,因此你看到的总是第1号线程来处理你的请求,从而达不到演示效果。最后第45行调用setRunning()方法又将线程置于睡眠状态,等待新请求的到来。
      最后还有一点要注意的是,如果你在一个方法中调用了wait()和notify()函数,那你一定要将此方法置为同步的,即synchronized,否则在编译时会报错,并得到一个莫名其妙的消息:“current thread not owner”(当前线程不是拥有者)。
    本文来自:关注J2EE,http://www.gbsou.com

  • 相关阅读:
    Codeforces 1255B Fridge Lockers
    Codeforces 1255A Changing Volume
    Codeforces 1255A Changing Volume
    leetcode 112. 路径总和
    leetcode 129. 求根到叶子节点数字之和
    leetcode 404. 左叶子之和
    leetcode 104. 二叉树的最大深度
    leetcode 235. 二叉搜索树的最近公共祖先
    450. Delete Node in a BST
    树的c++实现--建立一棵树
  • 原文地址:https://www.cnblogs.com/kelin1314/p/1678619.html
Copyright © 2011-2022 走看看