新手一枚,Java学习中,把自己学习多线程的知识总结一下,梳理下知识,方便日后查阅,高手莫进。
本文的主要内容:
[1] 实现线程的两种方法 [2] 线程的启动与停止
[3] 线程的互斥 [4] 线程协作
[5] 线程Join [6]Object实现生产者、消费者问题
[7]Lock类实现生产者、消费者问题 [8] 线程优先级
[9]守护线程(daemon线程) [10]线程池概念
一 实现线程的两种方法
JAVA中实现线程,有两种方法。一种是 扩展 Threaad 类,一种是实现package java.lang.Runnble接口。
首先来看看这JAVA API文档中如何定义这两个:
Runnble接口
package java.lang; public interface Runnable { public abstract void run(); }
这个接口的定义很简单,只是定义了一个方法。
Threaad 类
package java.lang; public class Thread implements Runnable { /* What will be run. */ private Runnable target; private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { ......... this.target = target; ........ } public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } public synchronized void start() { ........ } @Override public void run() { if (target != null) { target.run(); } }
可以看出,Thread 也是实现了Runnable接口,用start()方法启动Therad()对象时,调用 实现Runnable接口所实现的run()方法。
这个弄明白了,关于线程的启动与停止就很容易搞明白了。
二、线程的启动与停止
(1)启动一个线程必须调用Thread的start()方法,如果线程类继承Thread,则可以直接调用对象的start()方法启动,如果线程类实现的是Runnable接口,则需要将对象封装成Thread,再调用封装后对象的start();
(2)JAVA只允许单继承,因此当一个类继承另一个类,又想成为一个线程时,就不能继承Thread类了,只能通过实现Runnable接口来实现。
(3)线程的停止方法有
[1] 让线程的run()方法执行完,线程自然结束。(这种方法最好)
[2]通过轮询和共享标志位的方法来结束线程,例如while(flag){},flag的初始值设为真,当需要结束时,将flag的值设为false。(这种方法也不很 好,因为如果while(flag){}方法阻塞了,则flag会失效)
[3]使用interrupt(),而程序会丢出InterruptedException例外,因而使得执行绪离开run()方法。
[4]使用stop()方法终止线程,在上一个版本的JDK中,sun公司已经说明:Thread.stop, Thread.suspend and Thread.resume都已经不被推荐使用。因为会导致程序出现不可想象的状况。
小例子如下:
1 package com.beiyan.thread; 2 3 public class ThreadStartStop { 4 5 public static void main(String[] args) throws InterruptedException { 6 ThreadStartStop startStop = new ThreadStartStop(); 7 ThreadA a = startStop.new ThreadA();// ThreadA和ThreadB 8 ThreadB tb = startStop.new ThreadB(); // 是内部类,所以要先实例化主类,才能new两个线程对象 9 a.start(); // ThreadA extends Thread 可以直接启动, 10 Thread b = new Thread(tb);// ThreadB implements Runnable 11 b.start(); // 必须要先实例化一个Thread 对象,在Thread 对象里面启动 12 13 Thread.sleep(3000);// 主线程休息3秒 14 a.StopThread(); 15 System.out.println("---------用 interupt()终止线程----------"); 16 ThreadC c = startStop.new ThreadC(); 17 c.start(); 18 c.interrupt(); 19 20 } 21 22 class ThreadA extends Thread { 23 private boolean running = false; 24 25 // 重写Start方法 26 public void start() { 27 System.out.println("ThreadA启动"); 28 this.running = true; 29 super.start(); 30 } 31 32 // run方法 33 public void run() { 34 35 try { 36 while (running) { 37 Thread.sleep(4000); 38 System.out.println("ThreadA 执行中。。。"); 39 40 // wait(); 41 } 42 } catch (InterruptedException e) { 43 System.out.println("ThreadA end!"); 44 } 45 } 46 47 // 自定义线程停止方法 48 public void StopThread() { 49 System.out.println("ThreadA Stop"); 50 running = false; 51 } 52 } 53 54 class ThreadB implements Runnable { 55 56 @Override 57 public void run() { 58 59 System.out.println("ThreadB执行中。。。"); 60 try { 61 Thread.sleep(1000); 62 } catch (InterruptedException e) { 63 System.out.println("ThreadB Stop"); 64 } 65 System.out.println("ThreadB 结束"); 66 } 67 68 } 69 70 class ThreadC extends Thread { 71 72 // 重写Start方法 73 public void start() { 74 System.out.println("ThreadC启动"); 75 super.start(); 76 } 77 78 // run方法 79 public void run() { 80 81 try { 82 while (!Thread.interrupted()) { 83 Thread.sleep(3000); 84 System.out.println("ThreadC 执行中。。。"); 85 // wait(); 86 } 87 } catch (InterruptedException e) { 88 System.out.println("ThreadC end!"); 89 } 90 } 91 92 } 93 }
运行结果如下:
ThreadA启动
ThreadB执行中。。。
ThreadB 结束
ThreadA Stop
---------用 interupt()终止线程----------
ThreadC启动
ThreadA 执行中。。。
可以看出:
1、ThreadA是采用 flage标志位的方式终止线程的,最后一句结果是:ThreadA 执行中。。。当线程终止后,线程A还在继续执行,可见采用while(flag){}方法终止线程时,若线程出现Sleep()或者wait()阻塞时,并不能很好的结束线程。
2、ThreadC是采用interrupt()方法终止线程的。程序会丢出InterruptedException异常,因而使得执行绪离开run()方法。
3、线程ThreadA 和ThreadB 的启动方式。
三、线程的互斥
在操作系统中,有临界区,某些时刻只允许一个进程访问临界区。在Java中,对象也有临界区。在某些时候只允许一个线程访问临界区,称为线程的互斥。
在类的方法中使用synchronized()关键字可以保证同一时刻只有一个线程进入该方法。
1 public class Synchronized { 2 3 private int money = 1000; 4 private int money2 = 1000; 5 6 // 用synchronized关键字定义一个同步的方法。 7 public synchronized void add(int p) { 8 System.out.println("已有money: " + money + " 存入: " + p + " 总共:" + (money + p)); 9 money += p; 10 } 11 12 // 没有使用synchronized 13 public void add2(int p) { 14 System.out.println("已有money2: " + money2 + " 存入: " + p + " 总共:" + (money2 + p)); 15 money2 += p; 16 } 17 18 public static void main(String[] args) { 19 Synchronized sync = new Synchronized(); 20 // 没有使用synchronized,进行线程互斥 21 ThreadTest2 p1 = new ThreadTest2(sync, 300); 22 ThreadTest2 p2 = new ThreadTest2(sync, 200); 23 ThreadTest2 p3 = new ThreadTest2(sync, 100); 24 p1.start(); 25 p2.start(); 26 p3.start(); 27 // 使用synchronized,进行线程互斥 28 ThreadTest t1 = new ThreadTest(sync, 300); 29 ThreadTest t2 = new ThreadTest(sync, 200); 30 ThreadTest t3 = new ThreadTest(sync, 100); 31 t1.start(); 32 t2.start(); 33 t3.start(); 34 } 35 36 } 37 38 // 定义一个线程类,用于增加money 39 class ThreadTest extends Thread { 40 private Synchronized syn = null; 41 private int moneyAdd = 0; 42 43 public ThreadTest(Synchronized synchronized1, int money) 44 { 45 this.syn = synchronized1; 46 this.moneyAdd = money; 47 } 48 49 public void run() { 50 syn.add(moneyAdd); 51 } 52 } 53 54 // 定义一个线程类,用于增加money2 55 class ThreadTest2 extends Thread { 56 private Synchronized syn = null; 57 private int moneyAdd = 0; 58 59 public ThreadTest2(Synchronized synchronized1, int money) 60 { 61 this.syn = synchronized1; 62 this.moneyAdd = money; 63 } 64 65 public void run() { 66 syn.add2(moneyAdd); 67 } 68 }
运行结果如下:
已有money2: 1000 存入: 300 总共:1300
已有money2: 1000 存入: 200 总共:1200
已有money2: 1000 存入: 100 总共:1100
已有money: 1000 存入: 300 总共:1300
已有money: 1300 存入: 200 总共:1500
已有money: 1500 存入: 100 总共:1600
money2是没有使用synchronized进行同步处理的。
money是使用synchronized进行同步处理的。
可见使用synchronized同步机制实现线程的互斥运行,从而避免出现错误。
四、线程协作
有时候多个线程需要协作,线程A往缓冲区里面写数据,线程B从缓冲区里读取数据,当缓冲区满时,线程A必须等待;当缓冲区为空时,线程B必须等待。
在synchronized代码块里使用wait()方法,能够使当前线程进入等待状态,并释放当前线程用于的对象锁。
在synchronized代码块里使用notify()或者notifyall()方法可以使当前线程释放对象锁,并唤醒其他正在等待该对象锁的线程。当有多个线程在等待该对象锁时,由Java虚拟机决定被唤醒的进程。
例子如下:
1 package com.beiyan.thread; 2 3 import java.util.Vector; 4 5 public class WaitNotify { 6 // 内部类,实现task的减少和增加 7 class ThreadTask extends Thread { 8 private Vector task = new Vector(); 9 10 public void run() { 11 while (!Thread.interrupted()) { 12 synchronized (this) { 13 while (task.size() == 0) { 14 System.out.println("资源紧张,进入等待。。。"); 15 try { 16 wait(); 17 System.out.println("等待结束。。。"); 18 } catch (InterruptedException e) { 19 // TODO Auto-generated catch block 20 e.printStackTrace(); 21 } 22 } 23 try { 24 sleep(1000); 25 System.out.println("做完了一个任务"); 26 task.remove(task.size() - 1); 27 } catch (InterruptedException e) { 28 // TODO Auto-generated catch block 29 e.printStackTrace(); 30 } 31 } 32 } 33 } 34 35 // 增加task 供使用 36 public void addTask(String str) { 37 synchronized (this) { 38 task.add(str); 39 System.out.println("增加了一个任务"); 40 notify(); 41 } 42 } 43 } 44 45 public static void main(String[] args) throws InterruptedException { 46 WaitNotify notify = new WaitNotify(); 47 ThreadTask task = notify.new ThreadTask(); 48 task.start(); 49 Thread.sleep(1000); 50 for (int i = 0; i < 3; i++) { 51 task.addTask("str" + i); 52 Thread.sleep(1000); 53 } 54 55 } 56 }
从例子可以看出,task线程中,调用start(),进而执行run()方法的内容,让没有资源(工作任务)时,task线程进入wait()状态,释放对线程对象的锁,这时main线程调用task的addTask()方法,使任务增加一个,调用notify()方法并通知线程,有资源可以继续执行。
五、线程join
有时候一个线程需要等待其他线程执行完毕后再进行操作,这是一种等待关系,也是一种协作。
在当前线程中调用线程A的join(参数 b),表示:当前线程必须等待线程A执行长为 b 的时间再能继续执行。
若参数为空,则表示 线程A执行完之后,当前线程才能继续执行。
例子如下:
1 package com.beiyan.thread; 2 3 public class Join { 4 class ThreadA extends Thread { 5 private int id; 6 private String name; 7 8 public ThreadA(int a, String b) 9 { 10 this.id = a; 11 this.name = b; 12 } 13 14 public void run() { 15 int i = 0; 16 while (i < 5) { 17 System.out.println(this.name + " 执行中。。。"); 18 try { 19 Thread.sleep(500); 20 } catch (InterruptedException e) { 21 e.printStackTrace(); 22 23 } 24 i++; 25 } 26 } 27 } 28 29 public static void main(String[] args) throws InterruptedException { 30 Join join = new Join(); 31 ThreadA a = join.new ThreadA(1, "线程A"); 32 a.start(); 33 a.join(); // 线程A并入主线程,线程A执行完毕后,下面的for语句,才开始执行 34 35 for (int i = 0; i < 5; i++) { 36 System.out.println("Main Thread....."); 37 } 38 } 39 }
执行结果如下:
线程A 执行中。。。
线程A 执行中。。。
线程A 执行中。。。
线程A 执行中。。。
线程A 执行中。。。
Main Thread.....
Main Thread.....
Main Thread.....
Main Thread.....
Main Thread.....
线程A join()主线程后,成为主线程的一部分,合并成一个线程,所以按顺序执行。
六、Object实现生产者、消费者问题
生产者,消费者问题是一个多线程协作问题,生产者负责生产产品,并存入仓库,消费者从仓库中获得产品并消费。
例子如下:
1 package com.beiyan.thread; 2 3 import java.util.LinkedList; 4 5 public class ProdutConsume extends Thread { 6 7 private LinkedList<Object> wareHouse = new LinkedList<Object>(); 8 private final int MAX = 4; 9 10 // 生产者内部类 11 class Produt extends Thread { 12 public void run() { 13 14 while (!Thread.interrupted()) { 15 synchronized (wareHouse) { 16 try { 17 while (wareHouse.size() == MAX) { 18 System.out.println("仓库已满,正在等待消费"); 19 wareHouse.wait(); 20 21 } 22 Object obj = new Object(); 23 if (wareHouse.add(obj)) { 24 System.out.println("生产一个新产品"); 25 Thread.sleep((long) (Math.random() * 1000)); 26 wareHouse.notify(); 27 } 28 } catch (InterruptedException e) { 29 System.out.println("Producter Stop!!"); 30 } 31 32 } 33 } 34 35 } 36 } 37 38 // 消费者内部类 39 class Comsumer extends Thread { 40 41 public void run() { 42 43 while (!Thread.interrupted()) { 44 synchronized (wareHouse) { 45 try { 46 while (wareHouse.size() == 0) { 47 System.out.println("仓库为空,正在等待"); 48 wareHouse.wait(); 49 } 50 wareHouse.removeLast(); 51 System.out.println("消费一个新产品"); 52 Thread.sleep((long) (Math.random() * 500)); 53 wareHouse.notify(); 54 } catch (InterruptedException e) { 55 System.out.println("Comsumer Stop!!"); 56 } 57 58 } 59 } 60 61 } 62 } 63 64 public static void main(String[] args) throws InterruptedException { 65 ProdutConsume ps = new ProdutConsume(); 66 Produt produt = ps.new Produt(); 67 Comsumer comsumer = ps.new Comsumer(); 68 produt.start(); 69 comsumer.start(); 70 71 } 72 }
七、Lock类 实现生产者、消费者问题
java JDK1.5版本后,出现了Condition。它用来替代传统的Object的wait()、notify()实现线程间的协作。使用Condition的await()、signal()这种方式实现线程间协作,比传统方式更加安全和高效。
synchronized却只有一把锁,lock类可以有多把锁,比较灵活。对于生产者消费者问题,可以对仓库的满和空各设一把锁。
举个例子:
当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
但是采用synchronized关键字来实现同步的话,就会导致一个问题:
如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。
代码:
1 package com.beiyan.thread; 2 3 import java.awt.Container; 4 import java.util.LinkedList; 5 import java.util.concurrent.TimeUnit; 6 import java.util.concurrent.locks.Condition; 7 import java.util.concurrent.locks.Lock; 8 import java.util.concurrent.locks.ReentrantLock; 9 10 public class ProductConsumer2 extends Thread { 11 12 private LinkedList<Object> wareHouse = new LinkedList<Object>(); 13 private final int MAX = 4; 14 private final Lock lock = new ReentrantLock(); 15 private Condition full = lock.newCondition(); 16 private Condition empty = lock.newCondition(); 17 18 // 生产者内部类 19 class Produt extends Thread { 20 public void run() { 21 22 while (!Thread.interrupted()) { 23 lock.lock(); 24 try { 25 while (wareHouse.size() == MAX) { 26 System.out.println("仓库已满,正在等待消费"); 27 full.await(); 28 } 29 Object obj = new Object(); 30 if (wareHouse.add(obj)) { 31 System.out.println("生产一个新产品"); 32 Thread.sleep((long) (Math.random() * 1000)); 33 empty.signal(); 34 } 35 } catch (InterruptedException e) { 36 System.out.println("Producter Stop!!"); 37 } 38 lock.unlock(); 39 40 } 41 } 42 43 } 44 45 // 消费者内部类 46 class Comsumer extends Thread { 47 48 public void run() { 49 while (!Thread.interrupted()) { 50 lock.lock(); 51 try { 52 while (wareHouse.size() == 0) { 53 System.out.println("仓库为空,正在等待"); 54 empty.await(); 55 } 56 wareHouse.removeLast(); 57 System.out.println("消费一个新产品"); 58 Thread.sleep((long) (Math.random() * 500)); 59 full.signal(); 60 } catch (InterruptedException e) { 61 System.out.println("Comsumer Stop!!"); 62 } 63 lock.unlock(); 64 65 } 66 67 } 68 } 69 70 public static void main(String[] args) throws InterruptedException { 71 ProductConsumer2 ps = new ProductConsumer2(); 72 Produt produt = ps.new Produt(); 73 Comsumer comsumer = ps.new Comsumer(); 74 produt.start(); 75 comsumer.start(); 76 77 } 78 }
运行结果如下:
生产一个新产品
生产一个新产品
生产一个新产品
生产一个新产品
仓库已满,正在等待消费
消费一个新产品
消费一个新产品
消费一个新产品
消费一个新产品
仓库为空,正在等待
生产一个新产品
生产一个新产品
生产一个新产品
生产一个新产品
仓库已满,正在等待消费
消费一个新产品
消费一个新产品
消费一个新产品
消费一个新产品
仓库为空,正在等待
生产一个新产品
生产一个新产品
八、线程优先级
与操作系统中的进程一样,Java线程也有优先级,在同等情况下,对于两个同时启动的线程,优先级高的先执行。
Java线程优先级分为10个级别,数字越大,级别越高,默认为5;
Thread 的setPriority()可以设置线程的优先级。
代码如下:
1 package com.beiyan.thread; 2 3 public class Priority { 4 public static void main(String[] args) { 5 Priority priority = new Priority(); 6 ThreadPriority t1 = priority.new ThreadPriority(1); 7 ThreadPriority t2 = priority.new ThreadPriority(2); 8 System.out.println("线程t2的默认优先级: " + t2.getPriority()); 9 t1.setPriority(6); 10 t1.start(); 11 t2.start(); 12 } 13 14 class ThreadPriority extends Thread { 15 private int id; 16 17 public ThreadPriority(int a) 18 { 19 this.id = a; 20 } 21 22 public void run() { 23 for (int i = 1; i < 5; i++) { 24 System.out.println("Thread " + id + " 正在执行。。。。"); 25 try { 26 Thread.sleep((long) (Math.random() * 2000)); 27 } catch (InterruptedException e) { 28 System.out.println("Thread " + id + " interrupt"); 29 } 30 } 31 } 32 } 33 }
执行结果:
线程t2的默认优先级: 5
Thread 1 正在执行。。。。
Thread 2 正在执行。。。。
Thread 1 正在执行。。。。
Thread 1 正在执行。。。。
Thread 1 正在执行。。。。
Thread 2 正在执行。。。。
Thread 2 正在执行。。。。
Thread 2 正在执行。。。。
线程1的优先级设置为6时,级别比线程2高,故线程1比线程2优先执行。
九、守护线程
有一种线程叫做守护线程(Daemon线程),如Java虚拟机的垃圾回收线程,它们在后台运行,为非守护线程提供服务。
Thread的setDaemon实例方法设置线程是否为守护线程,参数为true表示该线程为守护线程。
线程运行后,setDaemon实例方法无效,即必须在调用start()方法之前调用setDaemon方法。
程序中启动的线程默认为非守护线程,但在守护线程中启动的线程都是守护线程。
当程序中,所有的非守护线程都结束时,守护线程自动结束。
例子如下:
1 package com.beiyan.thread; 2 3 public class Daemon { 4 5 public static void main(String[] args) { 6 7 Thread t1 = new MyCommon(); 8 9 Thread t2 = new Thread(new MyDaemon()); 10 11 t2.setDaemon(true); // 设置为守护线程 12 13 t2.start(); 14 15 t1.start(); 16 } 17 } 18 19 //普通user线程 20 class MyCommon extends Thread { 21 public void run() { 22 23 for (int i = 0; i < 4; i++) { 24 25 System.out.println("用户第" + i + "次执行!"); 26 27 try { 28 29 Thread.sleep(100); 30 } 31 32 catch (InterruptedException e) { 33 34 e.printStackTrace(); 35 } 36 } 37 38 } 39 } 40 41 // 守护线程,守护线程要设置为无限循环,或者运行时间长一点,不然用户线程(非守护)还没结束,守护线程已经结束。 42 class MyDaemon implements Runnable { 43 44 public void run() { 45 46 int i = 0; 47 try { 48 while (true) { 49 50 System.out.println("守护线程第" + i + "次执行!"); 51 52 Thread.sleep(100); 53 54 i++; 55 56 } 57 } catch ( 58 59 InterruptedException e) { 60 61 e.printStackTrace(); 62 63 } finally { 64 System.out.println("守护线程结束");// 验证守护线程的finally是否执行 65 } 66 67 } 68 }
运行结果如下:
用户第0次执行!
守护线程第0次执行!
守护线程第1次执行!
用户第1次执行!
守护线程第2次执行!
用户第2次执行!
守护线程第3次执行!
用户第3次执行!
守护线程第4次执行!
可以看出,在普通用户线程结束运行后,虽然守护线程没执行完,但也立即结束。
并且,守护线程finally()部分的代码是不执行的。
学习异常时说,finall()最后必须执行的。但对于守护线程来说不适用。
十 、线程池
在进行socket网络编程时,往往需要多个服务器都能连接到服务器,服务器为每个连接到服务器的用户开你一个线程进行处理,假如连接到服务器上的用户非常多,那么服务器的线程就会越用越少,最好的解决办法是使用线程池。
服务器启动时,分配何时数量的线程到一个线程池内,当有用户连上服务器之后,服务器从线程池中分配一个线程给用户,当线程池的线程分配完毕后,其他新连接的用户处于等待状态。
当有用户与服务器断开连接后,服务器重新分配线程。
具体例子放到下一篇Socket网络编程总结里面吧。
最后,激励下自己:
学习编程切忌浮躁,天亮后,面包会有的,牛奶会有的。