创建线程的两种方式
1.继承Thread类,并重写run方法实现线程执行的逻辑任务
public class ThreadTest01 {
public static void main(String[] args) {
MyThread01 myThread01 =new MyThread01();
MyRunnable myRunnable01 =new MyRunnable();
//调用start方法启动线程
myThread01.start();
}
}
//通过继承Thread来创建线程
class MyThread01 extends Thread{
//重写run方法实现线程任务逻辑
@Override
public void run() {
while (true){
System.out.println("Hello World");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.实现runnable接口重写run方法,构建Thread对象并注入以来的Runnable实现, 从而执行Runnable实现中的run方法
Runnable接口是JAVA中的一个线程接口,此接口提供唯一的一个run方法,Runnable接口的主要作用就是可以让多个Thread实例共享此接口的run方法, 从而达到线程建通信更加方便的目的
适合场景:多线程共同处理数据和信息交互
注意问题:线程协调同步安全处理
public class ThreadTest02 {
public static void main(String[] args) {
//使用同一个Runnable实现对象构建Thread线程对象
MyRunnable myRunnable01 =new MyRunnable();
//构建Thread对象并注入以来的Runnable实现
//thread01和thread02都不会执行自己的run方法, 都去调用myRunnable01的run方法了
//构建依赖于Runnable接口实例的普通用户线程并设置线程名称
Thread thread01 =new Thread(myRunnable01,"张三");
Thread thread02 =new Thread(myRunnable01,"李四");
//设置线程的优先级
thread01.setPriority(Thread.MIN_PRIORITY);
thread02.setPriority(10);
thread01.start();
thread02.start();
}
}
//实现runnable接口重写run方法
class MyRunnable implements Runnable{
//共享数据i
int i=0;
@Override
public void run() {
while (i<100){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前正在执行的线程对象
Thread currentThread=Thread.currentThread();
System.out.println("线程名为“"+currentThread.getName()+"”的线程正在调用Run方法,当前i值:"+i);
i++;
}
}
}
线程的优先级
在 Java 语言中,线程的优先级范围是 1~10,值必须在 1~10,否则会出现异常;优先级的默认值为 5。优先级较高的线程会被优先执行,当执行完毕,才会轮到优先级较低的线程执行。如果优先级相同,那么就采用轮流执行的方式。
给线程设置优先级的方式:
setPriority(int newPriority);
//下面几种方式都可以
thread01.setPriority(Thread.MIN_PRIORITY);
thread01.setPriority(Thread.MAX_PRIORITY)
thread01.setPriority(3)
线程的生命周期
Java线程具有五中基本状态
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法(thread01.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
线程状态藐视了线程对象处于生命周期的那个时刻,线程状态使用一组枚举(Thread.State)值进行表示
NEW:一个尚未启动的线程的状态。也称之为初始状态、开始状态。
RUNNABLE:一个可以运行的线程的状态,一个在 JVM 中执行的线程处于这一状态中。 可以运行是指这个线程已经在JVM中运行了,但是有可能正在等待其他的系统资源。也称之为就绪状态、可运行状态。
BLOCKED:一个线程因为等待监视锁而被阻塞的状态。也称之为阻塞状态。
WAITING:一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有三种,分别是调用Object.wait()、join()以及LockSupport.park()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。
TIMED_WAITING:一个正在限时等待另一个线程执行一个动作的线程处于这一状态。也称之为限时等待状态。造成线程限时等待状态的原因有五种,分别是:Thread.sleep(long)、Object.wait(long)、join(long)、LockSupport.parkNanos(obj,long)和LockSupport.parkUntil(obj,long)。
TERMINATED:一个完全运行完成结束退出的线程的状态。也称之为终止状态、结束状态。
获取当前线程的状态:
//返回值是Thread.State的枚举值
public Thread.State getState()
线程的阻塞和处理
线程在无法获得CPU分配的使用权限时形成线程的执行中断状态称为阻塞(不在等待获取CPU使用权的队列中)
阻塞中的线程将无条件让出CPU的使用权,直到解除阻塞重新排队获得CPU使用权后恢复运行或在阻塞中死亡
Java中实现线程阻塞的方法:
(1)线程睡眠:Thread.sleep (long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
(2)线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait() 一样。wait() 和 notify() 方法:两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许 指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用.
(3)线程礼让,Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程.
(4)线程自闭,join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
(5)suspend() 和 resume() 方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态。典型地,suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume() 使其恢复。Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。
线程联合
线程联合是一种某个线程启动后发出强制联合(join)的命令,导致其他线程必须按照联合的规则等待当前线程执行结束才能获得执行的机会;
对于发出join命令的线程将独占CPU使用权,而在join周期内其他线程则被剥夺了CPU的使用资格,线程联合对多线程共同执行计算效率有非常不利的影响,应更具实际情况选择是否有必要使用线程联合。
对于执行join的命令的线程, 如果在join期间被终端了当前状态, 则将抛出InterruptedException异常
void join(long millis)
无限期等待当前线程执行结束
void join(long millis)
等待该线程终止的时间最长为 millis 毫秒,时间到了之后其他线程就可以执行了。
void join(long millis, int nanos)
等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
public class ThreadJoinRunnable implements Runnable {
@Override
public void run() {
if (Thread.currentThread().getName().equals("leader")){
for (int i=1;i<10000000;i++){
if (i%10000==0){
System.out.println("当前领导正在通过"+i);
}
}
}
System.out.println(Thread.currentThread().getName()+",正在通过此路段");
}
}
class ThreadJoinRunnableTest{
public static void main(String[] args) {
ThreadJoinRunnable tjr=new ThreadJoinRunnable();
Thread boss =new Thread(tjr,"leader");
Thread norm1 =new Thread(tjr,"norm1");
Thread norm2 =new Thread(tjr,"norm2");
boss.start();
try {
//boss线程调用了join方法,norm1,norm2线程需要等boss线程执行结束才有执行的机会
boss.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
norm1.start();
norm2.start();
}
}
Thread静态本地yield()方法:
Thread.yield()方法作用是:暂停当前正在执行的线程对象(及放弃当前拥有的cup资源),并执行其他线程。yield()做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。
因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
(yield的本质是把当前线程重新置入抢CPU时间的”队列” (队列只是说所有线程都在一个起跑线上.并非真正意义上的队列)。)