Java是通过Java.lang.Thread类来实现多线程的,Thread对象描述了一个单独的线程。要产生一个线程,有两种方法:
1、需要从Java.lang.Thread类继承一个新的线程类,重载它的run()方法;
2、通过Runnalbe接口实现一个从非线程类继承来类的多线程,重载Runnalbe接口的run()方法。运行一个新的线程,只需要调用它的start()方法即可。如:
/**===================================================================== * 文件:ThreadDemo_01.java * 描述:产生一个新的线程 * ====================================================================== */ class ThreadDemo extends Thread{ Threads() { } Threads(String szName) { super(szName); } // 重载run函数 public void run() { for (int count = 1,row = 1; row < 20; row++,count++) { for (int i = 0; i < count; i++) { System.out.print('*'); } System.out.println(); } } } class ThreadMain{ public static void main(String argv[]){ ThreadDemo th = new ThreadDemo(); // 调用start()方法执行一个新的线程 th.start(); } }
有些时候我们需要等待一个线程终止后再运行我们的另一个线程,这时我们应该怎么办呢?请看下面的例子:
/**===================================================================== * 文件:ThreadDemo_02.java * 描述:等待一个线程的结束 * ====================================================================== */ class ThreadDemo extends Thread{ Threads() { } Threads(String szName) { super(szName); } // 重载run函数 public void run() { for (int count = 1,row = 1; row < 20; row++,count++) { for (int i = 0; i < count; i++) { System.out.print('*'); } System.out.println(); } } } class ThreadMain{ public static void main(String argv[]){ //产生两个同样的线程 ThreadDemo th1 = new ThreadDemo(); ThreadDemo th2 = new ThreadDemo(); // 我们的目的是先运行第一个线程,再运行第二个线程 th1.start(); th2.start(); } }
这里我们的目标是要先运行第一个线程,等第一个线程终止后再运行第二个线程,而实际运行的结果是如何的呢?实际上我们运行的结果并不是两个我们想要的直角三角形,而是一些乱七八糟的*号行,有的长,有的短。为什么会这样呢?因为线程并没有按照我们的调用顺序来执行,而是产生了线程赛跑现象。实际上Java并不能按我们的调用顺序来执行线程,这也说明了线程是并行执行的单独代码。如果要想得到我们预期的结果,这里我们就需要判断第一个线程是否已经终止,如果已经终止,再来调用第二个线程。代码如下:
/**===================================================================== * 文件:ThreadDemo_03.java * 描述:等待一个线程的结束的两种方法 * ====================================================================== */ class ThreadDemo extends Thread{ Threads() { } Threads(String szName) { super(szName); } // 重载run函数 public void run() { for (int count = 1,row = 1; row < 20; row++,count++) { for (int i = 0; i < count; i++) { System.out.print('*'); } System.out.println(); } } } class ThreadMain{ public static void main(String argv[]){ ThreadMain test = new ThreadMain(); test.Method1(); // test.Method2(); } // 第一种方法:不断查询第一个线程是否已经终止,如果没有,则让主线程睡眠一直到它终止为止 // 即:while/isAlive/sleep public void Method1(){ ThreadDemo th1 = new ThreadDemo(); ThreadDemo th2 = new ThreadDemo(); // 执行第一个线程 th1.start(); // 不断查询第一个线程的状态 while(th1.isAlive()){ try{ Thread.sleep(100); }catch(InterruptedException e){ } } //第一个线程终止,运行第二个线程 th2.start(); } // 第二种方法:join() public void Method2(){ ThreadDemo th1 = new ThreadDemo(); ThreadDemo th2 = new ThreadDemo(); // 执行第一个线程 th1.start(); try{ th1.join(); }catch(InterruptedException e){ } // 执行第二个线程 th2.start(); }
三、线程的同步问题
有些时候,我们需要很多个线程共享一段代码,比如一个私有成员或一个类中的静态成员,但是由于线程赛跑的问题,所以我们得到的常常不是正确的输出结果,而相反常常是张冠李戴,与我们预期的结果大不一样。看下面的例子:
/**============================================================================= * 文件:ThreadDemo_04.java * 描述:多线程不同步的原因 * ============================================================================= */ // 共享一个静态数据对象 class ShareData{ public static String szData = ""; } class ThreadDemo extends Thread{ private ShareData oShare; ThreadDemo(){ } ThreadDemo(String szName,ShareData oShare){ super(szName); this.oShare = oShare; } public void run(){ // 为了更清楚地看到不正确的结果,这里放一个大的循环 for (int i = 0; i < 50; i++){ if (this.getName().equals("Thread1")){ oShare.szData = "这是第 1 个线程"; // 为了演示产生的问题,这里设置一次睡眠 try{ Thread.sleep((int)Math.random() * 100); catch(InterruptedException e){ } // 输出结果 System.out.println(this.getName() + ":" + oShare.szData); }else if (this.getName().equals("Thread2")){ oShare.szData = "这是第 2 个线程"; // 为了演示产生的问题,这里设置一次睡眠 try{ Thread.sleep((int)Math.random() * 100); catch(InterruptedException e){ } // 输出结果 System.out.println(this.getName() + ":" + oShare.szData); } } } class ThreadMain{ public static void main(String argv[]){ ShareData oShare = new ShareData(); ThreadDemo th1 = new ThreadDemo("Thread1",oShare); ThreadDemo th2 = new ThreadDemo("Thread2",oShare); th1.start(); th2.start(); } }
由于线程的赛跑问题,所以输出的结果往往是Thread1对应“这是第 2 个线程”,这样与我们要输出的结果是不同的。为了解决这种问题(错误),Java为我们提供了“锁”的机制来实现线程的同步。锁的机制要求每个线程在进入共享代码之前都要取得锁,否则不能进入,而退出共享代码之前则释放该锁,这样就防止了几个或多个线程竞争共享代码的情况,从而解决了线程的不同步的问题。可以这样说,在运行共享代码时则是最多只有一个线程进入,也就是和我们说的垄断。锁机制的实现方法,则是在共享代码之前加入synchronized段,把共享代码包含在synchronized段中。上述问题的解决方法为:
/**============================================================================= * 文件:ThreadDemo_05.java * 描述:多线程不同步的解决方法--锁 * ============================================================================= */ // 共享一个静态数据对象 class ShareData{ public static String szData = ""; } class ThreadDemo extends Thread{ private ShareData oShare; ThreadDemo(){ } ThreadDemo(String szName,ShareData oShare){ super(szName); this.oShare = oShare; } public void run(){ // 为了更清楚地看到不正确的结果,这里放一个大的循环 for (int i = 0; i < 50; i++){ if (this.getName().equals("Thread1")){ // 锁定oShare共享对象 synchronized (oShare){ oShare.szData = "这是第 1 个线程"; // 为了演示产生的问题,这里设置一次睡眠 try{ Thread.sleep((int)Math.random() * 100); catch(InterruptedException e){ } // 输出结果 System.out.println(this.getName() + ":" + oShare.szData); } }else if (this.getName().equals("Thread2")){ // 锁定共享对象 synchronized (oShare){ oShare.szData = "这是第 2 个线程"; // 为了演示产生的问题,这里设置一次睡眠 try{ Thread.sleep((int)Math.random() * 100); catch(InterruptedException e){ } // 输出结果 System.out.println(this.getName() + ":" + oShare.szData); } } } } class ThreadMain{ public static void main(String argv[]){ ShareData oShare = new ShareData(); ThreadDemo th1 = new ThreadDemo("Thread1",oShare); ThreadDemo th2 = new ThreadDemo("Thread2",oShare); th1.start(); th2.start(); } }
由于过多的synchronized段将会影响程序的运行效率,因此引入了同步方法,同步方法的实现则是将共享代码单独写在一个方法里,在方法前加上synchronized关键字即可。
四、Java的等待通知机制
在有些时候,我们需要在几个或多个线程中按照一定的秩序来共享一定的资源。例如生产者--消费者的关系,在这一对关系中实际情况总是先有生产者生产了产品后,消费者才有可能消费;又如在父--子关系中,总是先有父亲,然后才能有儿子。然而在没有引入等待通知机制前,我们得到的情况却常常是错误的。这里我引入《用线程获得强大的功能》一文中的生产者--消费者的例子:
/* ================================================================================== * 文件:ThreadDemo07.java * 描述:生产者--消费者 * 注:其中的一些注释是我根据自己的理解加注的 * ================================================================================== */ // 共享的数据对象 class ShareData{ private char c; public void setShareChar(char c){ this.c = c; } public char getShareChar(){ return this.c; } } // 生产者线程 class Producer extends Thread{ private ShareData s; Producer(ShareData s){ this.s = s; } public void run(){ for (char ch = 'A'; ch <= 'Z'; ch++){ try{ Thread.sleep((int)Math.random() * 4000); }catch(InterruptedException e){} // 生产 s.setShareChar(ch); System.out.println(ch + " producer by producer."); } } } // 消费者线程 class Consumer extends Thread{ private ShareData s; Consumer(ShareData s){ this.s = s; } public void run(){ char ch; do{ try{ Thread.sleep((int)Math.random() * 4000); }catch(InterruptedException e){} // 消费 ch = s.getShareChar(); System.out.println(ch + " consumer by consumer."); }while(ch != 'Z'); } } class Test{ public static void main(String argv[]){ ShareData s = new ShareData(); new Consumer(s).start(); new Producer(s).start(); } }
在以上的程序中,模拟了生产者和消费者的关系,生产者在一个循环中不断生产了从A-Z的共享数据,而消费者则不断地消费生产者生产的A-Z的共享数据。我们开始已经说过,在这一对关系中,必须先有生产者生产,才能有消费者消费。但如果运行我们上面这个程序,结果却出现了在生产者没有生产之前,消费都就已经开始消费了或者是生产者生产了却未能被消费者消费这种反常现象。
下 面修改以上的例子(源自《用线程获得强大的功能》一文):
/* ================================================================================== * 文件:ThreadDemo08.java * 描述:生产者--消费者 * 注:其中的一些注释是我根据自己的理解加注的 * ================================================================================== */ class ShareData{ private char c; // 通知变量 private boolean writeable = true; // ------------------------------------------------------------------------- // 需要注意的是:在调用wait()方法时,需要把它放到一个同步段里,否则将会出现 // "java.lang.IllegalMonitorStateException: current thread not owner"的异常。 // ------------------------------------------------------------------------- public synchronized void setShareChar(char c){ if (!writeable){ try{ // 未消费等待 wait(); }catch(InterruptedException e){} } this.c = c; // 标记已经生产 writeable = false; // 通知消费者已经生产,可以消费 notify(); } public synchronized char getShareChar(){ if (writeable){ try{ // 未生产等待 wait(); }catch(InterruptedException e){} } // 标记已经消费 writeable = true; // 通知需要生产 notify(); return this.c; } } // 生产者线程 class Producer extends Thread{ private ShareData s; Producer(ShareData s){ this.s = s; } public void run(){ for (char ch = 'A'; ch <= 'Z'; ch++){ try{ Thread.sleep((int)Math.random() * 400); }catch(InterruptedException e){} s.setShareChar(ch); System.out.println(ch + " producer by producer."); } } } // 消费者线程 class Consumer extends Thread{ private ShareData s; Consumer(ShareData s){ this.s = s; } public void run(){ char ch; do{ try{ Thread.sleep((int)Math.random() * 400); }catch(InterruptedException e){} ch = s.getShareChar(); System.out.println(ch + " consumer by consumer.**"); }while (ch != 'Z'); } } class Test{ public static void main(String argv[]){ ShareData s = new ShareData(); new Consumer(s).start(); new Producer(s).start(); } }