用Thread类创建线程
要将一段代码在一个新的线程上运行,该代码应该在一个类的run函数中,并且run函数所在的类是Thread的子类,我们要实现多线程,必须编写一个继承Thread类的子类,子类要覆盖Thread类中的run函数,在子类的run函数中调用想在新线程上运行的程序代码。
启动一个新的线程,我们不是直接调用Thread的子类对象的run方法,而是调用Thread子类对象的start(从Thread类继承的)方法。Thread类对象的starr方法将产生一个新的线程,并在该线程上运行该Thread类对象中的run方法,根据面向对象的运行时的多态性。在该线程上实际运行的是Thread子类对象的run方法。
由于线程的代码段在run方法中,那么该方法执行完成以后线程也就相应的结束了。因而我们可以通过控制run方法中的循环条件来控制线程的结束。
后台线程与联合线程
如果我们对某个线程对象在启动(调用start方法)之前调用了setDaemon(true)方法,这个线程就变成了后台线程。
对java程序而言,只要还有一个前台线程在运行,这个进程就不会结束,如果一个进程中只有后台线程在运行,这个进程就会结束。
pp.join()的作用是把pp所对应的线程合并到调用pp.join()语句的线程中。如:
class ThreadDemo1 {
public static void main(String[] args)
{
Thread tt=new TestThread();
tt.start();
int index=0;
while(true)
{
if(index==100)
try
{
tt.join();
/*执行词句后,tt这个线程就与主线程合为一个线程,只有tt中的run方法执行完后,才会向下执行*/
}
catch(Exception e)
{
e.getMessage();
}
System.out.println("main():"+Thread.currentThread().getName());
}
}
}
class TestThread extends Thread
{
public void run()
{
while(true)
{
System.out.println("run():"+Thread.currentThread().getName());
}
}
}
我们还可以设定一个时间(毫秒),经过这段时间后,合并后的线程又分成两个线程,如:tt.join(10000);
下面是一个模拟四个窗口买票程序:
class ThreadDemo1 {
public static void main(String[] args)
{
new TestThread().start();
new TestThread().start();
new TestThread().start();
new TestThread().start();
}
}
class TestThread extends Thread
{
int tickets=100;
public void run()
{
while(true)
{
if(tickets>0)
System.out.println(Thread.currentThread().getName()+"is selling ticket"+tickets--);
}
}
}
但这四个线程会卖出同一张票,即每一个线程都会卖出1到100这一百张票,而不是共同卖这一百张票,因为我们产生了四个TestThread对象,它们各自拥有100张票。因此,我们要只创建一个对象,并且产生多个线程。
若要共同卖100张票,则需要用Runnable接口来实现多线程:
class ThreadDemo1 {
public static void main(String[] args)
{
TestThread tt=new TestThread(); //产生一个对象
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();//让四个线程对象接受同一个接口对象
}
}
class TestThread implements Runnable
{
int tickets=100;
public void run()
{
while(true)
{
if(tickets>0)
System.out.println(Thread.currentThread().getName()+"is selling ticket"+tickets--);
}
}
}
使用Runnable接口创建多线程:
适合多个相同程序代码的线程去处理同一资源的情况,把虚拟cpu(线程)同程序的代码、数据有效分离,较好地体现了面向对象的设计思想。
可以避免由于java的简单继承特性带来的局限。我们经常碰到这样的一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。
当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参出传递过去,这个对象就是一个实现了Runnable接口的类的实例。
几乎所有多线程应用都可用Runnable接口方式。
线程的同步
可用同步代码块:ynchronized(对象参数){}将代码包起来,使得在一段时间内,只允许一个线程进入。其参数可以为任一个对象,因为对象有一个标识位,可以作为监视器,当一个线程进入被监视的代码段,就会修改标识位,使得其他的线程无法进入,尽管此时cpu仍会切换线程,修改标识位的这个线程如同握有一把钥匙,只有它才能使用这段代码。只有修改标识位的这个线程恢复标识位,解锁监视器后,其他的线程才可进入。当一个线程在解锁监视器后,又可以再次进入,锁住这个监视器。要注为达到同步的目的,多个线程使用的监视器必须是相同的,即作为参数的那个对象,对于多个线程而言必须是相同的:
class TestThread implements Runnable
{
int tickets=100;
String str=new String("");
public void run()
{
while(true)
{
synchronized(str)
{
if(tickets>0)
System.out.println(Thread.currentThread().getName()+"is selling ticket"+tickets--);
}
}
}
}
我们还可以用同步函数,就是在函数声明时加上synchronized关键字,也可以达到同步的效果:
class TestThread implements Runnable
{
int tickets=100;
String str=new String("");
public void run()
{
while(true)
{
sale();
}
}
public synchronized void sale()
{
if(tickets>0)
System.out.println(Thread.currentThread().getName()+"is selling ticket"+tickets--);
}
}
与同步代码块的原理相同,只是同步方法使用的对象监视器是this对象。
线程间的通信
wait:告诉当前线程放弃监视器并进入睡眠状态直到其他线程进入同一监视器并调用notify为止。
notify:用于唤醒同一对象监视器中调用wait的第一个线程。用于类似饭馆有一个空位后通知所有等候就餐的顾客中的第一位可以入座的情况。
notifyAll:用于唤醒同一对象监视器中调用wait的所有线程,具有最高优先级的线程首先被唤醒并执行。
以上三个方法在所有的java类中都有。
下面编写一个程序:生产者向缓冲区中写入信息,而消费者从缓冲其中提取信息,生产者每写入一次,消费者就提取一次,不能连续提取或写入信息。
class Producer implements Runnable
{
Queue q;
public Producer(Queue q)
{
this.q=q;
}
public void run()
{
int i=0;
while(true)
{
if(i==0)
{
q.put("zhangsan", "male");
}
else
{
q.put("lisi", "female");
}
i=(i+1)%2;
}
}
}
class Consumer implements Runnable
{
Queue q;
public Consumer(Queue q)
{
this.q=q;
}
public void run()
{
while(true)
{
synchronized(q)
{
q.get();
}
}
}
}
class Queue
{
private String name="unknown";
private String sex="unknown";
private boolean beFull=false;
public synchronized void put(String name,String sex)
{
if(beFull)
try{wait();}catch(Exception e){};
this.name=name;
this.sex=sex;
beFull=true;
notify();
}
public synchronized void get()
{
if(!beFull)
try{wait();}catch(Exception e){};
//调用wait和notify(notiftyAll)的对象必须与监视器对象同,此处为this对象。
System.out.print(name);
System.out.println(":"+sex);
beFull=false;
notify();
}
}
class Test
{
public static void main(String[]args)
{
Queue q=new Queue();
new Thread(new Producer(q)).start();
new Thread(new Consumer(q)).start();
}
}
线程的生命控制
如图:
注意:当调用start方法后,线程并不会立即进入执行状态,只有被调度器调度后,才能执行。
当run方法执行完毕后,相应的线程也会死亡。