|
|
JAVA程序在运行时最少会产生两个线程:“主线程”、垃圾回收线程。 程序清单:ThreadDemo1.java public class ThreadDemo1 { public static void main(String args[]) { new TestThread().run(); //循环输出 while(true) { System.out.println("main thread is running"); } } } class TestThread { public void run() { while(true) { System.out.println(Thread.currentThread().getName() + " is running"); } } }
启动一个线程,不是直接调用Thread子类对象的run方法,而是调用Thread子类对象的start(从Thread类中继承的)方法
程序清单:ThreadDemo2.java public class ThreadDemo2 { public static void main(String args[]) { new TestThread().start(); // 实际上是调用了线程类的run()方法 //循环输出 while(true) { System.out.println("main thread is running"); } } } class TestThread { public void run() { while(true) { System.out.println(Thread.currentThread().getName() + " is running"); } } }
实现线程,不是只能通过继承Thread类来实现,还可以通过实现Runnable这个接口来实现线程 java.lang Class Thread java.lang.Object java.lang.Thread All Implemented Interfaces: Runnable
可以发现,Thread类也是实现了Runnable接口
程序清单:ThreadDemo3.java public class ThreadDemo3 { public static void main(String args[]) { //new TestThread ().start(); TestThread tt= new TestThread(); //创建TestThread类的一个实例 Thread t= new Thread(tt); //创建一个Thread类的实例 t.start(); //使线程进入Runnable状态 while(true) { System.out.println("main thread is running"); } } } class TestThread implements Runnable //extends Thread { public void run() //线程的代码段,当执行start()时,线程从此出开始执行 { while(true) { System.out.println(Thread.currentThread().getName() + " is running"); } } }
用Thread继承而来的线程,一个线程序对象只能启动一次,无论调用多少遍start()方法,结果都只有一个线程
实现Runnable接口相对于继承Thread类来说,有如下显著好处: 1、 适合多个相干同程序代码的线程去处理同一资源的情况 2、 可以避免由于Java单继承特性带来的局限 3、 有利于程序的健壮性,代码能够被多个线程共享
程序清单:ThreadDemo4.java public class ThreadDemo4 { public static void main(String [] args) { ThreadTest t=new ThreadTest(); //一个线程对象只能启动一次,无论调用多少遍start()方法,结构都只有一个线程 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--); } } }
修改后的ThreadDemo4 程序清单:ThreadDemo4.java public class ThreadDemo4 { public static void main(String [] args) { ThreadTest t=new ThreadTest(); //启动了四个线程,分别执行各自的操作 new ThreadTest().start(); new ThreadTest().t.start(); new ThreadTest().t.start(); new ThreadTest().t.start(); } } class ThreadTest extends Thread { private int tickets=100; public void run() { while(true) { // Thread.currentThread().getName(),得到当前运行的线程的名称 if(tickets>0) System.out.println(Thread.currentThread().getName() + " is saling ticket " + tickets--); } } }
线程命名:getName()和setName() 在Thread API中,使用getName方法读取当前的名字。
GetNameThreadDemo.java
/* *目的:getName()方法的使用 *说明:getName()方法用于获得线程的名字 */ public class GetNameThreadDemo extends Thread { public void run() { for(int i=0;i<10;i++) printMsg(); } public void printMsg() { //获得运行此代码的线程的引用 Thread t = Thread.currentThread(); String name = t.getName(); System.out.println("name = "+name); } public static void main(String[] args) { GetNameThreadDemo t1 = new GetNameThreadDemo(); t1.start();
for(int i=0;i<10;i++) { t1.printMsg(); } } }
使用setName() 使用setName()方法可以显示指定Thread对象的名字 线程的名字一般在启动线程前设置,但也允许为已经运行的线程设置名字。允许两个Thread对象有相同的名字,但为了清晰起见,应该尽量避免这种情况。
SetNameThreadDemo.java /* *目的:setName()方法的使用 *说明:setName()方法可以设置一个线程的名称 */
public class SetNameThreadDemo extends Thread { public void run() { for(int i=0;i<10;i++) { printMsg(); } } public void printMsg() { //获得运行此代码的线程的引用 Thread t = Thread.currentThread(); String name = t.getName(); System.out.println("name = "+name); } public static void main(String args[]) { SetNameThreadDemo tt = new SetNameThreadDemo(); //在这里设置线程的名称 tt.setName("test thread"); tt.start(); for(int i=0;i<10;i++) { tt.printMsg(); } } }
激活线程:start()和isAlive() start()方法通知线程规划器这个新线程已准备就绪,而且应当在规划器的最早方便时间调用它的run()方法。 isAlive()方法,用来测试线程是否已经启动而且仍然启动。 /* *目的:isAlive()方法的使用 *说明:isAlive()方法用于判断线程是否存活 */
public class StartThreadDemo extends Thread{ public void run() { for(int i=0;i<10;i++) { printMsg(); } } public void printMsg() { //获得运行此代码的线程的引用 Thread t = Thread.currentThread(); String name = t.getName(); System.out.println("name = "+name); } public static void main(String[] args) { StartThreadDemo t = new StartThreadDemo(); t.setName("test Thread"); System.out.println("before start() , t.isAlive() = "+t.isAlive()); t.start(); System.out.println("just after start() , t.isAlive() = "+t.isAlive()); for(int i=0;i<10;i++) { t.printMsg(); } //下面这段代码会随情况不同而不同,有时候可能输出false,有时候可能输出true System.out.println("at the end of main() , t.isAlive() = "+t.isAlive()); } }
可以发现通过继承Thread类只能创建一个资源对象, 下面是采用实现Runnable接口实现线程
程序清单:ThreadDemo5.java public class ThreadDemo5 { public static void main(String [] args) { ThreadTest t=new ThreadTest(); //虽然启动了四个线程,但是起到了同步的作用 new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } } class ThreadTest implements Runnable //这里实现了Runnable接口 { private int tickets=100; public void run() { while(true) { if(tickets>0) System.out.println(Thread.currentThread().getName() + " is saling ticket " + tickets--); } } }
public class DaemonThread { public static void main(String args[]) { ThreadTest t = new ThreadTest() ; Thread tt = new Thread(t) ; /* 对Java程序来说,只要还有一个前台线程在运行,这个进程就不会结束,如果一个进程中只有后台线程运行,这个进程就会结束,如果某个线程对象在启动(调用start方法)之前调用了setDaemon(true)方法,这个线程就变成了后台线程。 */ tt.setDaemon(true) ; //设置后台运行 tt.start(); } }
class ThreadTest implements Runnable { public void run() { while(true) { // Thread.currentThread().getName(),得到当前运行的线程的名称 System.out.println(Thread.currentThread().getName()+”is running.”); } } }
使用Thread.sleep() 此方法用于将线程短暂休眠 TwoThreadSleep.java public class TwoThreadSleep extends Thread { public void run() { loop(); }
public void loop() { // get a reference to the thread running this Thread t = Thread.currentThread(); String name = t.getName();
System.out.println("just entered loop() - " + name);
for ( int i = 0; i < 10; i++ ) { try { Thread.sleep(2000); } catch ( InterruptedException x ) { // ignore }
System.out.println("name=" + name); }
System.out.println("about to leave loop() - " + name); }
public static void main(String[] args) { TwoThreadSleep tt = new TwoThreadSleep(); tt.setName("my worker thread"); tt.start();
// pause for a bit try { Thread.sleep(700); } catch ( InterruptedException x ) { // ignore }
tt.loop(); } }
join用来强制运行某一线程,下面的这个程序在I=100时会强制运行该线程
程序清单:JoinThread.java public class JoinThread { public static void main(String[] args) { ThreadTest t=new ThreadTest(); Thread pp=new Thread(t); pp.start(); int i=0; while(true) { if(i==100) { try { pp.join(); //强制一线程运行完后,在运行后面的线程 } catch(Exception e) //会抛出InterruptedException { System.out.println(e.getMessage()); } } System.out.println("main Thread "+i++); } } } class ThreadTest implements Runnable { public void run() { String str=new String(); int i=0; while(true) { System.out.println(Thread.currentThread().getName()+" "+i++); } } }
EX: /* 编写一个类MyThread,它继承自Thread类,该类有两个变量:线程的名字整型变量delay,该类的构造函数有两个参数, 分别初始化str和delay,用String类的对象str表示,类的frun方法中,先让线程休眠delay毫秒后,再打印线程的名字。 编写应用程序,创建MyThread类的三个对象t1、t2和t3,分别指定线程名字为“线程A”、“线程B”和“线程C”, 休眠时间为1000ms、2000ms、3000ms,并启动这三个线程。 */ public class Exercise14 { public static void main(String args[]) { MyThread t1,t2,t3 ; t1 = new MyThread("线程A",1000); t2 = new MyThread("线程B",2000); t3 = new MyThread("线程C",3000); t1.start(); t2.start(); t3.start(); } } class MyThread extends Thread { private String str ; private int delay ; public MyThread(String s,int d) { super(s) ; delay = d ; } public void run() { try { Thread.sleep(delay); } catch (InterruptedException ex) { ex.printStackTrace(); } System.out.println(getName()+"运行!"); }
}练习2: /* 编写一个类A,并实现Runnable接口,该类有两个变量:str(String类型)和delay(int类型),该类的构造方法有两个参数, 分别初始化str和delay。类中的run方法如下实现,休眠delay毫秒后,打印str。编写应用程序,分别用类A的两个对象作为参数 创建两个线程对象t1、t2;类A的两个对象的str分别指定为“A类对象1”、“A类对象2”,休眠时间为1000ms、2000ms */ public class Exercise15 { public static void main(String args[]) { Thread t1,t2 ; t1 = new Thread(new A("A类对象1",1000)) ; t2 = new Thread(new A("A类对象2",2000)) ; t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException ex) { ex.printStackTrace(); } System.out.println("线程运行完毕!"); } } class A implements Runnable { private String str ; private int delay; public A(String s,int d) { str = s ; delay = d ; } public void run() { try { Thread.sleep(delay); } catch (InterruptedException ex) { ex.printStackTrace(); } System.out.println(str+"运行!"); } }
中断线程: 当一个线程运行时,另一个线程可以调用对应的Thread对象的interrupt()方法来中断它。 例题:SleepInteerupt.java public class SleepInterrupt extends Object implements Runnable { public void run() { try { System.out.println( "in run() - 这个线程休眠20秒"); Thread.sleep(20000); System.out.println("in run() - 继续运行"); } catch (InterruptedException x) { System.out.println( "in run() - 中断线程"); return; }
System.out.println("in run() - 休眠之后继续完成"); System.out.println("in run() - 正常退出"); }
public static void main(String[] args) { SleepInterrupt si = new SleepInterrupt(); Thread t = new Thread(si); t.start();
//在此休眠是为确保线程能运行一会 try { Thread.sleep(2000); } catch (InterruptedException x) {}
System.out.println( "in main() - 中断其他线程"); t.interrupt(); System.out.println("in main() - 退出"); } }
查看线程的中断状态:isInterrupted()方法 可以在Thread对象上调用isInterrupted()方法来检查任何线程的中断状态。 InterruptCheck.java public class InterruptCheck extends Object { public static void main(String[] args) { Thread t = Thread.currentThread(); System.out.println("Point A: t.isInterrupted()=" + t.isInterrupted()); t.interrupt(); System.out.println("Point B: t.isInterrupted()=" + t.isInterrupted()); System.out.println("Point C: t.isInterrupted()=" + t.isInterrupted());
try { Thread.sleep(2000); System.out.println("was NOT interrupted"); } catch ( InterruptedException x ) { System.out.println("was interrupted"); } //在这里因为sleep抛出了异常,所以它清除了中断标志
System.out.println("Point D: t.isInterrupted()=" + t.isInterrupted()); } }
class ThreadTest implements Runnable { private int tickets = 100 ; public void run() { while(true) { //假如一个线程未执行完,而另一线程又运行此代码,则会出现不同步问题 //为说明问题,将线程延迟 if(tickets>0) { try { Thread.sleep(10) ; //在这里,让线程休息10毫秒 } catch(Exception e) { System.out.println(e.getMessage()); } System.out.println(Thread.currentThread().getName()+ ”is saling ticket”+tickets--); //在这里有可能会打印出负数 } } } }
public class Test { public static void main(String aregs[]) { ThreadTest t = new ThreadTest() ; new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } }
为解决上面的问题,可以引用同步代码块的概念 class ThreadTest implements Runnable { private int tickets = 100 ; //因为此字符串变量声明在类之中,所以所有线程都会共享此对象 String str = new String(“”); public void run() { while(true) { synchronized(str) //在这里加上一个同步锁 { //假如一个线程未执行完,而另一线程又运行此代码,则会出现不同步问题 //为说明问题,将线程延迟 if(tickets>0) { try { Thread.sleep(10) ; //在这里,让线程休息10毫秒 } catch(Exception e) { System.out.println(e.getMessage()); } System.out.println(Thread.currentThread().getName()+ ”is saling ticket”+tickets--); //在这里有可能会打印出负数 } } } } }
public class Test { public static void main(String aregs[]) { ThreadTest t = new ThreadTest() ; new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } }
之所以把String str = new String(“”) ;放在run方法之外,是因为如果将它在run方法中声明,则每个线程都会得到各自的str对象,这样是不可能同步的。
class ThreadTest implements Runnable { private int tickets = 100 ; public void run() { //将此变量声明在run方法之中,这样每个线程都拥有各自的str,无法同步 String str = new String(“”); while(true) { synchronized(str) //在这里加上一个同步锁 { //假如一个线程未执行完,而另一线程又运行此代码,则会出现不同步问题 //为说明问题,将线程延迟 if(tickets>0) { try { Thread.sleep(10) ; //在这里,让线程休息10毫秒 } catch(Exception e) { System.out.println(e.getMessage()); } System.out.println(Thread.currentThread().getName()+ ”is saling ticket”+tickets--); //在这里有可能会打印出负数 } } } } }
同步函数 除了可以对代码块进行同步外,也可以对函数实现同步,只要在需要同步函数定义前加上synchronized关键字即可。 class ThreadTest 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 Test { public static void main(String aregs[]) { ThreadTest t = new ThreadTest() ; new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } }
程序清单:ThreadDemo6.java public class ThreadDemo6 { public static void main(String [] args) { ThreadTest t=new ThreadTest(); new Thread(t).start(); //这个线程调用同步代码块 t.str=new String("method"); new Thread(t).start(); //这个线程调用同步函数 } } class ThreadTest implements Runnable { private int tickets=100; //同步监视 String str = new String (""); public void run() { if(str.equals("method")) { while(true) { sale(); } } else { while(true) { synchronized(str) //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(Thread.currentThread().getName()+" is saling ticket " + tickets--); } } } 产生并启动第一个线程,这个线程不见得马上就开始运行,CPU可能还在原来的main线程上运行,并将str变量设置成了”method”。
银行问题的同步: class CAdd { private static int sum = 0 ; public synchronized static void add(int a) { int temp = sum ; try { Thread.sleep(100) ; } catch(Exception e) {} temp = temp+a ; sum = temp ; System.out.println(sum); } } class Test extends Thread { public static void main(String[] args) { Test t1 = new Test() ; Test t2 = new Test() ; t1.start() ; t2.start() ; } public void run() { for(int i=0;i<3;i++) CAdd.add(100) ; } }
死锁问题:
程序清单:Deadlock.java 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); //get lock on a in this thread. System.out.println("back in main thread"); } public void run() { Thread.currentThread().setName("RacingThread"); b.bar(a); //get lock on a in other thread. System.out.println("back in other thread"); } public static void main(String[] args) { new Deadlock(); } }
从结果可以看出,RacingThread进入了b的监视器,然后又在等待a的监视器。同时MainThread进入了a的监视器并等待b的监视器。这个程序永远不会完成。
线程间通信 一个线程向数据存储空间添加数据(生产者),另一个线程从数据存储空间取出数据(消费者) 这个程序有两种以外需要考虑: 1、 假设生产者线程刚向数据存储空间添加了一个人的姓名,还没有加入这个人的性别,CPU就切换到了消费者线程,消费者线程将把这个人的姓名和上一个恶人的性别联系到了一起 2、 生产者放了若干次的数据,消费者才开始取数据,或者是,消费者取完一个数据后,还没等到生产者放入新的数据,又重复取出已取过的数据。
问题的解决: class Producer implements Runnable { public void run() { while(true) { 编写往数据存储空间放入数据的代码 } } } class Consumer implements Runnable { public void run() { while(true) { 编写从数据存储空间读取数据的代码 } } }
定义一个新的类来作为数据存储空间 class Q { String name ; String sex ; } Producer和Consumer中的run函数都需要操作类Q的同一个对象实例
程序清单:ThreadCommunation.java 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.name="张孝祥"; q.sex="男"; } else { q.name="陈琼"; q.sex="女"; } i=(i+1)%2; } } } class Q { String name="陈琼"; String sex="女"; } class Consumer implements Runnable { Q q=null; public Consumer(Q q) { this.q=q; } public void run() { while(true) { System.out.println(q.name + "---->" + q.sex); } } } 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(); } }
在这里Producer和Consumer访问的是同一个Q对象 可以发现在这里一切都是正常的结果,但假如在这里将某一线程延时的话,如下所示: 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.name="张孝祥"; //加入此段代码 try { Thread.sleep(10) ; } catch(Exception e) { System.out.println(e.getMessage()); } q.sex="男"; } else { q.name="陈琼"; q.sex="女"; } i=(i+1)%2; } } } class Q { String name="陈琼"; String sex="女"; } class Consumer implements Runnable { Q q=null; public Consumer(Q q) { this.q=q; } public void run() { while(true) { System.out.println(q.name + "---->" + q.sex); } } } 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(); } }
这时再运行程序,可以发现结果出现了错误,这个时候可以才用前面提到的同步方法,对类Q中的代码进行同步,为了说明问题,在类Q中加入两个新方法,一个是设置,一个是取值 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("张孝祥","男") ; } else { q.put("陈琼","女") ; } i=(i+1)%2; } } } class Q { private String name="陈琼"; private String sex="女"; 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); } } class Consumer implements Runnable { Q q=null; public Consumer(Q q) { this.q=q; } public void run() { while(true) { System.out.println(q.name + "---->" + q.sex); } } } 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(); } }
wati:告诉当前线程放弃监视器并进入睡眠状态,直到其他线程进入同一监视器并调用notify为止。 notify:唤醒同一对象监视器中调用wait的第一个线程。 notifyAll:唤醒同一对象监视器中调用wait的所有线程,具有最高优先级的线程首先被唤醒并执行。
class Q { private String name="陈琼"; private String sex="女"; boolean bFull = false ; public synchronized void put(String name,String sex) { if(bFull) wati() ; //后来的线程要等待 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) wait() ; System.out.println(name+”--”+sex); bFull = false ; notify(); } }
控制线程的生命周期 控制线程生命周期的方法有很多种:如:suspend方法、resume方法和stop方法,不推荐使用suspend和resume方法是因为: 1、 会导致死锁的发生 2、 它允许一个线程(甲)通过直接控制另外一个线程(乙)的代码来直接控制那个线程(乙)。 虽然stop能够避免死锁的发生,但带来了另外的不足,如果一个线程正在操作共享数据段,操作过程没有完成就stop的话,将会导致数据的不完整性,所以stop方法也不提倡使用了。
public class ThreadLife { public static void main(String args[]) { ThreadTest t = new ThreadTest() ; new Thread(t).start() ; for(int I=0;I<100;I++) { if(I==50) t.stopMe() ; System.out.println(“mainThread is running”); } } }
class ThreadTest implements Runnable { private boolean bFlag = true ; public void stopMe() { bFlag = false ; } public void run() { while(bFlag) { System.out.println(Thread.currentThread().getName()+ “is running”); } } }
|
|