程序 - 进程 - 线程

基本概念

  • 程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象

  • 进程(process):是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。

    • 如:运行中的QQ,运行中的MP3播放器
    • 程序是静态的,进程是动态的
  • 线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径

    • 若一个程序可同一时间执行多个线程,就是支持多线程的

进程与多线程

​ 每个Java程序都有一个隐含的主线程: main 方法

何时需要多线程

  • 程序需要同时执行两个或多个任务。
  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
  • 需要一些后台运行的程序时。

使用多线程的优点

背景:只使用单个线程完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?

多线程程序的优点:

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  2. 提高计算机系统CPU的利用率
  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。

线程的分类

​ Java中的线程分为两类:一种是守护线程,一种是用户线程

  • 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开
  • 守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
  • Java垃圾回收就是一个典型的守护线程。
  • 若JVM中都是守护线程,当前JVM将退出。

多线程的创建和使用

Thread类

概述

  • Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来实现。

  • Thread类的特性

    • 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
    • 通过该Thread对象的start()方法来调用这个线程
  • Thread类构造方法

    • Thread():创建新的Thread对象
    • Thread(String threadname):创建线程并指定线程实例名
    • Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
    • Thread(Runnable target, String name):创建新的Thread对象

常用方法

  • void start():启动线程,并执行对象的run()方法

  • run():线程在被调度时执行的操作

  • String getName():返回线程的名称

  • void setName(String name):设置该线程名称

  • static currentThread():返回当前线程

  • static void yield():线程让步

    • 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
    • 若队列中没有同优先级的线程,忽略此方法
  • join():当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join()方法加入的 join 线程执行完为止

    • 低优先级的线程也可以获得执行
  • static void sleep(long millis):(指定时间:毫秒)

    • 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
    • 抛出InterruptedException异常
  • stop():强制线程生命期结束

  • boolean isAlive():返回boolean,判断线程是否还活着

多线程示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestThread {
public static void main(String[] args) {
SubThread subThread = new SubThread();
subThread.start();
for (int i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}

class SubThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}

线程的调度

  • 调度策略

    • 时间片
    • 抢占式:高优先级的线程抢占CPU
  • Java的调度方法

    • 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
    • 对高优先级,使用优先调度的抢占式策略

线程的优先级

  • 线程的优先级控制

    • MAX_PRIORITY(10);
    • MIN _PRIORITY(1);
    • NORM_PRIORITY(5);
  • 涉及的方法:

    • getPriority():返回线程优先值
    • setPriority(int newPriority):改变线程的优先级
    • 线程创建时继承父线程的优先级

练习

​ 创建两个子线程,让其中一个输出1-100之间的偶数,另一个输出1-100之间的奇数。

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
public class TestThread2 {
public static void main(String[] args) {
SubThread1 st1 = new SubThread1();
SubThread2 st2 = new SubThread2();
st1.start();
st2.start();
}
}
//输出1-100之间的偶数
class SubThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++){
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
//输出1-100之间的奇数
class SubThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++){
if (i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}

线程创建的两种方式

继承Thread类

  1. 定义子类继承Thread类
  2. 子类中重写Thread类中的run方法
  3. 创建Thread子类对象,即创建了线程对象
  4. 调用线程对象start方法:启动线程,调用run方法。

实现Runnable接口

  1. 定义子类,实现Runnable接口
  2. 子类中重写Runnable接口中的run方法
  3. 通过Thread类含参构造器创建线程对象
  4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中
  5. 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。

联系和区别

  • 区别:
    • 继承Thread: 线程代码存放Thread子类run方法中。
    • 实现Runnable:线程代码存在接口的子类的run方法。
  • 实现方法的好处:
    • 避免了单继承的局限性
    • 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。

车站售票

继承的方式

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
//模拟火车站售票,开启三个窗口售票,总票数为100张
public class TestSale {
public static void main(String[] args) {
TicketSale ticketSale1 = new TicketSale();
TicketSale ticketSale2 = new TicketSale();
TicketSale ticketSale3 = new TicketSale();
ticketSale1.setName("窗口1");
ticketSale2.setName("窗口2");
ticketSale3.setName("窗口3");
ticketSale1.start();
ticketSale2.start();
ticketSale3.start();
}
}

class TicketSale extends Thread{
static int ticket = 100;

@Override
public void run() {
while (true){
if (ticket > 0){
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--);
}
else
break;
}
}
}

