抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

多线程系列

线程状态

一个线程对象在它的生命周期内,需要经历5个状态。

图11-4线程生命周期图.png

新生状态(New)

用new关键字建立一个线程对象后,该线程对象就处于新生状态。

处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态。

就绪状态(Runnable)

处于就绪状态的线程已经具备了运行条件,但是还没有被分配到CPU,处于“线程就绪队列”,等待系统为其分配CPU。

就绪状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会进入执行状态。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。有4中原因会导致线程进入就绪状态:

  1. 新建线程:调用start()方法,进入就绪状态;

  2. 阻塞线程:阻塞解除,进入就绪状态;

  3. 运行线程:调用yield()方法,直接进入就绪状态;

  4. 运行线程:JVM将CPU资源从本线程切换到其他线程。

运行状态(Running)

在运行状态的线程执行自己run方法中的代码,直到调用其他方法而终止或等待某资源而阻塞或完成任务而死亡。

如果在给定的时间片内没有执行结束,就会被系统给换下来回到就绪状态。也可能由于某些“导致阻塞的事件”而进入阻塞状态。

阻塞状态(Blocked)

阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。

有4种原因会导致阻塞:

  1. 执行sleep(int millsecond)方法,使当前线程休眠,进入阻塞状态。当指定的时间到了后,线程进入就绪状态。

  2. 执行wait()方法,使当前线程进入阻塞状态。当使用nofity()方法唤醒这个线程后,它进入就绪状态。

  3. 线程运行时,某个操作进入阻塞状态,比如执行IO流操作(read()/write()方法本身就是阻塞的方法)。只有当引起该操作阻塞的原因消失后,线程进入就绪状态。

  4. join()线程联合: 当某个线程等待另一个线程执行结束后,才能继续执行时,使用join()方法

死亡状态(Terminated)

死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有两个:

  1. 正常运行的线程完成了它run()方法内的全部工作

  2. 线程被强制终止,如通过执行stop()或destroy()方法来终止一个线程

    (注:stop()/destroy()方法已经被JDK废弃,不推荐使用)。

当一个线程进入死亡状态以后,就不能再回到其它状态了。

终止线程

终止线程一般不使用JDK提供的stop()/destroy()方法(它们本身也被JDK废弃了)。

通常的做法是提供一个boolean型的终止变量,当这个变量置为false,则终止线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.myThread;

public class TestThread implements Runnable{
String name;
boolean live = true;// 标记变量,表示线程是否可中止;
public TestThread(String name) {
super();
this.name = name;
}
public void run() {
int i = 0;
//当live的值是true时,继续线程体;false则结束循环,继而终止线程体;
while (live) {
System.out.println(name + (i++));
}
}
public void terminate() {
live = false;
}

public static void main(String[] args) {
TestThread ttc = new TestThread("线程A:");
Thread t1 = new Thread(ttc);// 新生状态
t1.start();// 就绪状态
for (int i = 0; i < 10; i++) {
System.out.println("主线程" + i);
}
ttc.terminate();
System.out.println("ttc stop!");
}
}



该程序中通过主线程控制live的值,当主线程把live置为 false时,run()方法停止执行,子线程终止运行

暂停线程

暂停线程执行常用的方法有sleep()和yield()方法

这两个方法的区别是:

  1. sleep()方法:可以让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态。

    注意:是休眠期到了后才会进入就绪状态,参与竞争获取CPU使用权

  2. yield()方法:可以让正在运行的线程直接进入就绪状态,让出CPU的使用权

    注意:由于没有标出让出的时间,所以下一刻就会立即进入竞争获取CPU的就绪状态

join()方法

b.join()可以将一个线程b插入到当前线程a中,这时a线程需要等待b执行完才会继续

线程基本信息获取

1
2
3
4
5
6
7
8
isAlive();
getPriority(); //获取线程优先级,默认为5
setPriority(); //设置线程优先级数值(int)

setName();
getName();

currentThread(); //获得当前线程

注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。

线程同步

线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问造成的这种问题。

由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:

  • synchronized 方法
  • synchronized 块

synchronized 方法

1
public synchronized void func(int a);

synchronized 方法控制对“对象的类成员变量”的访问:每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。

synchronized块

synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。

Java 为我们提供了更好的解决办法,那就是 synchronized 块。 块可以让我们精确地控制到具体的“成员变量”,缩小同步的范围,提高效率

synchronized 块:通过 synchronized关键字来声明synchronized 块,语法如下:

1
2
3
synchronized(syncObject){ 
   //允许访问控制的代码
}

表示在操控 syncObject时,每次只能控制一次,如果多个线程同时执行到这一条语句,则同一时间只能有一个线程操控syncObject

死锁问题

死锁:

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。

评论