Java----线程学习(多线程)
1.线程的五个状态:
-创建状态:Thread t=new Thread(),创建了一个线程,此时该线程就是创建状态
-就绪状态():当t线程调用了start()方法,线程立即进入就绪状态,在就绪状态队列中的线程不一定会立即执行,取决于cpu的调度
-运行状态:当cpu调度后,线程进入运行状态,执行重写的run方法的线程体代码块
-阻塞状态:当系统调用sleep(),wait,或者同步锁定时,线程进入阻塞状态,代码不往下执行,等待阻塞解除后,重新进入就绪状态,等待cpu的再次调度
-死亡状态:线程中断或结束,进入死亡状态后便不能再次启动了
JDK文档中这样描述:
线程可以处于以下状态之一:
●NEW 尚未启动的线程处于此状态。
●RUNNABLE 在Java虚拟机中执行的线程处于此状态。
●BLOCKED 被阻塞等待监视器锁定的线程处于此状态。
●WAITING 正在等待另一个线程执行特定动作的线程处于此状态。
●TIMED WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
●TERMINATED 已退出的线程处于此状态。
一个线程可以在给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。
测试代码:
package edu.ncu.dong.Thread.method;
//线程状态测试
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//观察状态
Thread.State state=thread.getState();
System.out.println(state);//new
thread.start();
state=thread.getState();
System.out.println(state);//runnable
while (state!=Thread.State.TERMINATED){
Thread.sleep(500);
state=thread.getState();
System.out.println(state);//TIMED_WAITING
}
//thread.start();//不能再启动
}
}
结果如图:
注意的是已经处于死亡状态的线程不能重新启动:比如在main方法最后加上thread.start()就会报错:
2.线程常用方法
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改一个线程的优先级 |
static void sleep(long millis) | 指定当前线程休眠,参数以毫秒记 |
void jion() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程(不推荐使用) |
boolean isAlive() | 测试线程是否处于活动状态 |
1)线程的终止
不推荐使用JDK提供的stop(),destroy()方法(已废弃),推荐让线程自己停止下来,建议使用一个标志位进行终止变量当flag=false,则终止线程运行。
package edu.ncu.dong.Thread;
import com.sun.scenario.effect.impl.sw.java.JSWBlend_SRC_OUTPeer;
//测试使用线程终止
public class TestStop implements Runnable{
//1.设置一个标志位
private boolean flag=true;
@Override
public void run() {
int i=1;
while(flag){
System.out.println("这是第"+(i++)+"次");
}
}
//2.设置一个自定义方法停止线程
public void stop(){
this.flag=false;
}
public static void main(String[] args) {
TestStop test=new TestStop();
new Thread(test).start();
for (int i = 0; i < 500; i++) {
System.out.println("main:"+" "+i);
if (i==400){
//自己利用flag停止线程
test.stop();
System.out.println("线程该停止了");
}
}
}
}
运行结果:
2)进程休眠 sleep()
-sleep (时间)指定当前线程阻塞的毫秒数;
-sleep存在异常InterruptedException;
-sleep的时间达到后线程进入就绪状态;
-sleep可以模拟网络延时,倒计时等。
-每一个对象都有一个锁,sleep不会释放锁;
用sleep()模拟网络延时:这是个模拟的抢票系统
package edu.ncu.dong.Thread.method;
//模拟网络延时
public class TestSleep implements Runnable{
private Integer tickets=5;//当前票数
//线程执行体
@Override
public void run() {
while (true) {
if (tickets < 0)
break;//这里先判断:至少还有一张票,才模拟下面的抢票操作
System.out.println(Thread.currentThread().getName() + "执行了抢票操作,还有第" + tickets-- + "张票");
}
}
public static void main(String[] args) {
TestSleep test=new TestSleep();
new Thread(test,"东东").start();//第二个参数是自定义线程的名字
new Thread(test,"兰兰").start();
new Thread(test,"丽丽").start();
}
}
结果中可以看出,这个“丽丽”线程明显执行时间过长,别的两位无法抢票,所以我们改一下代码,模拟网络延时:
public void run() {
while (true) {
if (tickets < 0)
break;//这里先判断:至少还有一张票,才模拟下面的抢票操作
try{
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行了抢票操作,还有第" + tickets-- + "张票");
}
}
执行结果友好得多:
3)线程礼让yield()
-礼让线程:让当前正在执行的线程暂停,但不阻塞(阻塞是转为阻塞态)
-作用:将线程从运行状态转为就绪状态,让cpu重新调度,礼让不一定成功!看CPU的调度
测试代码:
package edu.ncu.dong.Thread.method;
//测试yield方法
public class TestYield {
public static void main(String[] args) {
//这里就用lambda表达式简写了
Runnable r=()->{
System.out.println("线程:"+Thread.currentThread().getName()+" 开始!");
Thread.yield();//礼让
System.out.println("线程:"+Thread.currentThread().getName()+" 结束!");
};
new Thread(r,"T1").start();
new Thread(r,"T2").start();
}
}
结果:礼让成功
再次运行结果:礼让失败
4)合并线程jion()
-这个方法很霸道:等待此线程执行完成后,再执行其他线程,其他线程阻塞
测试代码:
package edu.ncu.dong.Thread.method;
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
Runnable r=()->{
for (int i = 0; i < 5; i++) {
System.out.println("正在执行vip线程"+i);
}
};
Thread t=new Thread(r);
t.start();//此时start之后,t线程就已经处于就绪状态(runnable状态)了,所以cpu是可以调度执行t的run代码块的
for (int i = 0; i < 200; i++) {
System.out.println("main线程执行中"+i);
if(i==100)
//此时i等于了100,执行了join方法,main进入阻塞,直到t线程结束,所所以可能出现的情况是:
//t线程在join方法执行之前已经结束了(dead),此时就不会调用t线程了
t.join();
}
}
}
结果:
注意点:
代码中t.start():此时start之后,t线程就已经处于就绪状态(runnable状态)了,所以cpu是可以调度执行t的run代码块的
而另一方面: t.join();此时i等于了100,执行了join方法,main进入阻塞,直到t线程结束,所以可能出现的情况是:t线程在join方法执行之前已经结束了(dead),此时就不会调用t线程了。
如图:我把t线程的i调为5,此时线程t很快就结束了,而主线程就不会阻塞去调用t线程了。
5)线程优先级
Java提供一 个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。线程的优先级用数字表示,范围从1~10,默认为5。
方法名:void setPriority(int newPriority)
查看例子:
package edu.ncu.dong.Thread.method;
public class TestPriority {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+" its priority is : "+Thread.currentThread().getPriority());
MyPriority myPriority=new MyPriority();
Thread thread1=new Thread(myPriority);
thread1.setPriority(6);
Thread thread2=new Thread(myPriority);
thread2.setPriority(7);
Thread thread3=new Thread(myPriority);
thread3.setPriority(8);
Thread thread4=new Thread(myPriority);
thread4.setPriority(9);
Thread thread5=new Thread(myPriority);
thread5.setPriority(10);//10是最大的优先级
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1*1000);//延时,帮助cpu在调度其他线程时,当前线程还没结束
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
System.out.println(Thread.currentThread().getName()+" its priority is : "+Thread.currentThread().getPriority());
}
}
}
查看结果
可以发现,虽然Thread-4是最大优先级10,确实先于其他(除了主线程)线程运行了,但是看红色2处可以发现:并不一定优先级高就先执行,比如Thread-2先于Thread-3先执行,优先级只是会起到一定的作用。优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度。
6)守护线程(deamon)
线程分为用户线程和守护线程;虚拟机必须确保用户线程执行完毕(也就是说有用户线程存在时,JVM就会存活),但是不用等待守护线程执行完毕。守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。如,后台记录操作日志,监控内存,垃圾回收等待…
查看例子:
package edu.ncu.dong.Thread.method;
/**
* 测试守护线程
*/
public class TestDaemon {
public static void main(String[] args) {
/*
* 神在守护我们来模拟守护线程
*/
Godness godness=new Godness();//神
We we=new We();//平凡人
Thread threadGod=new Thread(godness);
threadGod.setDaemon(true);//默认false,表示用户线程,true表示设置为守护线程
Thread threadWe=new Thread(we);
//启动线程
threadGod.start();//守护线程
threadWe.start();//用户线程
}
}
class Godness implements Runnable{
@Override
public void run() {
while (true){
System.out.println(" God bless us!");
}
}
}
class We implements Runnable{
@Override
public void run() {
for (int i = 0; i < 3000; i++) {
System.out.println(" happy every day!");
}
System.out.println(" good bye !");
}
}
我们查看运行结果:
这里各自获得cpu的调度执行
这里We线程(用户线程)执行结束,Godness线程还在执行
一段时间后,Godness也结束了,由于Godness线程是个While(true)死循环,不应该结束,但是由于它是守护线程,所以当没有用户线程存在时,守护线程也就随即结束。