概念

进程

线程

创建线程两种方法

在Thread子类覆盖的run方法中编写运行代码

public class NumberThread extends Thread

Tread线程类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Thread extends Object implements Runnable
{
public Thread() //构造方法
public Thread(String name) //name指定线程名
public Thread(Runnable target) //target指定线程的目标对象
public Thread(Runnable target, String name)
public void run() //描述线程操作的线程体
public final String getName() //返回线程名
public final void setName(String name) //设置线程名
public static int activeCount() //返回当前活动线程个数
public static Thread currentThread() //返回当前执行线程对象
public Sting toString() //返回线程的字符串信息
public void start() //启动已创建的线程对象
}

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Thread thread = new Thread() {
@Override
public void run() {
//希望代码长期运行下去就编写在一个循环里面
while (true) {
try {
//线程睡眠
Thread.sleep(500);
} catch (InterruptedException e) {

e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}

};
//启动线程
thread.start();

涉及一个以往知识点:能否在run方法声明上抛出InterruptedException异常,以便省略run方法内部对Thread.sleep()语句的try…catch处理?
不行,子类不能抛出比父类更多的异常

在传递给Thread对象的Runnable对象的run方法中编写代码

public class NumberRunnable implements Runnable

Runnable接口

1
2
3
4
public interface Runnable
{
public abstract void run();
}

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Thread	 thread2=new Thread(new Runnable() {
@Override
public void run() {
//希望代码长期运行下去就编写在一个循环里面
while (true) {
try {
//线程睡眠
Thread.sleep(500);
} catch (InterruptedException e) {

e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}

}
});
thread2.start();
}

问题:如果在Thread子类覆盖的run方法中编写了运行代码,也为Thread子类对象传递了一个Runnable对象,那么,线程运行时的执行代码是子类的run方法的代码?还是Runnable对象的run方法的代码?
子类的,因为子类已经把父类的run方法覆盖了,会以子类的run方法为准。为什么没有执行runnable对象?因为执行该对象的代码在父类中,现在父类Thread的代码被子类覆盖已经没有了,所以不可能执行父类中找runnable对象的方法

线程的新建、启动

Thread类创建多线程的步骤

1.继承Thread类
2.重写run方法,将需要在线程的功能在这个方法里实现
3.创建线程类实例
4.调用实例的start方法

Runnable类创建多线程的步骤

1.实现runnable接口
2.重写run方法,将需要在线程实现的功能在这个方法完成
3.使用实现了runnable接口的类创建实例,即线程体
4.使用Thread创建线程实例,将第3步创建的runnable对象实例作为构造方法参数
5.调用Thread实例的start方法启动线程,执行run方法


让当前线程( 调用sleep()方法的线程 )休息一会,即是让当前线程由运行状态进入到阻塞状态,进而使其他线程有机会继续执行任务。虽然使线程休眠,但是并不释放对象锁,所以说如果在同步块中使用sleep(),其他线程仍然无法获得执行权限。
注意:sleep()方法定义在Thread类中,会调用sleep(millis)这个本地方法,抛出InterruptedException异常,因此需要捕获该异常。

线程的同步机制

交互的并发线程是指它们共享某些变量。
线程安全问题(代码的原子性:有个线程来执行我的时候,别的线程就不能来执行我)(多个线程在操作共享的数据;操作共享数据的线程代码有多条。当一个线程在执行操作共享数据的多条代码过程中,其它线程参与了运算,就会导致线程安全问题的产生。)

竞争关系的交互线程间需要采用线程互斥方式解决共享资源冲突问题。
协作关系的交互线程间需要采用线程同步方式解决线程间通信及因执行速度不同而引起的不同步问题。

线程互斥

使用synchronized代码块及其原理(一段代码或两段代码被两个线程执行时要互斥,则需用synchronized代码块包围起来)
使用synchronized方法
分析静态方法所使用的同步监视器对象(锁)是什么?


临界资源:多线程共享变量代表的资源称为临界资源;
临界区:并发线程中与共享变量有关的程序段称为临界区
有以下3个调度原则:

  • 一次至多一个线程能够在它的临界区内。
  • 不能让一个线程无限地留在它的临界区内。
  • 不能强迫一个线程无限地等待进入它的临界区。特别地,进入临界区的任一线程不能妨碍正等待进入的其他线程的进展。
    基于操作系统对于线程进入临界区的3条原则,可以使用同步语句和同步方法来实现线程互斥。

同步语句

synchronized (对象) //对象即临界资源
语句块//语句块为临界区

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
//【例7.6】  互斥的存取款线程设计。
public class AccountLock {
public static void main(String args[]) throws InterruptedException {
Account wang = new Account("Wang");
Thread thread = new SaveLock(wang,100);
thread.start();

(new SaveLock(wang,200)).start();
(new FetchLock(wang,300)).start();

(new SaveLock(new Account("Li"),100)).start();
}

}

class SaveLock extends Thread // 带互斥锁的存款线程类
{
private Account account; // 账户
private double value; // 存款金额

public SaveLock(Account c, double value) {
this.account = c;
this.value = value;
}

public void run() {
synchronized (this.account) // 声明临界区,锁定指定账户对象
{
double howmatch = this.account.getBalance();
try {
Thread.sleep(1); // 花费时间
} catch (InterruptedException e) {
}
this.account.put(this.value);
System.out.println(this.account.getName() + "账户:现有" + howmatch + ", 存入" + this.value + ", 余额"
+ this.account.getBalance());
}
}
}

class FetchLock extends Thread // 带互斥锁的取款线程类
{
private Account account;
private double value;

public FetchLock(Account c, double value) {
this.account = c;
this.value = value;
}

public void run() {
synchronized (this.account) // 声明临界区,锁定指定账户对象
{
double howmatch = this.account.getBalance();
try {
Thread.sleep(1); // 花费时间
} catch (InterruptedException e) {
}
System.out.println(this.account.getName() + "账户:现有" + howmatch + ", 取走" + this.account.get(this.value)
+ ", 余额" + this.account.getBalance());
}
}

}

同步方法

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
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
public class MethodLock {
public static void main(String args[]) throws InterruptedException {
AccountMethodLock wang = new AccountMethodLock("Wang");
SaveMLock saveTarget1 = new SaveMLock(wang, 100);
Thread thread = new Thread(saveTarget1);
thread.start();

new Thread(new SaveMLock(wang, 200)).start();
new Thread(new FetchMLock(wang, 300)).start();

new Thread(new SaveMLock(new AccountMethodLock("Li"), 100)).start();

}
}

class SaveMLock implements Runnable // 带互斥锁的存款线程类
{
private AccountMethodLock account; // 账户
private double value; // 存款金额

public SaveMLock(AccountMethodLock c, double value) {
this.account = c;
this.value = value;
}

public void run() {
this.account.put(value);
}

}

class FetchMLock implements Runnable // 带互斥锁的取款线程类
{
private AccountMethodLock account;
private double value;

public FetchMLock(AccountMethodLock c, double value) {
this.account = c;
this.value = value;
}

public void run() {
this.account.get(value);
}

}

class AccountMethodLock // 账户类
{
private String name; // 储户姓名
private double balance; // 账户余额

public AccountMethodLock(String name) {
this.name = name;
this.balance = 0;
}

public String getName() // 返回账户名
{
return name;
}

public double getBalance() // 查看账户余额
{
return balance;
}

public synchronized void put(double value) // 存款操作,参数为存入金额
{
double howmatch = this.getBalance();
if (value > 0)
this.balance += value; // 存款操作使余额值增加

try {
Thread.sleep(1); // 花费时间
} catch (InterruptedException e) {
}
System.out.println(this.getName() + "账户:现有" + howmatch + ", 存入" + value + ", 余额" + this.getBalance());
}

public synchronized void get(double value) // 取款操作,参数为取款金额,返回实际取到金额
{
double howmatch = this.getBalance();

if (value > 0) {
if (value <= this.balance)
this.balance -= value; // 取款操作使余额值减少
else // 账户余额不够所取时
{
value = this.balance; // 取走全部余额
this.balance = 0;
}
System.out.println(this.getName() + "账户:现有" + howmatch + ", 取走" + value + ", 余额" + this.getBalance());
}
}
}

线程同步

多线程协调运行的原则就是:当条件不满足时,线程进入等待状态;当条件满足时,线程被唤醒,继续执行任务。
必须在synchronized块中才能调用wait()方法,因为wait()方法调用时,会释放线程获得的锁,wait()方法返回后,线程又会重新试图获得锁。因此,只能在锁对象上调用wait()方法。因为在getTask()中,我们获得了this锁,因此,只能在this对象上调用wait()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public synchronized String getTask() {
while (queue.isEmpty()) {
// 释放this锁:
this.wait();
// 重新获取this锁
}
return queue.remove();
}

public synchronized void addTask(String s) {
this.queue.add(s);
this.notify(); // 唤醒在this锁等待的线程
}

wait和notify用于多线程协调运行:

  • 在synchronized内部可以调用wait()使线程进入等待状态;
  • 必须在已获得的锁对象上调用wait()方法;
  • 在synchronized内部可以调用notify()或notifyAll()唤醒其他等待线程;
  • 必须在已获得的锁对象上调用notify()或notifyAll()方法;
  • 已唤醒的线程还需要重新获得锁后才能继续执行。