作者的blog:(http://blog.matrix.org.cn/page/Kaizen)
首先要理解线程首先须要了解一些主要的东西,我们如今所使用的大多数操作系统都属于多任务,分时操作系统。正是因为这样的操作系统的出现才有了多线程这个概念。我们使用的windows,linux就属于此列。
什么是分时操作系统呢。通俗一点与就是能够同一时间运行多个程序的操作系统,在自己的电脑上面。你是不是一边听歌,一边聊天还一边看网页呢?但实际上。并不上cpu在同一时候运行这些程序,cpu仅仅是将时间分割为时间片,然后将时间片分配给这些程序。获得时间片的程序開始运行,不等运行完毕,下个程序又获得时间片開始运行,这样多个程序轮流运行一段时间,因为如今cpu的快速计算能力,给人的感觉就像是多个程序在同一时候运行一样。
一般能够在同一时间内运行多个程序的操作系统都有进程的概念.一个进程就是一个运行中的程序,而每一个进程都有自己独立的一块内存空间,一组系统资源.在进程概念中,每一个进程的内部数据和状态都是完全独立的.因此能够想像创建并运行一个进程的系统开像是比較大的,所以线程出现了。在java中。程序通过流控制来运行程序流,程序中单个顺序的流控制称为线程,多线程则指的是在单个程序中能够同一时候运行多个不同的线程,运行不同的任务.多线程意味着一个程序的多行语句能够看上去差点儿在同一时间内同一时候运行.(你能够将前面一句话的程序换成进程,进程是程序的一次运行过程,是系统运行程序的基本单位)
线程与进程类似,是一段完毕某个特定功能的代码,是程序中单个顺序的流控制;但与进程不同的是,同类的多个线程是共享一块内存空间和一组系统资源,而线程本身的数据通常仅仅有微处理器的寄存器数据,以及一个供程序运行时使用的堆栈.所以系统在产生一个线程,或者在各个线程之间切换时,负担要比进程小的多,正因如此,线程也被称为轻负荷进程(light-weight process).一个进程中能够包括多个线程.
多任务是指在一个系统中能够同一时候运行多个程序,即有多个独立运行的任务,每一个任务相应一个进程。同进程一样,一个线程也有从创建,运行到消亡的过程,称为线程的生命周期.用线程的状态(state)表明线程处在生命周期的哪个阶段.线程有创建,可运行,运行中,堵塞,死亡五中状态.通过线程的控制与调度可使线程在这几种状态间转化每一个程序至少自己主动拥有一个线程,称为主线程.当程序载入到内存时,启动主线程.
[线程的运行机制以及调度模型]
java中多线程就是一个类或一个程序运行或管理多个线程运行任务的能力。每一个线程能够独立于其它线程而独立运行,当然也能够和其它线程协同运行,一个类控制着它的全部线程。能够决定哪个线程得到优先级,哪个线程能够訪问其它类的资源,哪个线程開始运行,哪个保持休眠状态。
以下是线程的机制图:
线程的状态表示线程正在进行的活动以及在此时间段内所能完毕的任务.线程有创建,可运行,运行中,堵塞,死亡五中状态.一个具有生命的线程,总是处于这五种状态之中的一个:
1.创建状态
使用new运算符创建一个线程后,该线程仅仅是一个空对象,系统没有分配资源,称该线程处于创建状态(new thread)
2.可运行状态
使用start()方法启动一个线程后,系统为该线程分配了除CPU外的所需资源,使该线程处于可运行状态(Runnable)
3.运行中状态
Java运行系统通过调度选中一个Runnable的线程,使其占有CPU并转为运行中状态(Running).此时,系统真正运行线程的run()方法.
4.堵塞状态
一个正在运行的线程因某种原因不能继续运行时,进入堵塞状态(Blocked)
5.死亡状态
线程结束后是死亡状态(Dead)
同一时刻假设有多个线程处于可运行状态,则他们须要排队等待CPU资源.此时每一个线程自己主动获得一个线程的优先级(priority),优先级的高低反映线程的重要或紧急程度.可运行状态的线程按优先级排队,线程调度根据优先级基础上的"先到先服务"原则.
线程调度管理器负责线程排队和CPU在线程间的分配,并由线程调度算法进行调度.当线程调度管理器选种某个线程时,该线程获得CPU资源而进入运行状态.
线程调度是先占式调度,即假设在当前线程运行过程中一个更高优先级的线程进入可运行状态,则这个线程马上被调度运行.先占式调度分为:独占式和分时方式.
独占方式下,当前运行线程将一直运行下去,直 到运行完毕或因为某种原因主动放弃CPU,或CPU被一个更高优先级的线程抢占
分时方式下,当前运行线程获得一个时间片,时间到时,即使没有运行完也要让出CPU,进入可运行状态,等待下一个时间片的调度.系统选中其它可运行状态的线程运行
分时方式的系统使每一个线程工作若干步,实现多线程同一时候运行
另外请注意以下的线程调度规则(假设有不理解,不急。往下看):
①假设两个或是两个以上的线程都改动一个对象,那么把运行改动的方法定义为被同步的(Synchronized),假设对象更新影响到仅仅读方法,那么仅仅度方法也应该定义为同步的
②假设一个线程必须等待一个对象状态发生变化,那么它应该在对象内部等待,而不是在外部等待,它能够调用一个被同步的方法,并让这种方法调用wait()
③每当一个方法改变某个对象的状态的时候。它应该调用notifyAll()方法,这给等待队列的线程提供机会来看一看运行环境是否已发生改变
④记住wait(),notify(),notifyAll()方法属于Object类。而不是Thread类,细致检查看是否每次运行wait()方法都有相应的notify()或notifyAll()方法。且它们作用与同样的对象 在java中每一个类都有一个主线程,要运行一个程序。那么这个类当中一定要有main方法。这个man方法也就是java class中的主线程。你能够自己创建线程,有两种方法,一是继承Thread类,或是实现Runnable接口。普通情况下。最好避免继承。因为java中是单根继承,假设你选用继承,那么你的类就失去了弹性,当然也不能完全否定继承Thread,该方法编写简单,能够直接操作线程,适用于单重继承情况。至于选用那一种。详细情况详细分析。
eg.继承Thread
public class MyThread_1 extends Thread
{
public void run()
{
//some code
}
}
eg.实现Runnable接口
public class MyThread_2 implements Runnable
{
public void run()
{
//some code
}
}
当使用继承创建线程。这样启动线程:
new MyThread_1().start()
当使用实现接口创建线程,这样启动线程:
new Thread(new MyThread_2()).start()
注意。事实上是创建一个线程实例,并以实现了Runnable接口的类为參数传入这个实例,当运行这个线程的时候,MyThread_2中run里面的代码将被运行。
以下是完毕的样例:
public class MyThread implements Runnable
{
public void run()
{
System.out.println("My Name is "+Thread.currentThread().getName());
}
public static void main(String[] args)
{
new Thread(new MyThread()).start();
}
}
运行后将打印出:
My Name is Thread-0
你也能够创建多个线程,像以下这样
new Thread(new MyThread()).start();
new Thread(new MyThread()).start();
new Thread(new MyThread()).start();
那么会打印出:
My Name is Thread-0
My Name is Thread-1
My Name is Thread-2
看了上面的结果,你可能会觉得线程的运行顺序是依次运行的。可是那仅仅是普通情况。千万不要用以为是线程的运行机制。影响线程运行顺序的因素有几点:首先看看前面提到的优先级别
public class MyThread implements Runnable
{
public void run()
{
System.out.println("My Name is "+Thread.currentThread().getName());
}
public static void main(String[] args)
{
Thread t1=new Thread(new MyThread());
Thread t2=new Thread(new MyThread());
Thread t3=new Thread(new MyThread());
t2.setPriority(Thread.MAX_PRIORITY);//赋予最高优先级
t1.start();
t2.start();
t3.start();
}
}
再看看结果:
My Name is Thread-1
My Name is Thread-0
My Name is Thread-2
线程的优先级分为10级。分别用1到10的整数代表,默认情况是5。上面的t2.setPriority(Thread.MAX_PRIORITY)等价与t2.setPriority(10)
然后是线程程序本身的设计,比方使用sleep,yield,join,wait等方法(详情请看JDKDocument)
public class MyThread implements Runnable
{
public void run()
{
try
{
int sleepTime=(int)(Math.random()*100);//产生随机数字,
Thread.currentThread().sleep(sleepTime);//让其休眠一定时间,时间又上面sleepTime决定
//public static void sleep(long millis)throw InterruptedException (API)
System.out.println(Thread.currentThread().getName()+" 睡了 "+sleepTime);
}catch(InterruptedException ie)//因为线程在休眠可能被中断,所以调用sleep方法的时候须要捕捉异常
{
ie.printStackTrace();
}
}
public static void main(String[] args)
{
Thread t1=new Thread(new MyThread());
Thread t2=new Thread(new MyThread());
Thread t3=new Thread(new MyThread());
t1.start();
t2.start();
t3.start();
}
}
运行后观察其输出:
Thread-0 睡了 11
Thread-2 睡了 48
Thread-1 睡了 69
上面的运行结果是随机的。再运行非常可能出现不同的结果。
因为上面我在run中增加了休眠语句,当线程休眠的时候就会让出cpu。cpu将会选择运行处于runnable状态中的其它线程。当然也可能出现这样的情况,休眠的Thread马上进入了runnable状态。cpu再次运行它。
[线程组概念]
线程是能够被组织的。java中存在线程组的概念。每一个线程都是一个线程组的成员,线程组把多个线程集成为一个对象,通过线程组能够同一时候对当中的多个线程进行操作,如启动一个线程组的全部线程等.Java的线程组由java.lang包中的Thread——Group类实现.
ThreadGroup类用来管理一组线程,包括:线程的数目,线程间的关系,线程正在运行的操作,以及线程将要启动或终止时间等.线程组还能够包括线程组.在Java的应用程序中,最高层的线程组是名位main的线程组,在main中还能够增加线程或线程组,在mian的子线程组中也能够增加线程和线程组,形成线程组和线程之间的树状继承关系。像上面创建的线程都是属于main这个线程组的。
借用上面的样例,main里面能够这样写:
public static void main(String[] args)
{
/***************************************
ThreadGroup(String name)
ThreadGroup(ThreadGroup parent, String name)
***********************************/
ThreadGroup group1=new ThreadGroup("group1");
ThreadGroup group2=new ThreadGroup(group1,"group2");
Thread t1=new Thread(group2,new MyThread());
Thread t2=new Thread(group2,new MyThread());
Thread t3=new Thread(group2,new MyThread());
t1.start();
t2.start();
t3.start();
}
线程组的嵌套,t1,t2,t3被增加group2,group2增加group1。
另外一个比較多就是关于线程同步方面的。试想这样一种情况,你有一笔存款在银行,你在一家银行为你的账户存款,而你的妻子在还有一家银行从这个账户提款,如今你有1000块在你的账户里面。你存入了1000,可是因为还有一方也在对这笔存款进行操作,人家開始运行的时候仅仅看到账户里面原来的1000元。当你的妻子提款1000元后,你妻子所在的银行就觉得你的账户里面没有钱了,而你所在的银行却觉得你还有2000元。
看看以下的样例:
class BlankSaving //储蓄账户
{
private static int money=10000;
public void add(int i)
{
money=money+i;
System.out.println("Husband 向银行存入了 [¥"+i+"]");
}
public void get(int i)
{
money=money-i;
System.out.println("Wife 向银行取走了 [¥"+i+"]");
if(money<0)
System.out.println("剩余金额不足。");
}
public int showMoney()
{
return money;
}
}
class Operater implements Runnable
{
String name;
BlankSaving bs;
public Operater(BlankSaving b,String s)
{
name=s;
bs=b;
}
public static void oper(String name,BlankSaving bs)
{
if(name.equals("husband"))
{
try
{
for(int i=0;i<10;i++)
{
Thread.currentThread().sleep((int)(Math.random()*300));
bs.add(1000);
}
}catch(InterruptedException e){}
}else
{
try
{
for(int i=0;i<10;i++)
{
Thread.currentThread().sleep((int)(Math.random()*300));
bs.get(1000);
}
}catch(InterruptedException e){}
}
}
public void run()
{
oper(name,bs);
}
}
public class BankTest
{
public static void main(String[] args)throws InterruptedException
{
BlankSaving bs=new BlankSaving();
Operater o1=new Operater(bs,"husband");
Operater o2=new Operater(bs,"wife");
Thread t1=new Thread(o1);
Thread t2=new Thread(o2);
t1.start();
t2.start();
Thread.currentThread().sleep(500);
}
}
以下是当中一次的运行结果:
---------first--------------
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
看到了吗。这可不是正确的需求,在husband还没有结束操作的时候,wife就插了进来,这样非常可能导致意外的结果。
解决的方法非常easy,就是将对数据进行操作方法声明为synchronized,当方法被该keyword声明后,也就意味着,假设这个数据被加锁,仅仅有一个对象得到这个数据的锁的时候该对象才干对这个数据进行操作。也就是当你存款的时候。这笔账户在其它地方是不能进行操作的,仅仅有你存款完毕。银行管理人员将账户解锁。其它人才干对这个账户进行操作。
改动public static void oper(String name,BlankSaving bs)为public static void oper(String name,BlankSaving bs),再看看结果:
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
当丈夫完毕操作后,妻子才開始运行操作,这样的话,对共享对象的操作就不会有问题了。
[wait and notify]
你能够利用这两个方法非常好的控制线程的运行流程,当线程调用wait方法后。线程将被挂起。直到被还有一线程唤醒(notify)或则是假设wait方法指定有时间得话,在没有被唤醒的情况下,指定时间时间过后也将自己主动被唤醒。
可是要注意一定,被唤醒并非指马上运行,而是从组塞状态变为可运行状态。其是否运行还要看cpu的调度。
事例代码:
class MyThread_1 extends Thread
{
Object lock;
public MyThread_1(Object o)
{
lock=o;
}
public void run()
{
try
{
synchronized(lock)
{
System.out.println("Enter Thread_1 and wait");
lock.wait();
System.out.println("be notified");
}
}catch(InterruptedException e){}
}
}
class MyThread_2 extends Thread
{
Object lock;
public MyThread_2(Object o)
{
lock=o;
}
public void run()
{
synchronized(lock)
{
System.out.println("Enter Thread_2 and notify");
lock.notify();
}
}
}
public class MyThread
{
public static void main(String[] args)
{
int[] in=new int[0];//notice
MyThread_1 t1=new MyThread_1(in);
MyThread_2 t2=new MyThread_2(in);
t1.start();
t2.start();
}
}
运行结果例如以下:
Enter Thread_1 and wait
Enter Thread_2 and notify
Thread_1 be notified
可能你注意到了在使用wait and notify方法得时候我使用了synchronized块来包装这两个方法。这是因为调用这两个方法的时候线程必须获得锁,也就是上面代码中的lock[],假设你不用synchronized包装这两个方法的得话。又或则锁不一是同一把,比方在MyThread_2中synchronized(lock)改为synchronized(this),那么运行这个程序的时候将会抛出java.lang.IllegalMonitorStateException运行期异常。另外wait and notify方法是Object中的。并不在Thread这个类中。最后你可能注意到了这点:int[] in=new int[0];为什么不是创建new Object而是一个0长度的数组,那是因为在java中创建一个0长度的数组来充当锁更加高效。
Thread作为java中一重要组成部分。当然还有非常多地方须要更深刻的认识,上面仅仅是对Thread的一些常识和易错问题做了一个简要的总结。若要真正的掌握java的线程,还须要自己多做总结