声明:这篇文章主要内容大部分都是从林信良的Java学习日记这本书中摘录下来,仅作为个人学习参考用
1.概念
以前的程序从main()程序进入点开始到结束只有一个流程,而有的时候设计程序需要多个流程,就是多线程。
在Java中,如果想在main()以外独立设计流程,必须实现java.lang.Runnable接口,流程的进入点是操作在run()方法中。
例子:龟兔赛跑
(赛程长度为10步,每经过一秒,乌龟前进一步,兔子前进两步或者睡觉)
这里就是兔子是一个线程,乌龟是另外一个线程,最后由主函数调用这两个线程。
在Tortoise类中,乌龟的线程会从run()开始,乌龟只需要负责乌龟每秒走一步就可以,不会混杂兔子的线程。
乌龟:
package com.thread;
public class Tortoise implements Runnable
{
private int totalStep;
private int step;
public Tortoise(int totalStep) //设置构造方法,传入赛程总步数
{
this.totalStep=totalStep;
}
@Override
public void run()
{
try {
while(step<totalStep)
{
Thread.sleep(1000);//暂停1000毫秒,也就是1秒
step++;
System.out.printf("乌龟跑了 %d 步.........%n", step);//这句输出不懂
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
兔子:
package com.thread;
public class Hare implements Runnable
{
private boolean[] flags={true,false};
private int totalStep;
private int step;
public Hare(int totalStep)
{
this.totalStep=totalStep;
}
@Override
public void run() {
try {
while(step<totalStep)
{
Thread.sleep(1000);
boolean isHareSleep = flags[(int)(Math.random()*10)%2];//判断兔子是否睡觉
if(isHareSleep)//如果兔子睡着了,就原地不动,step就不加
{
System.out.printf("兔子睡着了zzzzz");
}
else//如果兔子没有睡着,就一秒走两步
{
step+=2;
System.out.printf("兔子跑了%d步....%n", step);
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
比赛:
package com.thread;
public class Race {
public static void main(String[] args) {
Tortoise tortoise = new Tortoise(10);//新建乌龟对象
Hare hare = new Hare(10);//新建兔子对象
Thread tortoiseThread = new Thread(tortoise);//新建乌龟对象的线程
Thread hareThread = new Thread(hare);//新建兔子对象的线程
tortoiseThread.start();//乌龟线程开始
hareThread.start();//兔子线程开始
}
}
运行结果:看样子兔子天天睡着啊= =注意,每次运行的结果不一定一致。
Thread和Runnable:
事实上JVM启动以后,不只有一个主线程,还会有垃圾收集、内存管理等线程,不过这些是底层机制,单纯从编写程序的角度看JVM,就只有一个主线程。
有两种写多线程的方法:
- 将流程定义在Runnable的run()方法中
- 继承Thread类,重写run()方法。
上面的龟兔赛跑就是第一种,下面改写成第二种:
package com.thread;
public class TortoiseThread extends Thread
{
……
@Override
public void run()
{
……
}
}
下面的兔子也是一样。
然后在main()主线程中,这样写调用刚才写的线程:
new TortoiseThread(10).start();
new HareThread(10).start();
3. 线程的生命周期:
1.Daemon(伴随)线程:
如果主线程启动了额外线程,JVM会等所有线程都执行完才终止JVM,如过一个线程被标示为Daemon线程,在所有非Daemon线程都结束时,JVM就会自动停止。、
表示一个线程是Daemon线程可以这样写:实例化线程对象,然后用setDaemon
方法
thread.setDaemon(true);
使用isDaemon()
方法可以判断线程是否为Daemon线程。
2.Thread基本状态图:
在调用Thread实例start()方法之后,基本状态有三种:
- Runnable:可执行(没有真正开始运行,等待CPU分配)
- Running:执行中
- Blocked:被阻断
Thread.start()之后,线程进入Runnable状态,等待CPU分配。
线程看起来像是同时执行,但是事实上在同一个时间点,一个CPU只能执行一个线程,只不过CPU会不断地切换线程,而且动作很快。。。。
我们可以设定线程的优先权,使用Thread的setPriority()
方法可以设定优先权,可以设置1~10,数字越大优先权越高,默认是5,如果优先权相同,就轮流执行。
有几种情况会让线程进入Blocked状态:
调用Thread.sleep()
方法
等待输入/输出完成后
当某个线程进入Blocked的时候,让另一个线程排入CPU执行(成为Running状态),经常是改进效能的方式之一。
一个线程进去Blocked状态之后,可以由另一个线程调用该线程的interrupt()
方法,会抛出InterruptException
异常对象。:
例子:
package com.thread;
public class Interrupt {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
try {
Thread.sleep(99999999);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println("我醒了!!!XD");
}
}
};
thread.start();
thread.interrupt();//主线程调用thread的interrupt()
}
}
3.安插线程:
如果A线程正在运行,流程中允许B线程加入,等到B线程执行完毕后再继续A线程,就是安插线程。用的是join()
方法。
threadB.join()//线程B join在当前线程中
可以在join()这个括号中加时间,就是这个加入的线程最多可以处理多久就要返回执行原来的线程。单位是毫秒。
5.停止线程:
一般不用。
线程后面还有很多,以后详细看。