Java多线程编程之使用多线程

来源:互联网 发布:谷歌域名 编辑:程序博客网 时间:2024/05/17 04:01

继承Thread类:

Java中,自带了对多线程技术的支持。继承Thread,还有实现Runnable接口。先看一下Thread类的结果,如下:

public class Thread implements Runnable

从上面的代码中发现,Thread类实现了Runnable接口,它们之间具有多态关系。

这种方法的局限是不支持多继承,因为Java是单继承的,所以为了多继承,完全可以实现Runnable接口的方法。

一边实现一边继承,但是这两种方法创建线程在工作时性质是一样的,没有本质区别。

public class MyThread extends Thread{@Overridepublic void run() {super.run();System.out.println("MyThread");}}public class Run {public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start();System.out.println("运行结束!");}}

代码的运用结果:

从图结果看,MyThread类的run方法执行较晚,这也说明在使用多线程技术时,代码的执行结果与代码顺序或代码调用顺序无关的。

如果多次调用start()方法,则会出现java.lang.IllegalThreadStateException异常

上面介绍了线程的调用随机性,下面通过例子来演示线程的随机性:

创建自定义线程类MyThread.java,代码如下:

public class MyThread extends Thread{@Overridepublic void run() {super.run();for(int i=0;i<10;i++){int time = (int)(Math.random()*1000);try {Thread.sleep(time);System.out.println("run="+Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}}}

再创建运行类:

public class Run {public static void main(String[] args) {MyThread myThread = new MyThread();myThread.setName("myThread");myThread.start();for(int i=0;i<10;i++){int time = (int)(Math.random()*1000);try {Thread.sleep(time);System.out.println("main="+Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}}}

在代码中,为了展现出线程具有随机性,所以使用随机数的形式来使用线程得到挂起效果,从而表现CPU执行哪个线程具有不确定性。

Thread类的start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法。这个过程其实就是让系统安排一个时间来调用Thread中的run()方法,也就是使线程得到运行,启动线程具有异步执行的效果。如果调用代码thread.run()就不是异步执行,而是同步。那么此线程对象并不交给“线程规划器”来进行处理,而是由main主线程来调用run()方法,也就是必须等run()方法中的代码执行完后才执行后面的代码。

以异步方式运行效果如下:



另外还需注意,执行start()方法的顺序并不代表线程启动的顺序。

线程类代码如下:

public class TheadT1 extends Thread{private int i;public TheadT1(int i){this.i = i;}public void run() {System.out.println(i);}}

执行类代码:

public class TheadT1Test {public static void main(String[] args) {TheadT1 t1 = new TheadT1(1);TheadT1 t2 = new TheadT1(2);TheadT1 t3 = new TheadT1(3);TheadT1 t4 = new TheadT1(4);TheadT1 t5 = new TheadT1(5);t1.start();t2.start();t3.start();t4.start();t5.start();}}

程序运用结果如下图:


实现Runnable接口:

如果欲创建的线程类已经有一个父类的话,那只能通过实现Runnable接口实现多线程功能。

创建一个实现Runnable接口的类,代码如下:

public class MyRunnable implements Runnable{@Overridepublic void run() {System.out.println("MyRunnable");}}public class RunnableRun {public static void main(String[] args) {MyRunnable run = new MyRunnable();Thread t = new Thread(run);t.start();System.out.println("运用结束!");}}

运用结果:

使用Thread类的方式来开发多线程应用程序在设计上是有局限性的,因为Java是单继承,所以为了改变这种限制,可以使用Runnbale接口的方式来实现多线程技术。


实例变量与线程安全:

自定义线程类中的实例变量针对其他线程可以有共享与不共享之分,这在多个线程之间进行交互时是很重要的一个技术点。

(1)   不共享数据的情况:

通过下面一个示例查看数据不共享情况。


示例代码如下:

public class SharedIsNoThread extends Thread{private int count = 5;public SharedIsNoThread(String name){this.setName(name);}@Overridepublic void run() {super.run();while(count>0){count--;System.out.println("由"+Thread.currentThread().getName()+"计算,count="+count);}}}public class SharedIsNoThreadTest {public static void main(String[] args) {SharedIsNoThread t1 = new SharedIsNoThread("A");SharedIsNoThread t2 = new SharedIsNoThread("B");SharedIsNoThread t3 = new SharedIsNoThread("C");t1.start();t2.start();t3.start();}}

运行结果如下图:


通过程序表明,一共创建了3个线程,每个线程都有各自的count变量,自己减少自己的count变量的值。这样的情况就是变量不共享,此示例并不存在多个线程访问同一个实例变量的情况。

(2)   共享数据的情况

共享数据的情况如下图所示:


共享数据的情况就是多个线程可以访问同一个变量,比如在实现投票功能时,多个线程可以同时处理同一个人的票数。

下面通过一个示例看一下数据共享情况:

public class SharedThread extends Thread{private int count = 5;@Overridepublic void run() {super.run();while(count>0){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}count--;System.out.println("由"+Thread.currentThread().getName()+"计算,count="+count);}}}public class SharedThreadTest {public static void main(String[] args) {SharedThread sharedThread = new SharedThread();Thread t1 = new Thread(sharedThread, "A");Thread t2 = new Thread(sharedThread, "B");Thread t3 = new Thread(sharedThread, "C");t1.start();t2.start();t3.start();}}

休眠一秒,是为了情景更容易出现。

运行结果如下图:


从输出结果来看,出现了负数,产生了“非线程安全”问题。而我们想要的打印结果却是不重复的,而是依次递减并且最小等于0的。

在某些jvm中,i—的操作要分成如下3步:

(1)   取得原有i值。

(2)   计算i-1。

(3)   对i进行赋值。

而且在我们程序中还有while判断这一步。如果出现多个线程访问,那么一定会出现非线程安全问题。

其实这个示例就是典型的销售场景:3个销售员,每个销售员卖出一个货品后不可以得出相同的剩余数量,必须在每个销售员卖完一个货品后,其他销售人员才可以在新的剩余物品数上继续减1操作。这时候就需要使多个线程间进行同步,也就是按顺序的方式进行减1操作。更改代码如下:

public class SharedThread extends Thread{private int count = 5;@Overridesynchronized public void run() {super.run();while(count>0){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}count--;System.out.println("有"+Thread.currentThread().getName()+"计算,count="+count);}}}

重新执行程序,就不会出现负数和值一样的情况。

通过run方法前加入synchronized关键字,使多个线程执行run()方法时,以排队的方式进行。当一个线程调用run方法前,先判断run方法有没有被上锁,如上锁,说明有其他线程正在调用run方法,必须等其他线程对run方法调用结束才可以调用run方法。这样也就实现了排队调用run方法的目的,也就达到了按顺序对count变量减1的效果。synchronized关键字可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。

当一个线程想要执行同步代码块中代码时,线程首先尝试获取锁,如果能拿到这把锁,那么这个线程就能执行同步代码块中的代码。如果不能拿到这把锁,那么这个线程会不断尝试拿这把锁,直到能够拿到为止,而且是有多个线程同时争抢这把锁。流程如下图:

留意i--与System.out.println()的异常:

上面介绍解决非线程安全问题使用的是synchronized关键字,这里通过程序案例细化一下println()方法与i++联合使用时“有可能”出现的另外一种异常情况,并说明其中的原因。

示例代码如下:

public class MyThread extends Thread{private int i = 5;@Overridepublic void run() {super.run();System.out.println("i="+(i--)+" threadName="+Thread.currentThread().getName());}}public class Run {public static void main(String[] args) {MyThread thread = new MyThread();Thread t1 = new Thread(thread);Thread t2 = new Thread(thread);Thread t3 = new Thread(thread);Thread t4 = new Thread(thread);Thread t5 = new Thread(thread);t1.start();t2.start();t3.start();t4.start();t5.start();}}

程序的执行结果:

本测试的用例是:虽然println()方法内部是同步的,但是i--的操作却是在进入该方法前执行的,所以有发生非线程安全的问题的概率。


所以,为了防止发生非线程安全的问题,还是需要继续使用同步方法。

currentThread()方法

currentThread()方法可返回代码段正在被哪个线程调用的信息。下面通过一个示例进行说明。具体如下:

public class Run {public static void main(String[] args) {System.out.print(Thread.currentThread().getName());}}

运行结果:



结果说明,main方法被名为main的线程调用。

继续实验,创建如下代码:

public class MyThread extends Thread{public MyThread(){System.out.println("构造方法的打印:"+Thread.currentThread().getName());}@Overridepublic void run() {System.out.println("run方法的打印:"+Thread.currentThread().getName());}}public class Run {public static void main(String[] args) {Thread myThread = new MyThread();myThread.start();}}

执行结果如下:


通过结果可以看出,MyThread类的构造方法是被main线程调用的,而run方法是被名称为Thread-0的线程调用的,run方法是自动调用的。

代码改为如下:

public class Run {public static void main(String[] args) {Thread myThread = new MyThread();//myThread.start();myThread.run();}}


在来测试一个比较复杂的,示例代码如下:

public class CountOperate extends Thread{public CountOperate(){System.out.println("----CountOperate begin----");System.out.println("CountOperate Thread.currentThread().getName:"+Thread.currentThread().getName());System.out.println("CountOperate this.getName:"+this.getName());System.out.println("----CountOperate end----");}@Overridepublic void run() {System.out.println("----run begin----");System.out.println("run Thread.currentThread().getName:"+Thread.currentThread().getName());System.out.println("run this.getName:"+this.getName());System.out.println("----run end----");}}public class Run {public static void main(String[] args) {CountOperate c = new CountOperate();Thread t1 = new Thread(c,"A");System.out.println(t1);t1.start();}}
运行结果如下:


getName()和Thread.currentThread().getName()返回的值不同,你可以修改代码如下:

public class CountOperate extends Thread{public CountOperate(String name){super(name);System.out.println("----CountOperate begin----");System.out.println("CountOperate Thread.currentThread().getName:"+Thread.currentThread().getName());System.out.println("CountOperate this.getName:"+this.getName());System.out.println("----CountOperate end----");}public class RunCountOperate {public static void main(String[] args) {CountOperate countOperate = new CountOperate("M");countOperate.setName("N");Thread t1 = new Thread(countOperate);t1.setName("A");t1.start();}}

执行结果如下:


通过这个结果可以看出:this.getName()方法是指当前对象的线程名称,而Thread.currentThread.getName()方法表示正在运行的线程的名称。

isAlive()方法:

isAlive()方法的功能是判断当前线程是否处于活动状态。

创建示例代码,如下:

public class MyThread extends Thread{@Overridepublic void run() {System.out.println("---run:"+this.isAlive()+"---");}}public class Run {public static void main(String[] args) {MyThread m = new MyThread();System.out.println("begin="+m.isAlive());m.start();System.out.println("end="+m.isAlive());}}

程序运行结果如下:



方法isAlive()的作用是测试线程是否处于活动状态。什么是活动状态?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始的状态,就认为线程是存活的。

需要说明,如下代码:

System.out.println("end="+m.isAlive());

虽然上面示例打印的结果是true,但此值是不确定的。打印true是因为MyThread线程还没有执行完毕,所以输出true。如果代码改为如下:

public class Run {public static void main(String[] args) throws InterruptedException {MyThread m = new MyThread();System.out.println("begin="+m.isAlive());m.start();//线程休息一秒Thread.sleep(1000);System.out.println("end="+m.isAlive());}}

执行结果:



上述代码输出为false,因为MyThread线程在1秒钟内执行完毕了。

另外,在使用isAlive()方式时,如果将线程对象以构造参数的形式传递给Thread对象进行start()启动时,运行的结果和前面是有差异的。造成这样的差异原因是Thread.currentThread()和this的区别。下面通过代码测试一下:

public class CountOperate extends Thread{public CountOperate(String name){super(name);System.out.println("----CountOperate begin----");System.out.println("CountOperate Thread.currentThread().getName:"+Thread.currentThread().getName());System.out.println("CountOperate Thread.currentThread().isAlive:"+Thread.currentThread().isAlive());System.out.println("CountOperate this.getName:"+this.getName());System.out.println("CountOperate this.getName:"+this.isAlive());System.out.println("----CountOperate end----");}@Overridepublic void run() {System.out.println("----run begin----");System.out.println("run Thread.currentThread().getName:"+Thread.currentThread().getName());System.out.println("run Thread.currentThread().isAlive:"+Thread.currentThread().isAlive());System.out.println("run this.getName:"+this.getName());System.out.println("run this.isAlive:"+this.isAlive());System.out.println("----run end----");}}public class RunCountOperate {public static void main(String[] args) {CountOperate countOperate = new CountOperate("M");countOperate.setName("N");Thread t1 = new Thread(countOperate);System.out.println("main begin t1 isAlive="+t1.isAlive());t1.setName("A");t1.start();System.out.println("main end t1 isAlive="+t1.isAlive());}}

执行结果如下:

sleep()方法

方法sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。

通过一个示例说明,如下:

public class MyThread1 extends Thread{@Overridepublic void run() {System.out.println("run threadName="+this.currentThread().getName()+" begin");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("run threadName="+this.currentThread().getName()+" end");}}public class Run1 {public static void main(String[] args) {MyThread1 t1 = new MyThread1();System.out.println("begin="+System.currentTimeMillis());t1.run();System.out.println("end="+System.currentTimeMillis());}}

运行代码结果如下:


继续修改代码,如下:

public class MyThread2 extends Thread{@Overridepublic void run() {System.out.println("run threadName="+this.currentThread().getName()+" begin="+System.currentTimeMillis());try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("run threadName="+this.currentThread().getName()+" end="+System.currentTimeMillis());}}public class Run2 {public static void main(String[] args) {MyThread2 t2 = new MyThread2();System.out.println("begin="+System.currentTimeMillis());t2.start();System.out.println("end="+System.currentTimeMillis());}}

运行结果如下:

由于main线程与Thread2线程是异步执行的,所以首先打印的信息begin和end。而MyThread2稍后执行,在最后两行打印run begin 和 run end相关的信息。

getId()方法

getId()方法的作用是取得线程的唯一标识。、

创建示例代码如下:


public class Run1 {public static void main(String[] args) {Thread thread = Thread.currentThread();System.out.println(thread.getName()+" "+thread.getId());}}

程序运行结果如下:





1 0
原创粉丝点击