多线程
创建线程的方法有2种:继承Thread类和实现Runnable接口。
由于java不允许多重继承,所以已经继承了一个类的子类要想实现多线程,必须实现Runnable接口。(比如:你自己的applet类)
1、继承Thread类:
public class ThreadDemo{
public static void main(String [] args)
{
//new TestThread().run();
new TestThread().start();
while(true)
{
System.out.println("main thread is running");
}
}
}
class TestThread extends Thread {
public void run()
{
while(true)
{
System.out.println(Thread.currentThread().getName() + " is running!");
}
//Thread.currentThread()是获得该代码当前执行时对应的那个线程对象
//getName()方法取得当前线程的名称
}
}
小结:
A)要将一段代码在一个新的线程上运行,该代码应该在一个类的run函数中,并且run函数所在的类是Thread类在子类。倒过来看,要实现多线程,必须编写一个继承了Thread类的子类,子类要覆盖Thread类中的run函数,在子类的run函数中调用想在新线程上运行的代码。
B)启动一个新线程,不是直接调用Thread子类的run方法,而是调用Thread子类对象的start(从Thread类中继承的)方法,Thread类对象的start方法将产生一个新的线程,并在该线程上运行该Thread类对象中的run方法,根据面向对象的多态性,在该线程上实际运行的是Thread子类对象中的run方法。
C)由于线程的代码段在run方法中,那么该方法执行完成以后,线程也就相应的结束了,因此可以通过控制run方法中的循环条件来控制线程的终止。
2、实现Runnable接口:
public class TreadDemo3 {
public static void main(String[] args) {
TestThread tt=new TestThread();
Thread t=new Thread(tt);
t.start();
{ System.out.println("main thread is running"); }
}}
class TestThread implements Runnable {
public void run()
{while(true)
{ System.out.println(Thread.currentThread().getName() + " is running!");
} } }
2种实现多线程的方法的对比:
模拟买火车票
用Thread类实现的情况1:
public class ThreadDemo4 {
public static void main(String[] args) {
ThreadTest t=new ThreadTest();
t.start();
t.start();
t.start();
t.start();
}
}
class ThreadTest extends Thread{
private int tickets=100;
public void run()
{
while(true)
{
if (tickets>0)
System.out.println(Thread.currentThread().getName() + " is saling ticket " + tickets--);
} }}
结果:
结论:一个线程对象只能启动一个线程,无论你调用多少次start()方法,结果都只有一个线程。
用Thread类实现的情况2:
public class ThreadDemo4 {
public static void main(String[] args) {
new ThreadTest().start();
new ThreadTest().start();
new ThreadTest().start();
new ThreadTest().start();
}
}
class ThreadTest extends Thread{
private int tickets=100;
public void run()
{
while(true)
{
if (tickets>0)
System.out.println(Thread.currentThread().getName() + " is saling ticket " + tickets--);
}
}
}
结果:
结论:
创建了4个ThreadTest对象,就等于创建了4个资源,每个ThreadTest对象都有100张票,每个线程在独立地处理各自的资源。
用接口实现:
public class ThreadDemo5 {
public static void main(String[] args) {
ThreadTest1 t=new ThreadTest1();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class ThreadTest1 implements Runnable{
private int tickets=100;
public void run()
{
while(true){
if (tickets>0)
System.out.println(Thread.currentThread().getName() + " is saling ticket " + tickets--);
} }}
结论:创建了4个线程,每个线程调用的是同一个ThreadTest对象中的run方法,访问的是同一个对象的变量的实例。
可见实现Runnable接口相对于继承Thread类来说,有如下好处:
1、适合多个相同程序代码去处理同一资源的情况,把虚拟cpu同程序的代码,数据有效分离,较好地体现了面向对象的设计思想。
2、可以避免由于java的单继承特性带来的局限。即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时继承2个类,所以只能使用实现Runnable接口。
3、有利于程序的健壮性,代码能够被多个线程共享,代码与数据独立的。
实际上,几乎所有的多线程应用都采用实现Runnable接口的方式。
后台线程与联合线程
从上面的例子来说虽然main线程结束了,但是整个java程序没有结束,对于java程序来说,只要还有一个前台线程,程序就不会结束。
按前面的方法产生的线程都是前台线程,如果对某个线程对象在启动之前调用了setDaemon(true)方法,这个线程就变成了后台线程。
public class DaemonThread {
public static void main(String[] args) {
ThreadTest2 t=new ThreadTest2();
Thread tt=new Thread(t);
tt.setDaemon(true);
tt.start();
}}
class ThreadTest2 implements Runnable{
public void run(){
while(true)
{ System.out.println(Thread.currentThread().getName() + " is running.");
} }}
联合线程与join方法:
多线程的实际应用:
1、 如果程序要将数据库一个表中的所有记录复制到另外一个表中,利用循环将源表中的记录逐一读取并插入到另外一个表中,当表的记录有上百万行时,整个过程会非常的长,如果想放弃的话,就只能关机了。但是可以利用多线程来编写,由主线程负责表记录的复制,复制代码放在一个循环中,循环条件由一个boolean变量来控制,如:
Boolean bFlag=true;
While(bFlag)
{ 复制代码 }
在创建一个新线程,与用户交互,接收用户的键盘输入,当接收到用户停止命令时,新线程将主线程的循环条件bFlag设置为假,即通知主线程下次检查循环条件时结束复制过程。
多线程的同步
前面模拟买火车票的问题中,可能遇到一个特殊的情况就是同一张车票被打印2次或者多次,也由可能出现0或者负数。
注意:调用Thread.sleep()的方法时,必须要使用try…catch()语句,因为sleep函数定义的时候使用了关键字throws。
public static void sleep(long millis,int nanos) throws InterruptedException
同步代码块:
模拟买火车票的例子,为了防止出现0或者负数。做了如下修改:
public class ThreadDemo5 {
public static void main(String[] args) {
ThreadTest1 t=new ThreadTest1();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}}
class ThreadTest1 implements Runnable{
private int tickets=100;
String str="";
public void run()
{
while(true){
synchronized(str)
{
if (tickets>0)
{ try{Thread.sleep(10);}
//由于Thread.sleep()方法的定义中通过throws关键字声明该方法中可能发生异常,所以
//我们程序中调用Thread.sleep()方法的时候一定要使用try,catch语句。
//public static void sleep(long millis,int nanos) throws InterruptedException
catch(Exception e){
System.out.println(e.getMessage());
}
System.out.println(Thread.currentThread().getName() + " is saling ticket " + tickets--);
} } }}}
需要具有原子性的代码,放入synchronized语句内,形成同步代码块。在同一时刻只能有一个线程可以进入同步代码块内运行,只有当该线程离开同步代码块后,其他线程才可以进入同步代码块内运行。Synchronized语句的格式为:
Synchronized(object) {代码块}//object可以是任意的一个对象
当线程执行到synchronized的时候,jvm检查传入的实参对象(即object),并得到该对象的锁旗标(即标志位)。如果得不到,那么次线程就会被加入到一个与该对象的锁旗标相关联的等待线程池中,一直等到该对象的锁旗标被归还,池中的等待线程就会得到该旗标,然后继续执行下去。当线程执行完成同步代码块时,就会自动释放它占有的同步对象的锁旗标。
一个用于synchronized语句中的对象被称为一个监视器,当一个线程获得了synchronized(object)语句中的代码的执行权,即意味着它锁定了监视器,在一段时间内,只能有一个线程可以锁定监视器。所有其他的线程在试图进入已锁定的监视器时将被挂起,直到锁定了监视器的线程执行完synchronized(object)语句中的代码块,即监视器被解锁为止,另外的线程才可以进入锁定监视器,一个刚锁定了监视器的线程在监视器被解锁后可以再次进入并锁定同一监视器;另外,当在同步块中遇到break语句或抛出异常时,线程也会释放该标旗锁。
同步函数:
除了代码块可以同步外,函数也可以同步。
class ThreadTest3 implements Runnable{
private int tickets=100;
public void run()
{
while(true){
sale();
}}
public synchronized void sale()
{ if (tickets>0)
{ try{Thread.sleep(10);}
catch(Exception e){
System.out.println(e.getMessage());
}
System.out.println(Thread.currentThread().getName() + " is saling ticket " + tickets--);
} } }
同步函数的原理与同步代码块的原理一样。
代码块与函数的同步:
public class TreadDemo6 {
public static void main(String[] args) {
ThreadTest6 t=new ThreadTest6();
new Thread(t).start();
try{Thread.sleep(1);}
catch(Exception e){System.out.println(e.getMessage());}
t.str=new String("method");
new Thread(t).start();
}}
class ThreadTest6 implements Runnable{
private int tickets=100;
String str=new String("");
public void run()
{
if(str.equals("method"))
{
while(true)
{
sale();
}
}
else
{
synchronized(this)
{
if (tickets>0)
{
try{Thread.sleep(10);}
catch(Exception e ){System.out.println(e.getMessage()); }
System.out.println(Thread.currentThread().getName() + " is saling ticket " + tickets--);
} } } }
public synchronized void sale()
{
if (tickets>0)
{
try{ Thread.sleep(10); }
catch(Exception e ){ System.out.println(e.getMessage()); }
//System.out.println("Test");
System.out.println(Thread.currentThread().getName() + " is saling ticket " + tickets--);
}}}
死锁问题
在2个线程对2个同步对象具有循环依赖时,就会出现死锁。
class A {
synchronized void foo(B b)
{
String name=Thread.currentThread().getName();
System.out.println(name + "entered A.foo");
try{ Thread.sleep(1000); }
catch(Exception e){ System.out.println(e.getMessage()); }
System.out.println(name + "trying to call B.last()");
b.last();
}
synchronized void last()
{
System.out.println("inside A.last");
}
}
class B {
synchronized void bar(A a){
String name=Thread.currentThread().getName();
System.out.println(name + "entered B.Bar");
try{ Thread.sleep(1000); }
catch(Exception e){ System.out.println(e.getMessage()); }
System.out.println(name + "trying to call A.last()");
a.last();
}
synchronized void last()
{
System.out.println("inside A.last");
}
}
class Deadlock implements Runnable{
A a=new A();
B b=new B();
Deadlock()
{
Thread.currentThread().setName("mainThread");
new Thread(this).start();
a.foo(b);
System.out.println("back in main thread");
}
public void run(){
Thread.currentThread().setName("RacingThread");
b.bar(a);
System.out.println("back in other thread");
}
public static void main(String [] args)
{new Deadlock();
}}
输出:
RacingThread进入了b的监视器,然后在等待a的监视器。同时mainThread进入了a的监视器并等待b的监视器。这个程序永远不会完成。
线程间通信
public class ThreadCommunation {
public static void main(String[] args) {
Q q=new Q();
new Thread(new Producer(q)).start();
new Thread(new Consumer(q)).start();
}}
class Producer implements Runnable{
Q q=null;
public Producer(Q q)
{ this.q=q;}
public void run()
{
int i=0;
while(true)
{
synchronized(q)
{
if (i==0)
{ q.name="gby";
try{ Thread.sleep(10); }
catch(Exception e){System.out.println(e.getMessage());}
q.sex="man";
}
else
{ q.name="wy";
q.sex="woman";
}
i=(i+1)%2;
}}}}
class Consumer implements Runnable{
Q q=null;
public Consumer(Q q)
{ this.q=q;}
public void run()
{
while(true)
{
synchronized(q)
{
System.out.println(q.name + "--->" + q.sex);
}}}}
class Q{
String name="wy";
String sex="woman";
}
现在2个线程就不会运行出错了。
注意:在2个实现Runnable接口的类中,都要加上synchronized(object)语句。
格式化代码以后:
public class ThreadCommunation {
public static void main(String[] args) {
Q q=new Q();
new Thread(new Producer(q)).start();
new Thread(new Consumer(q)).start();
}}
class Producer implements Runnable{
Q q=null;
public Producer(Q q)
{ this.q=q;}
public void run()
{
int i=0;
while(true)
{
if (i==0)
q.put("gby","man");
else
q.put("wy","woman");
i=(i+1)%2;
}
}
}
class Consumer implements Runnable{
Q q=null;
public Consumer(Q q)
{ this.q=q;}
public void run()
{
while(true)
{
q.get();
}
}
}
class Q{
private String name="wy";
private String sex="woman";
public synchronized void put(String name,String sex)
{
this.name=name;
try{ Thread.sleep(10); }
catch(Exception e){System.out.println(e.getMessage());}
this.sex=sex;
}
public synchronized void get()
{
System.out.println(name + "--->" + sex);
}
}
Java是通过object类的wait,notify,notifyAll方法来实现线程间的通信问题,所有的类都继承于object,因此任何类中都可以直接使用这些方法。
Wait:告诉当前线程放弃监视器并进入睡眠状态,直到其他线程进入同一监视器并调用notify为止。
Notify:唤醒同一对象监视器中调用wait的第一个线程。用于类似饭馆有一个空位后,通知所有等待就餐的顾客中第一位可以入座。
notifyAll:唤醒同一对象监视器中调用wait的所有线程,具有最高优先级的线程首先被唤醒执行。
如果想让上面的程序符合我们的要求,那么必须在类Q中定义一个新的成员变量bFull,来表示数据存储空间的状态,当consumer线程取走数据后,bFull为false,当Producer线程放入数据后,bFull为true。只有bFull为true时,consumer线程才能取走数据,否则必须等待Producer线程放入新的数据后的通知;反之,只有bFull为false,Producer线程才能放入新的数据,否则就必须等待Consumer线程取走数据后的通知,修改Q类的代码如下:
class Q{
private String name="wy";
private String sex="woman";
boolean bFull=false;
public synchronized void put(String name,String sex)
{
if(bFull)
try { wait();} catch (InterruptedException e1) {e1.printStackTrace();}
this.name=name;
try{ Thread.sleep(10); }
catch(Exception e){System.out.println(e.getMessage());}
this.sex=sex;
bFull=true;
notify();
}
public synchronized void get()
{
if(!bFull)
try { wait();} catch (InterruptedException e) {e.printStackTrace();}
System.out.println(name + "--->" + sex);
bFull=false;
notify();
}
}
public class ThreadCommunation {
public static void main(String[] args) {
Q q=new Q();
new Thread(new Producer(q)).start();
new Thread(new Consumer(q)).start();
}}
class Producer implements Runnable{
Q q=null;
public Producer(Q q)
{ this.q=q;}
public void run()
{
int i=0;
while(true)
{
if (i==0)
q.put("gby","man");
else
q.put("wy","woman");
i=(i+1)%2;
}}}
class Consumer implements Runnable{
Q q=null;
public Consumer(Q q)
{ this.q=q;}
public void run()
{
while(true)
{
q.get();
}}}
线程生命的控制
上图所示:一个线程的产生是从我们调用了start方法开始进入Running状态,即可以被调度状态,并没有真正开始运行,调度器可以将cpu分配给它,真正运行其中的程序代码。线程在运行过程中,有以下几个可能的去向。
1、没有遇到任何阻隔,运行完成直接结束,也就是run()方法执行完毕。
2、调度器将cpu分配给其他线程,这个线程变为Runnable状态。
3、请求锁旗标,却得不到,这时候它要等待对象的锁旗标,得到锁旗标后会进入runnable状态开始运行。
4、遇到wait方法,它会被放入等待池中继续等待,直到有notify()或者interrupt方法执行,它才会被唤醒或打断开始等待对象锁旗标,等到锁旗标后进入runnable状态继续执行。
如何控制线程的生命
控制线程生命周期的方法有:suspend方法,resume方法,stop方法。
其中不推荐使用suspend方法和sesume方法,因为:
1、 会导致死锁发生。
2、 它允许一个线程(甲)通过直接控制另一个线程(乙)的代码来直接控制那个线程(乙)。
3、 虽然stop能够避免死锁发生,但是带来了另外的不足,如果一个线程正在操作共享数据段,操作过程没有完成就stop了的话,将导致数据的不完整性。因此stop方法也不提倡使用。
可以使用以下方法:
public class Threadlife {
public static void main(String[] args) {
ThreadTest7 t =new ThreadTest7();
new Thread(t).start();
for (int i=0;i<100;i++)
{
if (i==50)
t.stopMe();
System.out.println("maingThread is running");
}
}
}
class ThreadTest7 implements Runnable{
private boolean bFlag=true;
public void stopMe()
{bFlag= false;}
public void run()
{
while(bFlag)
{
System.out.println(Thread.currentThread().getName() + " is running ");
}
}
}
程序中定义了一个计算器i,用来控制main线程的循环打印次数,在i的值从0到50的这段时间内,2个线程是交替运行的,但是计数器i的取值变为50的时候,程序调用了ThreadTest类的stopMe方法,而在stopMe方法中,将bFlag变量赋值为false,也就是终止了while循环,run方法结束,线程随之结束。Main线程在计数器i等于50的时候,调用了ThreadTest类的stopMe方法后,cpu不一定马上切换到线程上,也就是说线程不一定会马上停止。
推荐使用控制run方法中的循环条件的方式来结束一个线程。