今天准备总结一下关于Java 线程的问题,提到线程很容易与进程混淆,从计算机操作系统的发展来看,经历了这样的两个阶段:
单进程处理:最早以前的DOS 系统就属于单进程处理,即:在同一个时间段上只能有一个程序在执行,所以在DOS 系统中只要有病毒的出现,则立刻会有反映;
多进程处理:我们现在使用的Windows 操作系统就是典型的一个多线程,所以,如果在windows 中出现病毒了,则系统照样可以使用,通过Ctrl+Shift+delete 可以查看windows 系统的具体进程情况;
那么对于资源来讲,所有的IO 设备、CPU 等等只有一个,那么对于多线程的处理来讲,在同一个时间段上会有多个程序运行,但是在同一个时间点 上只能有一个程序运行。所以我们可以发现线程是在进程的基础上进一步的划分,我们可以举个这样的例子,Eclipse 中对Java 的关键字的检查,是在Eclipse 整个程序运行中检测运行的。因此进程中止了,线程也随之中止。但是线程中止了,进程可能依然会执行。我们可以这样理解,进程是一个静态的概念,一个任务或者说一个程序,一个进程里有一个主线程。
下面我们来看看Java 中对线程处理机制的支持,在Java 语言中对线程的实现有两种方恨死:一个是继承Thread 类,另一个是实现Runnable 接口。下面我们来分别来看看这两种实现方式:
继承Thread 类:
一个java 类只要继承了Thread 类 ,同时覆写了本类中的run() 方法,则就可以实现Java 中的多线程操作了。
MyThread.java :
01 | package com.iflytek.thread; |
04 | * @author xudongwang 2012-1-1 |
06 | * Email:xdwangiflytek@gmail.com |
08 | public class MyThread extends Thread { |
12 | public MyThread(String name) { |
17 | for ( int i = 0 ; i < 10 ; i++) { |
18 | System.out.println( "Thread运行:" + name + ",i=" + i); |
下面我们来实现上面的多线程操作类,MyThreadTest.java :
01 | package com.iflytek.thread; |
04 | * @author xudongwang 2012-1-1 |
06 | * Email:xdwangiflytek@gmail.com |
08 | public class MyThreadTest { |
10 | public static void main(String[] args) { |
11 | MyThread thread1 = new MyThread( "线程A" ); |
12 | MyThread thread2 = new MyThread( "线程B" ); |
通过运行结果,我们可以发现其执行的结果非常有规律,先执行完第一个对象,再执行完第二个对象的,即没有实现交互的现象;
通过JDK 文档可以发现,一旦我们调用Start() 方法,则会通过JVM 找到run() 方法。所以当我们将上面调用的run() 方法改为start() 方法:
01 | package com.iflytek.thread; |
04 | * @author xudongwang 2012-1-1 |
06 | * Email:xdwangiflytek@gmail.com |
08 | public class MyThreadTest { |
10 | public static void main(String[] args) { |
11 | MyThread thread1 = new MyThread( "线程A" ); |
12 | MyThread thread2 = new MyThread( "线程B" ); |
14 | thread1.start(); // 调用线程 |
这时再去执行发现结果有交互的现象,所以这也值得我们思考为什么非要使用start() 方法启动多线程呢?通过查看Java 源码:
01 | public synchronized void start() { //定义start方法 |
03 | * This method is not invoked for the main method thread or "system" |
04 | * group threads created/set up by the VM. Any new functionality added |
05 | * to this method in the future may have to also be added to the VM. |
07 | * A zero status value corresponds to state "NEW". |
09 | if (threadStatus != 0 || this != me) //判断线程是否已经启动 |
10 | throw new IllegalThreadStateException(); |
13 | if (stopBeforeStart) { |
14 | stop0(throwableFromStop); |
18 | private native void start0(); //使用native关键字声明的方法没有方法体 |
说明:操作系统有很多种,Windows 、Linux 、UNIX ,既然多线程操作中要进行CPU 资源的强占,也就是说要等待CPU 调度,那么这些调度的操作是由各个操作系统的底层实现的,所以在Java 程序中根本就没法实现,那么此时Java 的设计者定义了native 关键字,使用此关键字表示可以调用操作系统的底层函数,那么这样的技术又称为JNI 技术(Java Native Interface ),而且,此方法在执行的时候将调用run 方法完成,由系统默认调用的。
下面我们看看线程的状态:
实现Runnable 接口:
因为我们知道继承的单一继承的局限性,所以我们在开发中一个多线程的操作类很少去使用Thread 类完成,而是通过Runnable 接口完成。
查看源码发现Runnable 的定义:
1 | public interface Runnable { |
2 | public abstract void run(); |
所以一个类只要实现了此接口,并覆写run() 方法
01 | package com.iflytek.thread; |
05 | * @author xudongwang 2012-1-1 |
07 | * Email:xdwangiflytek@gmail.com |
09 | public class MyThreadByRunnable implements Runnable { |
13 | public MyThreadByRunnable(String name) { |
17 | public void run() { // 覆写run()方法 |
18 | for ( int i = 0 ; i < 10 ; i++) { |
19 | System.out.println( "Thread运行:" + name + ",i=" + i); |
有了多线程操作类下面我们需要启动多线程,但是在现在使用Runnable 定义的子类中并没有start() 方法,而只有Thread 类中才有,在Thread 类中存在以下的一个构造方法:
1 | public Thread(Runnable target) { |
2 | init( null , target, "Thread-" + nextThreadNum(), 0 ); |
此构造方法接受Runnable 的子类实例,也就是说现在我们可以通过Thread 类来启动Runnable 实现的多线程。
01 | package com.iflytek.thread; |
05 | * @author xudongwang 2012-1-1 |
07 | * Email:xdwangiflytek@gmail.com |
09 | public class MyThreadByRunnableTest { |
10 | public static void main(String[] args) { |
12 | MyThreadByRunnable thread1 = new MyThreadByRunnable( "线程A" ); |
13 | MyThreadByRunnable thread2 = new MyThreadByRunnable( "线程B" ); |
14 | new Thread(thread1).start(); |
15 | new Thread(thread2).start(); |
当然上面的操作代码也属于交替的运行,所以此时程序也同样实现了多线的操作;
下面我们来总结一下两种实现方式的区别及联系:
在程序的开发中只要是多线程则肯定永远以实现Runnable 接口为正统操作,因为实现Runnable 接口相比继承Thread 类有如下的好处:
1、 避免单继承的局限性,一个类可以同时实现多个接口
2、 适合于资源的共享
下面来说说关于线程的几个Demo ;
1 、两个线程访问同一个对象,ThreadSyncDemo.java :
01 | package com.iflytek.thread; |
04 | * @author xudongwang 2012-1-1 |
06 | * Email:xdwangiflytek@gmail.com |
08 | public class ThreadSyncDemo implements Runnable { |
09 | Timer timer = new Timer(); |
11 | public static void main(String[] args) { |
12 | ThreadSyncDemo threadSyncDemo = new ThreadSyncDemo(); |
13 | Thread thread1 = new Thread(threadSyncDemo); |
14 | Thread thread2 = new Thread(threadSyncDemo); |
15 | thread1.setName( "t1" ); // 修改线程名称 |
16 | thread2.setName( "t2" ); |
23 | timer.add(Thread.currentThread().getName()); |
28 | private static int num = 0 ; |
30 | public void add(String name) { |
33 | // 第一个线程执行到 这里时被休眠了,这是num为1,而第二个线程重新来执行时num为2,并休眠,而此时第一个线程启动了 |
35 | } catch (InterruptedException e) { |
37 | System.out.println(name + ",你是第" + num + "个使用timer的线程" ); |
运行结果:
t1, 你是第 2 个使用 timer 的线程 t2, 你是第 2 个使用timer 的线程 |
而如果程序这样改动一下,ThreadSyncDemo02.java :
01 | package com.iflytek.thread; |
04 | * @author xudongwang 2012-1-1 |
06 | * Email:xdwangiflytek@gmail.com |
08 | public class ThreadSyncDemo02 implements Runnable { |
09 | Timer02 timer = new Timer02(); |
11 | public static void main(String[] args) { |
12 | ThreadSyncDemo02 threadSyncDemo = new ThreadSyncDemo02(); |
13 | Thread thread1 = new Thread(threadSyncDemo); |
14 | Thread thread2 = new Thread(threadSyncDemo); |
15 | thread1.setName( "t1" ); // 修改线程名称 |
16 | thread2.setName( "t2" ); |
23 | timer.add(Thread.currentThread().getName()); |
28 | private static int num = 0 ; |
30 | public synchronized void add(String name) { // 执行这个方法的过程之中,当前对象被锁定 |
31 | synchronized ( this ) { // 这样的话,在{}中的线程执行的过程中不会被另一个线程打断,也就是说{}只能有一个线程 |
34 | Thread.sleep( 1 ); // 第一个线程执行到 |
35 | // 这里时被休眠了,这是num为1,而第二个线程重新来执行时num为2,并休眠,而此时第一个线程启动了 |
36 | } catch (InterruptedException e) { |
38 | System.out.println(name + ",你是第" + num + "个使用timer的线程" ); |
运行结果:
t1, 你是第 1 个使用 timer 的线程 t2, 你是第 2 个使用timer 的线程 |
2 、死锁,ThreadDieDemo.java :
01 | package com.iflytek.thread; |
04 | * @author xudongwang 2012-1-1 |
06 | * Email:xdwangiflytek@gmail.com |
08 | public class ThreadDieDemo { |
10 | public static void main(String[] args) { |
11 | DeadLock lock1 = new DeadLock(); |
12 | DeadLock lock2 = new DeadLock(); |
15 | Thread thread1 = new Thread(lock1); |
16 | Thread thread2 = new Thread(lock2); |
22 | class DeadLock implements Runnable { |
24 | static Object o1 = new Object(); |
25 | static Object o2 = new Object(); |
29 | System.out.println( "flag = " + flag); |
34 | } catch (InterruptedException e) { |
38 | System.out.println( "o2" ); |
46 | } catch (InterruptedException e) { |
50 | System.out.println( "o1" ); |
3 、生产者和消费者问题:
首先简单说明一下sleep 、wait 、notify 的区别:
sleep :sleep 是在Thread 中的,同时在sleep 的时候锁还在;
wait :wait 必须是在锁住对象时才能wait ,同时在wait 的时候,锁就不在归那个对象所有了,而在其方法定义在Object 中,它是让进入到此锁住对象的线程wait ;
notify :与wait 相对应,叫醒一个现在正在wait 在我这个对象上的线程,谁现在正在我这个对象上等待,我就叫醒这个线程让他继续执行,他也是Object 类中的方法;
ProductCustomerDemo.java :
001 | package com.iflytek.thread; |
004 | * @author xudongwang 2012-1-1 |
006 | * Email:xdwangiflytek@gmail.com |
008 | public class ProductCustomerDemo { |
009 | public static void main(String[] args) { |
010 | WoToStack woToStack = new WoToStack(); |
011 | Product product = new Product(woToStack); |
012 | Customer customer = new Customer(woToStack); |
013 | new Thread(product).start(); |
014 | new Thread(customer).start(); |
021 | * @author xudongwang 2012-1-1 |
023 | * Email:xdwangiflytek@gmail.com |
028 | public WoTo( int id) { |
033 | public String toString() { |
034 | return "WoTo [id=" + id + "]" ; |
040 | WoTo[] arrayWoTo = new WoTo[ 10 ]; // 这里限制一下,框子最多装10个WoTo |
047 | public synchronized void push(WoTo wt) { |
048 | // 这里用while是因为如果被打断还要执行判断,而如果是if则会直接进入下一个语句 |
049 | while (index == arrayWoTo.length) { |
052 | } catch (InterruptedException e) { |
057 | arrayWoTo[index] = wt; |
066 | public synchronized WoTo pop() { |
070 | } catch (InterruptedException e) { |
076 | return arrayWoTo[index]; |
083 | * @author xudongwang 2012-1-1 |
085 | * Email:xdwangiflytek@gmail.com |
087 | class Product implements Runnable { |
088 | // 首先生产者需要知道生产WoTo放在哪里 |
089 | WoToStack stack = null ; |
091 | public Product(WoToStack stack) { |
097 | for ( int i = 0 ; i < 20 ; i++) { // 这里我们限制一下每一个生产者可以生产20个WoTo |
098 | WoTo woTo = new WoTo(i); |
100 | System.out.println( "生产者生产了 :" + woTo); |
102 | Thread.sleep(( int ) Math.random() * 200 ); |
103 | } catch (InterruptedException e) { |
110 | class Customer implements Runnable { |
111 | WoToStack stack = null ; |
113 | public Customer(WoToStack stack) { |
119 | for ( int i = 0 ; i < 20 ; i++) { // 这里我们也限制一下每一个小费者可以消费20个WoTo |
120 | WoTo woTo = stack.pop(); |
121 | System.out.println( "消费者消费了 :" + woTo); |
123 | Thread.sleep(( int ) Math.random() * 1000 ); |
124 | } catch (InterruptedException e) { |
4 、卖票问题(Runnable 资源共享):
MyThread.java:
01 | package com.iflytek.maipiao; |
04 | * @author xudongwang 2012-1-1 |
06 | * Email:xdwangiflytek@gmail.com |
08 | public class MyThread extends Thread { |
10 | private int ticket = 5 ; // 一共5张票 |
13 | for ( int i = 0 ; i < 50 ; i++) { |
15 | System.out.println( "卖票:ticket = " + this .ticket--); |
下面建三个线程对象,同时卖票,ThreadTicket.java :
01 | package com.iflytek.maipiao; |
04 | * @author xudongwang 2012-1-1 |
06 | * Email:xdwangiflytek@gmail.com |
08 | public class ThreadTicket { |
10 | public static void main(String[] args) { |
11 | MyThread thread1 = new MyThread(); |
12 | MyThread thread2 = new MyThread(); |
13 | MyThread thread3 = new MyThread(); |
运行发现一共卖了15 张票,但是实际上只有5 张票,所以证明每一个线程都卖自己的票,这样就没有达到资源共享的目的。
其实我们使用Runnable 接口的话,则就可以实现资源的共享:
MyThreadByRunnable.java :
01 | package com.iflytek.maipiao; |
04 | * @author xudongwang 2012-1-1 |
06 | * Email:xdwangiflytek@gmail.com |
08 | public class MyThreadByRunnable implements Runnable { |
10 | private int ticket = 5 ; // 一共5张票 |
13 | for ( int i = 0 ; i < 50 ; i++) { |
15 | System.out.println( "卖票:ticket = " + this .ticket--); |
同样,我们再弄一个多线程进行卖票的操作,RunnableTicket.Java :
01 | package com.iflytek.maipiao; |
04 | * @author xudongwang 2012-1-1 |
06 | * Email:xdwangiflytek@gmail.com |
08 | public class RunnableTicket { |
10 | public static void main(String[] args) { |
11 | MyThreadByRunnable threadByRunnable = new MyThreadByRunnable(); |
12 | new Thread(threadByRunnable).start(); |
13 | new Thread(threadByRunnable).start(); |
14 | new Thread(threadByRunnable).start(); |
虽然现在程序中有三个线程,但是从运行结果上看,三个线程一共卖出了5 张票,也就是说使用Runnable 实现的多线程可以达到资源共享的目的。
实际上,Runnable 接口和Thread 类之间还是存在联系的
1 | Public class Thread implements Runnable { |
发现Thread 类也是Runnable 接口的子类。
在实际的开发中比如说发多个邮件提醒等都会用到线程的,所以线程还是很重要的;
转自:http://xdwangiflytek.iteye.com/blog/1333128