实现的方式

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
//模拟火车站售票,开启三个窗口售票,总票数为100张
public class TestSale2 {
public static void main(String[] args) {
TicketSale2 ticketSale = new TicketSale2();
Thread thread1 = new Thread(ticketSale);
Thread thread2 = new Thread(ticketSale);
Thread thread3 = new Thread(ticketSale);
thread1.setName("窗口1");
thread2.setName("窗口2");
thread3.setName("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}

class TicketSale2 implements Runnable{
int ticket = 100;

@Override
public void run() {
while (true){
if (ticket > 0){
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--);
}
else
break;
}
}
}

注意:两种方式去实现,存在线程安全问题(会出现重票、错票)

线程的生命周期

概述

​ JDK中用Thread.State枚举表示了线程的几种状态。

​ 要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态

  • 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
  • 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件
  • 运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能
  • 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
  • 死亡:线程完成了它的全部工作或线程被提前强制性地中止

线程状态转换

线程的同步

问题的提出

  • 多个线程执行的不确定性引起执行结果的不稳定。
  • 多个线程对账本的共享,会造成操作的不完整性,会破坏数据。

售票问题分析

  • 理想状态
  • 极端状态
1
2
3
4
5
6
7
8
9
10
11
12
int ticket = 100;
@Override
public void run() {
while (true){
if (ticket > 0){
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--);
}
else
break;
}
}
  • 问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
  • 解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

Synchronized

​ Java对于多线程的安全问题提供了专业的解决方式:同步机制

在实现的方式中,使用同步Synchronized,可以用this作为锁,但是在继承的方式中,不能,只能在外部设置Object对象。

同步代码块

1
2
3
synchronized (对象/同步监视器){
// 需要被同步的代码;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class TicketSale implements Runnable{
int ticket = 100;
Object obj = new Object(); //所有线程必须共用这一个obj

@Override
public void run() {
while (true){
synchronized (obj) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--);
} else
break;
}
}
}
}

同步方法

  • synchronized 还可以放在方法声明中,表示整个方法为同步方法。
1
2
3
public synchronized void show (String name){
······
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class TicketSale2 implements Runnable{
int ticket = 100; //共享数据

@Override
public void run() {
while (true){
show();
}
}

public synchronized void show(){
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--);
}
}
}

互斥锁

​ 在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性

  • 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
  • 关键字 synchronized 来与对象的互斥锁联系。当某个对象用 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问。
  • 同步的局限性:导致程序的执行效率要降低
  • 同步方法(非静态的)的锁为this
  • 同步方法(静态的)的锁为当前类本身

