1、作用:使java应用程序同时完成多项任务,当其一个线程被阻塞时,只有那个被阻塞的线程暂停,所有其他线程继续执行。
2、概念:一个java程序可以包含多个线程,每个线程具有部分程序功能,能与其他线程同时执行,这种能力称为多线程。
3、线程只是在系统层被实现,核心编程语言需要一个特定的编程接口来实现。在java中,创建线程的方法有两种,其一是继承Thread类,其二是实现Runnable接口。
继承Thread类实例:
class MyThread extends Thread
{
int count=1,num;static int COUNT=1;
MyThread(int num)
{
this.num=num;
System.out.println("创建线程"+num);
}
public void run()
{
while(true)
{
System.out.println("线程"+num+"统计:"+count+";总统计:"+(COUNT++));
if((++count)>6)
return;
}
}
}
public class Test
{
public static void main(String args[])
{
for(int i=0;i<5;i++)
new MyThread(i).start();
}
}
实现Runnable接口实例:
class MyThread implements Runnable
{
int count=1,num;static int COUNT=1;
MyThread(int num)
{
this.num=num;
System.out.println("创建线程"+num);
}
public void run() //覆盖run()方法
{
while(true)
{
System.out.println("线程"+num+"统计:"+count+";总统计:"+(COUNT++));
if((++count)>6)
return;
}
}
}
public class Test
{
public static void main(String args[])
{
//构造线程过程:Runnable target=new MyThread();
// 构造线程过程续:Thread myThread=new Thread(taget);
for(int i=0;i<5;i++)
new Thread(new MyThread(i)).start();//Thread的构造函数实现之
}
}
两种创建线程试方式的比较:
§实现Runnable的优点:java的单一继承机制,使用户只能采用实现Runnable方法。
§继承Thread的优点:当一个run()方法体现在继承Thread类的类中,用this指向实际控制运行的Thread实例,不需要如下控制:T hread.currentThread().jion(),而可以简单地写为:jion()。
§使用 Runnable 接口来实现多线程使得我们能够在一个类中包容所有的代码,有利于封装,它的缺点在于,我们只能使用一套代码,若想创建多个线程并使各个线程执行不同的代码,则仍必须额外创建类,如果这样的话,在大多数情况下也许还不如直接用多个类分别继承 Thread 来得紧凑。
4、线程生命周期基本状态图:
一个Thread对象在它的生命周期中会处于以下几种状态:
§新建状态(New Thread):线程已创建、实例化完毕,还未执行方法体run()以启动该线程。
§就绪状态/可运行状态(Runnable):已调用start方法,为该线程完成登记工作和分配资源工作。可以用isAlive()方法识别一个线程是否处于runnable状态,若是返回true,否则返回false。
§运行状态(Running):线程调度器为其分配了CPU时间,处于运行状态。
§阻塞/挂起状态(Wait/Block):等待某个事件发生,一旦发生就离开该状态,进入Runnable状态。通常在调用sleep或wait方法进入该状态,I/O阻塞时也可进入该状态,或发生在多线程同步访问时,线程试图锁住已被另一个线程锁住的对象。
§终止状态(Dead):run方法执行完毕,或非预期的异常发生导致run方法终止,使线程死亡,此时不可重新启动,与普通对象没有区别。
5、一个多线程的实例:
class NewThread implements Runnable
{
String name;
Thread t;
NewThread(String name)
{
this.name=name;
t=new Thread(this,name);
System.out.println("New thread:"+t);
t.start();
}
public void run()
{
try
{
for(int i=3;i>0;i--)
Thread.sleep(1000);//使一个线程暂停执行一段时间
}
catch (InterruptedException e)
{
System.out.println(name+"Interrupted");
}
System.out.println(name+" exting.");
}
}
public class Test
{
public static void main(String args[])
{
NewThread nt1=new NewThread("First");
NewThread nt2=new NewThread("Second");
NewThread nt3=new NewThread("Third");
//isAlive()方法用来测试其调用的线程是否仍在运行
System.out.println("IsAlive(First):"+nt1.t.isAlive());
System.out.println("IsAlive(Second):"+nt2.t.isAlive());
System.out.println("IsAlive(Third):"+nt3.t.isAlive());
try
{
System.out.println("Waiting for threads to finish.");
//等待调用jion()的线程直到结束语
nt1.t.join();
nt2.t.join();
nt3.t.join();
}
catch (InterruptedException e)
{
System.out.println("Main thread Interrupted");
}
//isAlive()方法用来测试其调用的线程是否仍在运行
System.out.println("IsAlive(First):"+nt1.t.isAlive());
System.out.println("IsAlive(Second):"+nt2.t.isAlive());
System.out.println("IsAlive(Third):"+nt3.t.isAlive());
System.out.println("Main thread exiting.");
}
}
运行结果:
本例子实现了主线程最后结束,方法是子线程调用join()方法,让主线程等待其结束。
6、一个故事及和这个故事有关的线程问题:
故事:一男仙一女妖,偶然邂逅,真情相生,从此缠绵一处,不误正业。仙的上司听说后,非常恼火,上告玉帝,玉帝授权给他,让他惩治这一对仙妖冤家。他一直在想如何惩治时,一日到牢中发现,女妖正用勺子喂男仙,监狱的饭菜很差,但这一对其乐融融。他冷然一笑,走出牢房,对技术员说:“你给我编写一个程序,置入他们脑子里面,让一个永远不停地喂,一个永远不停地吃。”技术员眨了一会眼睛,“这个有两个问题,一是喂完了一勺子后,要停下来去取,这要男仙去等;在男仙嘴中塞满没有咽到肚里时,举起勺子的女妖要等,所以……”“这是你的事,你自己看着办。”技术员眨了一下眼睛退下,回到办公室,偶一思索,打开电脑,写出如下程序:
class Food
{
int n;
//标志,为false不允许咬,为true允许咬
boolean blnValue=false;
synchronized int get()
{
try
{//如果没有食物,男仙等待
wait();
}
catch (InterruptedException e)
{
System.out.println("InterruptedException caught");
}
//开吃,同时告诉女妖要盛饭了
System.out.println("Got:"+n);
blnValue=false;
notify();
return n;
}
synchronized void put(int n)
{
if(blnValue)
try
{//如果还没吃完,女妖等待
wait();
}
catch (InterruptedException e)
{
System.out.println("InterruptedException caught");
}
//去取食物,同时告诉男仙
this.n=n;
blnValue=true;
System.out.println("Put: "+n);
notify();
}
}
class Immortal implements Runnable
{
Food f;
Immortal(Food f)
{
this.f=f;
new Thread(this,"Immortal").start();
}
public void run()
{
while(true)
{
f.get();
}
}
}
class Goblin implements Runnable
{
Food f;
Goblin(Food f)
{
this.f=f;
new Thread(this,"Goblin").start();
}
public void run()
{
int i=1;
while(true)
{
f.put(i++);
}
}
}
class Test
{
public static void main(String args[])
{
Food f=new Food();
new Immortal(f);
new Goblin(f);
System.out.println("问世间情为何物,叫人喂而不倦,吃而不倦:");
}
}
技术员拿着程序去见上司,上司让它运行一遍,他运行之。上司点了点头,从抽屉里拿出一叠钞票,递给他说:“这是你的特别奖金,但是我要告诉你这不是因你的程序而发——这个程序我似曾相识啊,是因为你的这句:‘问世间情为何物,叫人喂而不倦,吃而不倦。’真是妙极了,是一种无耻的幸灾乐祸者,最想吟上800遍的啊。真希望,他们你喂我吃时,围着一圈人高唱这句啊。”“你说,这段程序你似曾相识?”“是啊,我也在学JAVA啊,挺好玩的嘛,线程我刚学了没多久。”
出了上司的办公室,技术员的衣服都湿了一片,回去后想了很久,第二天辞了职。当别人问及原因,他说:“偶在这压力太大,已经无法承受。”在一片惋惜中他离开了仙界,终于一次在酒后他说:“唉,我的那个上司没有人性啊,我怕。”“去,瞧你说,仙哪有人性,仙有仙品。”“那他是没有仙品了,反正是一种该有的东西他没有。”
7、百兽之王大宴宾客及线程优先级问题
百兽之王,偶逢佳运,得一至宝,欣喜若狂,于是大宴宾客,以示庆贺。设宴当日,高朋满座,良友如云,大家举杯相碰,其声清脆。百兽之王听之,捻须而笑。忽然一迎宾者慌张跑来,“报告大王,不好了,一群黑压压的飞虫正往这边飞来。”“今日来者均为客,奏乐欢迎。”“来得太多了,怕我把全部的食物拿出来,也不够招待他们的。”“奶奶个熊的,真是传说中的乞丐飞团,待我察看一下。”
百兽之王来到门外,搭眼一瞧,颜有所失,转首对狐狸说:“这个事情你去处理。”“是,大王。”狐狸答应之后,眼睛咕噜一转,计上心来,拿出手机来。
“喂,老兄,最近忙什么呢?”
“鼓捣JAVA啊。”
“功力有大增吧?”
“还好了,嘛事啊?”
“我有个问题,想请你帮个忙?”
“老朋友了,好说,不过,我有很长时间没有喝过酒了。”
“哈哈,放心吧,今天我们大王大宴宾客,款待贵宾的十瓶酒有你一瓶。”
“两瓶吧。”
“你TMD的真厉害,好吧,但你可得保证我们大王满意。”
“皆大欢喜,说吧,什么事?”狐狸便把大王给他的任务,添上自己的考虑说了一遍。很快,他的朋友,用java写出了一个程序,在大门的内嵌电脑上运行之。狐狸便对着那群飞虫说:“蜜蜂们,蝴蝶们,你们好,欢迎你们来做客。今天人这么多,这么热闹,我们不如做个游戏。就是,当我喊开始的时候,你们就从这个大门往里飞,一段时间,大门会自动关闭。然后,在大门里的,我们招待,在大门外的请便。”
随着狐狸的一声开始,蜜蜂们,蝴蝶们就匆匆往里飞。大门关上之后,根据统计的蜜蜂、蝴蝶数量,摆桌开宴。
宴毕,皆大欢喜,狐狸的朋友也得到两瓶好酒。我闻听此事,去访狐狸的这位朋友,想看一下他的那个程序。他看了我一会,在键盘上调了一通,写了一些代码,要了我的U盘,保存到上面,递给我说:“回去,好好看看这个。”
回来后,我打开那个程序段,原来是有关线程优先级问题的一个小程序:
class Animal implements Runnable
{
int count=0;
Thread t;
private volatile boolean running=true;
public Animal(int p)
{
t=new Thread(this);
t.setPriority(p);
}
public void run()
{
while(running)
{
count++;
}
}
public void stop()
{
running=false;
}
public void start()
{
t.start();
}
}
public class Test
{
public static void main(String args[])
{
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
Animal bee=new Animal(Thread.NORM_PRIORITY+2);
Animal butterfly=new Animal(4);
bee.start();
butterfly.start();
try
{
Thread.sleep(50000);
}
catch (InterruptedException e)
{
System.out.println("Main thread interrupted.");
}
bee.stop();
butterfly.stop();
try
{
bee.t.join();
butterfly.t.join();
}
catch (InterruptedException e)
{
System.out.println("InterruptedException caught");
}
System.out.println("关门>>>>>>");
System.out.println("飞来蜜蜂"+bee.count+"只.");
System.out.println("飞来蝴蝶"+butterfly.count+"只.");
}
}
8、线程同步和死锁
当两个或更多线程需要访问同一个共享资源时,须用某种方式来确保资源某一时刻只被一个线程使用,达到这个目的方式称之为同步。java中引入了互斥锁的概念,每个对象都对应于一个可称为“互斥锁”的标记,这个标记保证在任一时刻,只能有一个访问该对象。关键字synchronized用来与对象的互斥锁联系,实现同步。凡有带有synchronized关键字的方法或者代码段,系统运行时只会为之分配一个线程。
实例:
class TThread extends Thread
{
private int sum=0;
String str=new String("");
public void run()
{
while(true)
{
synchronized(str)
{
if(sum<=10)
{
try
{
Thread.sleep(10);
}
catch (Exception e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"now sum is:"+sum++);
}
}
}
}
}
public class Test
{
public static void main(String[] args)
{
TThread t=new TThread();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
注意: java语言中提供了wait()和notify()两个方法,这两个方法不能被重载,并且只能在同步方法中被调用。如果程序中有多个线程竞争多个资源,可能发生死锁。当一个线程等待由另一个线程持有的锁,而后者正在等待已被第一个线程持有的锁时,就会发生死锁。Java技术不检测也不试图避免这种情况,因而保证不发生死锁是程序员的责任。一个通用的法则:决定获取锁的次序并始终遵照这个次序,按照与获取相反的次序释放锁。