线程的编号和名称

  • 相关方法

    long getId() - 用于获取调用对象所表示线程的编号.

    String getName() - 用于获取调用对象所表示线程的名称.

    void setName(String name) - 用于设置线程的名称为参数指定的数值.

    static Thread currentThread() - 获取当前正在执行线程的引用。

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
public class ThreadIdNameTest extends Thread {
public ThreadIdNameTest(String string) {
super(string); // 调用父类的有参构造方法
}
@Override
public void run() {
System.out.println("子线程的编号是:" + getId()
+ ",子线程的名称是:" + getName()); // 10 Thread-0 guanyu
// 修改一下线程的名称
setName("zhangfei");
System.out.println("修改后子线程的编号是:" + getId()
+ ",子线程的名称是:" + getName()); // 10 zhangfei
}

public static void main(String[] args) {
Thread t1 = new ThreadIdNameTest("guanyu");
t1.start();
System.out.println("------------------------------------");

// 获取主线程的线程对象
// 获取当前正在执行线程的线程对象,当前正在执行的线程是主线程
Thread t2 = Thread.currentThread();
System.out.println("主线程的编号是:" + t2.getId()
+ ",名称是:" + t2.getName());
}
}
  • 常用方法(1)

    static void yield() - 当前线程让出处理器(离开running状态),使当前线程进入runnable状态等待.

    static void sleep(times)
    使当前线程从running放弃处理器进入block状态,休眠times毫秒,再返回到runnable;如果其他线程打断当前线程的block(sleep),就会发生interruptedException.

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
import java.text.SimpleDateFormat;
import java.util.Date;
public class ThreadSleepTest extends Thread {
private boolean flag = true;
// 子类重写的方法不能抛出更大的异常,Thread类中的run方法没有抛出异常
@Override
public void run() {
// 实现时钟的模拟,也就是每隔一秒打印一下系统时间
while(flag) {
// 获取当前系统时间
Date d1 = new Date();
// 调整一下输出格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 打印获取到的时间
System.out.println(sdf.format(d1));
// 休眠1秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

public static void main(String[] args) {
ThreadSleepTest tst = new ThreadSleepTest();
tst.start();

System.out.println("主线程开始等待...");
// 让主线程睡眠5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//tst.stop(); 过时的方法 不建议使用
tst.flag = false;
System.out.println("主线程等待结束!");
}
}
  • 常用方法(2)

    void join() - 等待该线程终止.

    void join(long millis) - 表示等待参数指定的毫秒数.

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
public class ThreadJoinTest extends Thread {
@Override
public void run() {
// 模拟大年三十倒数10个数
System.out.println("倒计时开始!");
for(int i = 10; i > 0; i--) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("新年快乐!!!");
}

public static void main(String[] args) {
ThreadJoinTest tjt = new ThreadJoinTest();
tjt.start();
System.out.println("主线程开始等待...");
try {
// 表示当前正在执行的线程等待调用join方法的线程终止
// 也就是主线程在等待子线程终止
//tjt.join(); // 一直等下去
tjt.join(5000); // 等待5秒钟
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//System.out.println("终于等到你,还好没放弃!");
System.out.println("Say GoodBye!");
}
}
  • 常用方法(3)

    boolean isDaemon() - 用于判断是否为守护线程.

    void setDaemon(boolean on) - 用于设置线程为守护线程.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ThreadDaemonTest extends Thread {
@Override
public void run() {
// 当创建一个新线程时,该线程默认不是守护线程
// 当子线程不是守护线程时,主线程结束后子线程依然会将所有整数打印完毕
// 当子线程是守护线程后,主线程结束后子线程会随之结束
System.out.println(isDaemon()? "子线程是守护线程": "子线程不是守护线程");
for(int i = 0; i < 50; i++) {
System.out.println("子线程中:i = " + i);
}
}

public static void main(String[] args) {
ThreadDaemonTest tdt = new ThreadDaemonTest();
// 设置子线程为守护线程
tdt.setDaemon(true);
tdt.start();
for(int i = 0; i < 20; i++) {
System.out.println("----------主线程中:i = " + i);
}
}
}

线程的同步机制(重点)

  • 基本概念

    当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,为了解决上述问题就需要对线程之间进行协调,而线程之间的通信和协调就叫线程同步机制

如:
03年左右 银行卡机制 开户 => 存折 和 银行卡

  • 解决方案

    由程序结果可知:当两个线程同时进行取款操作后,导致最终的账户余额不合理。

    引发的原因:线程一还没有执行完毕取款操作,线程二就已经开始取款。

    解决方案: 让线程一执行完毕取款后线程二再进行取款,也就是将线程的并发操作改为串行操作

    注意事项: 若两个线程依次启动执行取款操作,则多线程的意义便不复存在。多线程的意义是同时执行多个任务,因此尽量减少串行的范围。

线程的同步实现

  • 死锁的概念

    当两个线程或多个线程之间互相锁定是就形成了死锁.

线程一执行的代码:

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
public class Account implements Runnable{
private int balance;
Demo dm = new Demo();

public Account() {
super();
}
public Account(int balance) {
super();
setBalance(balance);
}

public int getBalance() {
return balance;
}
public void setBalance(int balance) {
if(balance >= 0) {
this.balance = balance;
} else {
System.out.println("金额不合理!!!");
}
}

@Override
public synchronized void run() {
// 在成员方法中出现this表示当前正在调用的对象,也就是acc
//synchronized(this) {
System.out.println("线程" + Thread.currentThread().getName() + "启动...");
//synchronized(dm) { // ok
//synchronized(new Demo()) { // error
// 模拟从银行后台数据库查询账户余额的过程
int temp = getBalance(); // temp = 1000; temp = 1000;
// 模拟取款的操作
if(temp >= 200) {
System.out.println("正在出钞,请稍后...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
temp -= 200; // temp = 800; temp = 800;
System.out.println("请取走您的钞票!");
} else {
System.out.println("账户余额不足,请核对您的账户余额!");
}
// 模拟将最新的余额写入后台数据库
setBalance(temp); //balance = 800; balance = 800;
//}
}
}
class Demo {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class AccountTest {
public static void main(String[] args) {
Account acc = new Account(1000);
//Account acc2 = new Account(10000);
Thread t1 = new Thread(acc);
Thread t2 = new Thread(acc);
//Thread t2 = new Thread(acc2);
t1.start();
t2.start();

System.out.println("主线程开始等待...");
try {
t1.join();
//t2.start();
t2.join();
//acc.run();
//acc.run();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("当前账户的余额是:" + acc.getBalance()); // 600
}
}