多线程是JAVA中的重要组成部分。本文是多线程的基础,着重从多线程的概述、多线程的实现方式、线程调度和线程控制以及线程生命周期等方面来阐述多线程的知识。
一、首先我们先搞清楚进程与线程的区别:
进程:进程是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
线程:是程序的执行单元,执行路径。是程序使用CPU的最基本单位。
单线程:程序只有一条执行路径。
多线程:程序有多条执行路径。像JVM的启动就是多线程的,因为它最少启动了主线程和垃圾回收线程。
那么多线程有什么意义呢?多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率。程序的执行其实都是在抢CPU的资源,CPU的执行权。
二、在java中,多线程的实现方式有两种,一种是继承Thread类,另一种是实现Runnable接口。(准确的说是三种实现方式,第三种是实现Callable接口,此接口需要与Future、线程池配合使用,后面讲)
首先看第一种,继承Thread类。
public class MyThread extendsThread {publicMyThread(String name){super(name);
}
@Overridepublic voidrun() {for (int i = 0; i < 10; i++) {
System.out.println(getName()+":"+i);
}
}
}
测试:public classMyThreadDemo {public static voidmain(String[] args) {
MyThread my1=new MyThread("令狐冲");
MyThread my2=new MyThread("任盈盈");
my1.start();
my2.start();
}
}
结果:
令狐冲:0
任盈盈:0
令狐冲:1
任盈盈:1
令狐冲:2
任盈盈:2
令狐冲:3
任盈盈:3
令狐冲:4
任盈盈:4
令狐冲:5
任盈盈:5
令狐冲:6
任盈盈:6
令狐冲:7
任盈盈:7
令狐冲:8
任盈盈:8
令狐冲:9
任盈盈:9
第二种是实现Runnable接口
public class MyRunnable implementsRunnable {
@Overridepublic voidrun() {for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
测试:public classMyRunnableDemo {public static voidmain(String[] args) {
MyRunnable run=newMyRunnable();
Thread t1=new Thread(run, "东方不败");
Thread t2=new Thread(run, "岳不群");
t1.start();
t2.start();
}
}
结果:
东方不败:0岳不群:0东方不败:1岳不群:1东方不败:2岳不群:2东方不败:3岳不群:3东方不败:4岳不群:4东方不败:5岳不群:5东方不败:6岳不群:6岳不群:7东方不败:7岳不群:8东方不败:8岳不群:9东方不败:9
推荐使用Runnable接口,因为可以避免由于Java单继承带来的局限性。适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
三、线程间的调度
线程有两种调度模型:
分时调度模型: 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型 :优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。线程默认优先级是5,线程优先级的范围是:1-10。线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。
public class MyRunnableDemo {
public static void main(String[] args) {
MyRunnable run=new MyRunnable();
Thread t1=new Thread(run, "东方不败");
Thread t2=new Thread(run, "岳不群");
t1.setPriority(10);//设置线程优先级
t1.start();
t2.start();
}
}
测试结果:
东方不败:0岳不群:0东方不败:1东方不败:2东方不败:3东方不败:4岳不群:1东方不败:5东方不败:6东方不败:7东方不败:8东方不败:9岳不群:2岳不群:3岳不群:4岳不群:5岳不群:6岳不群:7岳不群:8岳不群:9
四、线程控制
线程休眠:Thread.sleep(毫秒数);让当前正在执行的线程休眠(暂停执行)。
public class ThreadYield extendsThread {
@Overridepublic voidrun() {try{
Thread.sleep(500);
}catch(InterruptedException e) {
e.printStackTrace();
}for (int x = 0; x < 5; x++) {
System.out.println(getName()+ ":" +x);
}
}
}
线程加入:join();等待该线程终止
public class MyRunnable implementsRunnable {
@Overridepublic voidrun() {for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}public classMyRunnableDemo {public static voidmain(String[] args) {
MyRunnable run=newMyRunnable();
Thread t1=new Thread(run, "领导");
Thread t2=new Thread(run, "小兵A");
Thread t3=new Thread(run, "小兵B");
t1.start();try{
t1.join();//让领导先走,领导走了我们再走
t2.start();
t3.start();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
测试结果:
领导:0领导:1领导:2领导:3领导:4小兵A:0小兵A:1小兵A:2小兵A:3小兵A:4小兵B:0小兵B:1小兵B:2小兵B:3小兵B:4
线程礼让:yield();暂停当前正在执行的线程对象,并执行其他线程。让多个线程的执行更和谐,但是不能靠它保证一人一次。
public class ThreadYield extendsThread {
@Overridepublic voidrun() {for (int x = 0; x < 5; x++) {
System.out.println(getName()+ ":" +x);
Thread.yield();
}
}
}public classThreadYieldDemo {public static voidmain(String[] args) {
ThreadYield ty1= newThreadYield();
ThreadYield ty2= newThreadYield();
ty1.setName("官人");
ty2.setName("夫人");
ty1.start();
ty2.start();
}
}
测试结果:
官人:0
官人:1
夫人:0
夫人:1
夫人:2
官人:2
夫人:3
官人:3
夫人:4
官人:4
守护线程:以守护某个线程而存在,当这个线程结束,守护线程也将结束。
例子,张飞关羽守护刘备
public class ThreadDaemon extendsThread {
@Overridepublic voidrun() {for (int x = 0; x < 10; x++) {
System.out.println(getName()+ ":第" + x+"次救刘备");
}
}
}public classThreadYieldDemo {public static voidmain(String[] args) {
ThreadDaemon ty1= newThreadDaemon();
ThreadDaemon ty2= newThreadDaemon();
ty1.setName("张飞");
ty2.setName("关羽");//设置守护线程
ty1.setDaemon(true);
ty2.setDaemon(true);
ty1.start();
ty2.start();
Thread.currentThread().setName("刘备");for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
测试结果:
刘备:0关羽:第0次救刘备
张飞:第0次救刘备
关羽:第1次救刘备
刘备:1关羽:第2次救刘备
张飞:第1次救刘备
关羽:第3次救刘备
刘备:2关羽:第4次救刘备
张飞:第2次救刘备
关羽:第5次救刘备
关羽:第6次救刘备
张飞:第3次救刘备
张飞:第4次救刘备
张飞:第5次救刘备
关羽:第7次救刘备
关羽:第8次救刘备
关羽:第9次救刘备
张飞:第6次救刘备
张飞:第7次救刘备
中断线程:stop():强迫线程停止执行,不执行后面的代码。过时了,不建议使用。interrupt():把线程的状态终止,并抛出一个InterruptedException。
public class ThreadInterrupt implementsRunnable {
@Overridepublic voidrun() {
System.out.println("我上山去砍柴。。。。");try{
Thread.sleep(10000);//砍柴中。。。。
} catch(InterruptedException e) {
System.out.println("砍到一半,太累了,不干了。");
}
System.out.println("下山回家咯。。。");
}
}public classThreadInterruptDemo {public static voidmain(String[] args) {
ThreadInterrupt t=newThreadInterrupt();
Thread t1=newThread(t);
t1.start();try{
Thread.sleep(3000);//超过3秒还没砍完柴,就别砍了(即终止砍柴线程)
t1.interrupt();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
测试结果:
我上山去砍柴。。。。
砍到一半,太累了,不干了。
下山回家咯。。。
线程等待:Object类中的wait()方法,让当前线程进入等待状态,同时,wait()会让当前线程释放它所持有的锁。“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。wait(long timeout)让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
线程唤醒:Object类中的notify()方法,唤醒当前对象上的等待线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
五、线程生命周期
线程分为五个阶段:创建、就绪、运行、阻塞、终止。
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。