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());}}
程序运行结果如下:
- Java多线程编程之使用多线程
- java 多线程编程之CountDownLatch
- Java多线程编程之同步器
- Java多线程编程之死锁
- JAVA多线程编程之Condition
- Java 多线程编程之二 volatile 关键字的使用
- Java 多线程编程之三:synchronized 关键字的使用
- Java 多线程编程之二 volatile 关键字的使用
- java多线程编程之使用Synchronized块同步变量
- 《java多线程编程核心技术》之java多线程技能
- Java 多线程编程之八:多线程的调度
- Java 多线程编程之八:多线程的调度
- iOS多线程编程之NSThread的使用
- IOS多线程编程之NSThread的使用
- IOS多线程编程之NSThread的使用
- iOS多线程编程之NSThread的使用
- iOS多线程编程之NSThread的使用
- iOS多线程编程之NSThread的使用
- 图像去模糊从环境 配置到算法效果
- loadrunner 当有用户登录失败时,重新执行登录脚本
- Faster RCNN算法详解
- php安装redis扩展
- URL字段截取
- Java多线程编程之使用多线程
- 阿里weex研究iOS(三)真机实时调试
- ScrollView拖动回弹效果(包括横向和竖向)
- browser
- JS跨域问题,及在同一个服务器上布置两个网站
- Obsolete(C# 编程指南)
- 283. Move Zeroes
- mybatis实现表关联查询
- Web/c# 批量生成控件和操作