单例设计模式之懒汉式

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
//关于懒汉式的单例模式的安全问题:使用同步机制
//对于一般的方法内,使用同步代码块,可以考虑用this
//对于静态方法而言,可以考虑使用当前类本身充当锁
class Singleton{
private Singleton(){}

private static Singleton instance = null;

public static Singleton getInstance(){

if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
public class TestSingleton {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}

释放锁的操作

  1. 当前线程的同步方法、同步代码块执行结束
  2. 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
  3. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
  4. 当前线程在同步代码块、同步方法中执行了线程对象的**wait()**方法,当前线程暂停,并释放锁。

不会释放锁的操作

  1. 线程执行同步代码块或同步方法时,程序调用Thread.sleep()Thread.yield()方法暂停当前线程的执行
  2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
    • 应尽量避免使用suspend()resume()来控制线程

练习

​ 银行有一个账户。有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。问题:该程序是否有安全问题,如果有,如何解决?

【提示】

  • 明确哪些代码是多线程运行代码,须写入run()方法
  • 明确什么是共享数据
  • 明确多线程运行代码中哪些语句是操作共享数据的
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
35
36
37
38
39
40
41
42
43
//银行账户
class Account{
double balance; //余额
public Account(){}
//存钱
public synchronized void deposit(double amt) {
balance += amt;
try {
Thread.sleep(10);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + balance);
}
}
//储户
class Customer implements Runnable{
Account account;

public Customer(Account account){
this.account = account;
}

@Override
public void run() {
for (int i = 0; i < 3; i++){
account.deposit(1000);
}

}
}
public class TestAccount {
public static void main(String[] args) {
Account account = new Account();
Customer customer = new Customer(account);
Thread thread1 = new Thread(customer);
Thread thread2 = new Thread(customer);
thread1.setName("甲");
thread2.setName("乙");
thread1.start();
thread2.start();
}
}

拓展问题:可否实现两个储户交替存钱的操作。需要使用线程通信!

线程死锁

  • 死锁

    • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
  • 解决方法

    • 专门的算法、原则
    • 尽量减少同步资源的定义

线程通信

wait() notify() notifyAll()

  • wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问
  • notify()唤醒正在排队等待同步资源的线程中优先级最高者结束等待
  • notifyAll()唤醒正在排队等待资源的所有线程结束等待

​ Java.lang.Object提供的这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。

wait() 方法

  1. 在当前线程中调用方法: 对象名.wait()
  2. 使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify (或notifyAll) 为止
  3. 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
  4. 调用此方法后,当前线程将释放对象监控权 ,然后进入等待
  5. 在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行

notify()和notifyAll()

  1. 在当前线程中调用方法: 对象名.notify()
  2. 功能:唤醒等待该对象监控权的一个线程。
  3. 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)

练习

​ 使用两个线程打印 1-100,线程1, 线程2交替打印。

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
35
36
37
38
39
40
41
42
/**
* 线程通信
* 使用两个线程打印 1-100,线程1, 线程2交替打印。
**/
class PrintNum implements Runnable{
int num = 1;

@Override
public void run() {
while (true){
synchronized (this) {
notify();
if (num <= 100) {
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
} else
break;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class TestCommunication {
public static void main(String[] args) {
PrintNum p = new PrintNum();
Thread thread1 = new Thread(p);
Thread thread2 = new Thread(p);
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();
}
}

生产者/消费者问题

  • 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
  • 这里可能出现两个问题:
    • 生产者比消费者快时,消费者会漏掉一些数据没有取到。
    • 消费者比生产者快时,消费者会取相同的数据。
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
public class TestProduceConsume {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
Thread thread1 = new Thread(producer);
Thread thread2 = new Thread(consumer);
Thread thread3 = new Thread(producer);
thread1.setName("生产者1");
thread2.setName("消费者");
thread3.setName("生产者2");
thread1.start();
thread2.start();
thread3.start();
}
}

//店员
class Clerk {
int product;

//生产者生产商品
public synchronized void addProduct(){
if (product >= 20){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
product ++;
System.out.println(Thread.currentThread().getName() + ":生产了第" + product + "个产品");
notifyAll();
}
}

//消费者消费商品
public synchronized void consumeProduct(){
if (product <= 0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
System.out.println(Thread.currentThread().getName() + ":消费了第" + product + "个产品");
product --;
notifyAll();
}
}

}

//生产者
class Producer implements Runnable{
Clerk clerk;

public Producer(Clerk clerk) {
this.clerk = clerk;
}

@Override
public void run() {
System.out.println("生产者开始生产产品");
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.addProduct();
}
}
}

//消费者
class Consumer implements Runnable{
Clerk clerk;

public Consumer(Clerk clerk) {
this.clerk = clerk;
}

@Override
public void run() {
System.out.println("消费者开始消费产品");
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}

面试题

  • java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用?
    答:有两种实现方法,分别是继承Thread类与实现Runnable接口
    用synchronized关键字修饰同步方法
    反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被”挂起”的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。

  • sleep() 和 wait() 有什么区别?
    答:sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁
    wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

  • 同步和异步有何异同,在什么情况下分别使用他们?举例说明。
    答:如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个
    线程写过了,那么这些数据就是共享数据,必须进行同步存取。
    当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

  • 启动一个线程是用run()还是start()?
    答:启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。

  • 当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
    答:不能,一个对象的一个synchronized方法只能由一个线程访问。

  • 请说出你所知道的线程同步的方法。
    答:wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
    sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
    notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
    notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

  • 多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么?
    答:多线程有两种实现方法,分别是继承Thread类与实现Runnable接口
    同步的实现方面有两种,分别是synchronized,wait与notify

  • 线程的基本概念、线程的基本状态以及状态之间的关系
    答:线程指在程序执行过程中,能够执行程序代码的一个执行单位,每个程序至少都有一个线程,也就是程序本身。
    Java中的线程有四种状态分别是:运行、就绪、挂起、结束

  • 简述synchronized和java.util.concurrent.locks.Lock的异同 ?
    答:主要相同点:Lock能完成synchronized所实现的所有功能
    主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求
    程序员手工释放,并且必须在finally从句中释放。

  • 判断题
    1. C 和 Java 都是多线程语言。(错,C是单线程)
    2. 如果线程死亡,它便不能运行。(对)
    3. 在 Java 中,高优先级的可运行线程会抢占低优先级线程。(对)
    4. 程序开发者必须创建一个线程去管理内存的分配。(错,Java 提供了一个系统线程来管理内存的分配。)
    5. 一个线程在调用它的 start 方法,之前,该线程将一直处于出生期。(对)
    6. 当调用一个正在进行线程的 stop()方法时,该线程便会进入休眠状态。(错,应该是sleep()方法)
    7. 如果线程的 run 方法执行结束或抛出一个不能捕获的例外,线程便进入等待状态。(错,线程会死亡)
    8. 一个线程可以调用 yield 方法使其他线程有机会运行。(对,yield 方法总是让高优先级的就绪线程先运行)

  • 选择题
    1. Java 语言中提供了一个▁D▁线程,自动回收动态分配的内存。
      A 异步
      B 消费者
      C 守护
      D 垃圾收集
    2. 当▁A▁方法终止时,能使线程进入死亡状态。
      A run
      B setPrority
      C yield
      D sleep
    3. 用▁B▁方法可以改变线程的优先级。
      A run
      B setPrority
      C yield
      D sleep
    4. 线程通过▁C▁方法可以使具有相同优先级线程获得处理器。
      A run
      B setPrority
      C yield
      D sleep
    5. 线程通过▁D▁方法可以休眠一段时间,然后恢复运行。
      A run
      B setPrority
      C yield
      D sleep
    6. ▁B▁方法使对象等待队列的第一个线程进入就绪状态。
      A run
      B notify
      C yield
      D sleep
    7. 方法 resume( )负责重新开始▁D▁线程的执行。
      A 被 stop( )方法停止
      B 被 sleep( )方法停止
      C 被 wait( )方法停止
      D 被 suspend( )方法停止
    8. ▁BCD▁方法可以用来暂时停止当前线程的运行。
      A stop( )
      B sleep( )
      C wait( )
      D suspend()

  • Java为什么要引入线程机制,线程、程序、进程之间的关系是怎样的。

    答:线程可以彼此独立的执行,它是一种实现并发机制的有效手段,可以同时使用多个线程来完成不同的任务,并且一般用户在使用多线程时并不考虑底层处理的细节。
    程序是一段静态的代码,是软件执行的蓝本。进程是程序的一次动态执行过程,即是处于运行过程中的程序。
    线程是比进程更小的程序执行单位,一个进程可以启动多个线程同时运行,不同线程之间可以共享相同的内存区域和数据。多线程程序是运行时间后嗣可能出现在一个进程之内的、有一个以上线程同时运行的情况的程序。


  • Runnable接口包括哪些抽象方法?Thread类有哪些主要域和方法?

    答:Runnable接口中仅有run()抽象方法。
    Thread类主要域有:MAX_PRIORITY,MIN_PRIORITY,NORM_PRIORITY
    主要方法有start(),run(),sleep(),currentThread(),setPriority(),getPriority(),join()等。


  • 创建线程有哪两种方式?试写出每种的具体的流程。比较两种创建方式的不同,哪个更优。
    1. 继承Thread类
      • 定义类继承Thread类
      • 覆盖Thread类中的run方法。
      • 创建Thread子类对象,即创建了线程对象。
      • 调用线程对象start方法:启动线程,调用run方法。
    2. 实现Runnable接口
      • 定义类,实现Runnable接口。
      • 覆盖Runnable接口中的run方法。
      • 通过Thread类建立线程对象。
      • 将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中。
      • 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
    3. 【区别】
      • 继承Thread: 线程代码存放Thread子类run方法中
      • 实现Runnable:线程代码存在接口的子类的run方法
    4. 【实现方法的好处】
      • 避免了单继承的局限性
      • 多个线程可以共享同一个接口子类的对象,非常适合多个相同线程来处理同一份资源。