线程-1

​ 线程的使用,第一部分

1.多线程理解

​ 如果程序只有一条执行路径,那么该程序是单线程程序。如果该程序有多条执行路径,那么该程序是多线程程序。

1

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
1:要想了解多线程,必须先了解线程,而要想了解线程,必须先了解进程,因为线程是依赖于进程而存在。

2:什么是进程?
通过任务管理器我们就看到了进程的存在。
而通过观察,我们发现只有运行的程序才会出现进程。
进程:就是正在运行的程序。
进程是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。

3:多进程有什么意义呢?
单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。
举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。
也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。
并且呢,可以提高CPU的使用率。

问题:
一边玩游戏,一边听音乐是同时进行的吗?
不是。因为单CPU在某一个时间点上只能做一件事情。
而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。

4:什么是线程呢?
在同一个进程内又可以执行多个任务,而这每一个任务我就可以看出是一个线程。
线程:是程序的执行单元,执行路径。是程序使用CPU的最基本单位。
单线程:如果程序只有一条执行路径。
多线程:如果程序有多条执行路径。

5:多线程有什么意义呢?
多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率。
程序的执行其实都是在抢CPU的资源,CPU的执行权。
多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权。
我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性。
而多线程却给了我们一个错觉:让我们认为多个线程是并发执行的。其实不是。
因为多个线程共享同一个进程的资源(堆内存和方法区),但是栈内存是独立的,一个线程一个栈。
所以他们仍然是在抢CPU的资源执行。一个时间点上只有能有一个线程执行。而且谁抢到,这个不一定,所以,造成了线程运行的随机性。

6:那么什么又是并发呢?
大家注意两个词汇的区别:并行和并发。
前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
后者是物理上同时发生,指在某一个时间点同时运行多个程序。
那么,我们能不能实现真正意义上的并发呢,是可以的,多个CPU就可以实现,不过你得知道如何调度和控制它们。

总结:

  • 进程:
    • 正在运行的程序,是系统进行资源分配和调用的独立单位。
    • 每一个进程都有它自己的内存空间和系统资源。
  • 线程:
    • 是进程中的单个顺序控制流,是一条执行路径
    • 一个进程如果只有一条执行路径,则称为单线程程序。
    • 一个进程如果有多条执行路径,则称为多线程程序。

Java程序的运行原理:

​ 由java命令启动JVM,JVM启动就相当于启动了一个进程。接着有该进程创建了一个主线程去调用main方法。

jvm虚拟机的启动是单线程的还是多线程的?

​ 多线程的。

​ 原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出。现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。

2.实现多线程的程序

2-1.如何实现呢?

​ 由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。Java可以去调用写好的程序来实现多线程程序。由JAVA去调用这样的东西,然后提供一些类供我们使用。我们就可以实现多线程程序了。

2-2.那么Java提供的类是什么呢?

​ Thread。通过查看API,我们知道了有2中方式实现多线程程序。

方式1:继承Thread类。

步骤:

  • A:自定义类MyThread继承Thread类。
  • B:MyThread类里面重写run()
  • C:创建对象
  • D:启动线程

问题1:该类要重写run()方法,为什么呢?

​ 不是类中的所有代码都需要被线程执行的。而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。

问题2:调用run()方法为什么是单线程的呢?

​ 因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果要想看到多线程的效果,就必须说说另一个方法:start()。

面试题:run()和start()的区别?

  • run():仅仅是封装被线程执行的代码,直接调用是普通方法
  • start():首先启动了线程,然后再由jvm去调用该线程的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
public class MyThread extends Thread {
@Override
public void run() {
// 一般来说,被线程执行的代码肯定是比较耗时的。所以我们用循环改进
for (int x = 0; x < 200; x++) {
System.out.println(x);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
// 创建线程对象
MyThread my = new MyThread();
// 启动线程
my.run();
my.run();
// MyThread my = new MyThread();
// my.start();
// IllegalThreadStateException:非法的线程状态异常
// 为什么呢?
//因为这个相当于是my线程被调用了两次。而不是两个线程启动。
// my.start();
// 创建两个线程对象
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.start();
my2.start();
}
}

方式2:实现Runnable接口

步骤:

  • 自定义类MyRunnable实现Runnable接口
  • 重写run()方法
  • 创建MyRunnable类的对象
  • 创建Thread类的对象,并把C步骤的对象作为构造参数传递

实现接口方式的好处:

  • 可以避免由于Java单继承带来的局限性。
  • 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。

代码:

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
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
// 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用
// getName()不能直接用
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
public class MyRunnableDemo {
public static void main(String[] args) {
// 创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
// 创建Thread类的对象,并把C步骤的对象作为构造参数传递
// Thread(Runnable target)
// Thread t1 = new Thread(my);
// Thread t2 = new Thread(my);
// t1.setName("t1");
// t2.setName("t2");
// 方法使用
// Thread(Runnable target, String name)
Thread t1 = new Thread(my, "t1");
Thread t2 = new Thread(my, "t2");
t1.start();
t2.start();
}
}

3

线程名称

  • 如何获取线程对象的名称呢?
    • public final String getName() 获取线程的名称。
  • 如何设置线程对象的名称呢?
    • public final void setName(String name) 设置线程的名称

代码:

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
public class MyThread extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
// 创建线程对象
//无参构造+setXxx()
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
//调用方法设置名称
my1.setName("林青霞");
my2.setName("刘意");
my1.start();
my2.start();
//不用setName,会得到Thread-0
}
}
//名称为什么是:Thread-? 编号。 调用Thread的init方法设置的
/*
class Thread {
private char name[];
//名字
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
}
class MyThread extends Thread {
public MyThread() {
super();
}
}
*/

方式二

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 MyThread extends Thread {
//一定要
public MyThread() {
}
//一定要
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
带参构造方法给线程起名字
MyThread my1 = new MyThread("林青霞");
MyThread my2 = new MyThread("刘意");
my1.start();
my2.start();
}
}

问题1:针对不是Thread类的子类中如何获取线程对象名称呢?

  • public static Thread currentThread() 返回当前正在执行的线程对象
  • Thread.currentThread().getName()
1
System.out.println(Thread.currentThread().getName());

线程调度

​ 假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?

线程有两种调度模型:

  • 分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
  • 抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
  • Java使用的是抢占式调度模型。

如何设置和获取线程优先级?

线程没有设置优先级,肯定有默认优先级。线程默认优先级是5。

获取线程对象的优先级:

  • public final int getPriority():返回线程对象的优先级

设置线程对象的优先级:

  • public final void setPriority(int newPriority):更改线程的优先级。

注意:

​ 线程优先级的范围是:1-10。

​ 线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。因为,线程有随机性。

​ IllegalArgumentException 非法参数异常。

​ 抛出的异常表明向方法传递了一个不合法或不正确的参数。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ThreadPriorityDemo {
public static void main(String[] args) {
ThreadPriority tp1 = new ThreadPriority();
ThreadPriority tp2 = new ThreadPriority();
ThreadPriority tp3 = new ThreadPriority();
tp1.setName("tp1");
tp2.setName("tp2");
tp3.setName("tp3");
// 获取默认优先级
System.out.println(tp1.getPriority());
System.out.println(tp2.getPriority());
System.out.println(tp3.getPriority());
// 设置线程优先级
// tp1.setPriority(100000);
//出异常,优先级太大
//设置正确的线程优先级
tp1.setPriority(10);
tp2.setPriority(1);
tp1.start();
tp2.start();
tp3.start();
}
}

线程控制-睡眠

​ public static void sleep(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
public class ThreadSleep extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x + ",日期:" + new Date());
//休息1秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadSleepDemo {
public static void main(String[] args) {
ThreadSleep ts1 = new ThreadSleep();
ThreadSleep ts2 = new ThreadSleep();
ThreadSleep ts3 = new ThreadSleep();
ts1.setName("ts1");
ts2.setName("ts2");
ts3.setName("ts3");
ts1.start();
ts2.start();
ts3.start();
}
}

线程控制-加入

​ public final void join():等待该线程终止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//ThreadJoin为普通继承Thread
public class ThreadJoinDemo {
public static void main(String[] args) {
ThreadJoin tj1 = new ThreadJoin();
ThreadJoin tj2 = new ThreadJoin();
ThreadJoin tj3 = new ThreadJoin();
tj1.setName("tj1");
tj2.setName("tj2");
tj3.setName("tj3");
//直到tj1结束,tj2,tj3才开始
tj1.start();
try {
tj1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
tj2.start();
tj3.start();
}
}

线程控制-礼让

​ public static void yield()

​ 暂停当前正在执行的线程对象,并执行其他线程。 让多个线程的执行更和谐,但是不能靠它保证一人一次。(后面会完善用法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ThreadYield extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
Thread.yield();
}
}
}
public class ThreadYieldDemo {
public static void main(String[] args) {
ThreadYield ty1 = new ThreadYield();
ThreadYield ty2 = new ThreadYield();
ty1.setName("ty1");
ty2.setName("ty2");
ty1.start();
ty2.start();
}
}

线程控制-后台线程

​ public final void setDaemon(boolean on)

​ 将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//ThreadDaemon为普通继承Thread
public class ThreadDaemonDemo {
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();
td1.setName("td1");
td2.setName("td2");
// 设置收获线程
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
//当main线程结束,td1和td2线程也会结束,但会相应走一段,结束,但没那么快
Thread.currentThread().setName("main");
for (int x = 0; x < 5; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}

线程控制-中断线程

  • public final void stop()

    • 让线程停止,过时了,但是还可以使用。
  • public void interrupt()

    • 中断线程,方法:把线程的状态终止,并抛出一个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
public class ThreadStop extends Thread {
@Override
public void run() {
System.out.println("开始执行:" + new Date());
// 休息10秒钟
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("线程被终止了");
}
System.out.println("结束执行:" + new Date());
}
}
public class ThreadStopDemo {
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();
// 超过三秒不醒过来,终止
try {
Thread.sleep(3000);
//过时
// ts.stop();
ts.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

线程的生命周期

  • 新建:创建线程对象
  • 就绪:有执行资格,没有执行权
  • 运行:有执行资格,有执行权
  • 阻塞:由于一些操作让线程处于该状态。没有执行状态,没有执行权,而另一些操作却可以把它给激活,激活后处于就绪状态
  • 死亡:线程对象变成垃圾,等待被回收

2

3.线程练习

​ 某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。

两种方式实现:

继承Thread类

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
public class SellTicket extends Thread {
// 定义100张票
// private int tickets = 100;
// 为了让多个线程对象共享这100张票,用静态修饰
private static int tickets = 100;
@Override
public void run() {
// 每个线程进来都会走这里,这样的话,每个线程对象相当于买的是自己的那100张票,这不合理,所以应该定义到外面
// int tickets = 100;
// 是为了模拟一直有票
while (true) {
if (tickets > 0) {
System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
// 创建三个线程对象
SellTicket st1 = new SellTicket();
SellTicket st2 = new SellTicket();
SellTicket st3 = new SellTicket();
// 给线程对象起名字
st1.setName("窗口1");
st2.setName("窗口2");
st3.setName("窗口3");
// 启动线程
st1.start();
st2.start();
st3.start();
}
}

​ 会有错误,后面修订

实现Runnable接口

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
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "张票");
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}

​ 相比较之下,Runnable接口的方式产生错误会比继承Thread少,但还是会有,后面修订

问题与解决

​ 电影院售票程序,从表面上看不出什么问题,但是在真实生活中,售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟

​ 接口方式的卖票程序:每次卖票延迟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
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
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
//问题-1:相同的票卖了多次
@Override
public void run() {
while (true) {
// t1,t2,t3三个线程
// 这一次的tickets = 100;
if (tickets > 0) {
// 模拟真实的场景,稍作休息
try {
Thread.sleep(100); // t1就稍作休息,t2就稍作休息
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "张票");
// 理想状态:
// 窗口1正在出售第100张票
// 窗口2正在出售第99张票
// 但是呢?
// CPU的每一次执行必须是一个原子性(最简单基本的)的操作。
// 先记录以前的值
// 接着把ticket--
// 然后输出以前的值(t2来了)
// ticket的值就变成了99
// 窗口1正在出售第100张票
// 窗口2正在出售第100张票
}
}
}
//问题-2:出现了负数票
@Override
public void run() {
while (true) {
// t1,t2,t3三个线程
// 这一次的tickets = 1;
if (tickets > 0) {
// 为了模拟更真实的场景,我们稍作休息
try {
Thread.sleep(100); //t1进来了并休息,t2进来了并休息,t3进来了并休息,
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "张票");
//窗口1正在出售第1张票,tickets=0
//窗口2正在出售第0张票,tickets=-1
//窗口3正在出售第-1张票,tickets=-2
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}

通过加入延迟后,就产生了两个问题:

  • A:相同的票卖了多次
    • CPU的一次操作必须是原子性的
  • B:出现了负数票
    • 随机性和延迟导致的

线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的

​ 要想解决问题,就要知道哪些原因会导致出问题:(而且这些原因也是以后我们判断一个程序是否会有线程安全问题的标准)

  1. 是否是多线程环境
  2. 是否有共享数据
  3. 是否有多条语句操作共享数据

解决思想:

​ 把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行。Java给我们提供了:同步机制。

同步代码块:

​ 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
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
// 定义同一把锁
private Object obj = new Object();
@Override
public void run() {
while (true) {
// t1,t2,t3都能走到这里
// 假设t1抢到CPU的执行权,t1就要进来
// 假设t2抢到CPU的执行权,t2就要进来,发现门是关着的,进不去。所以就等着。
// 门(开,关)
synchronized (obj) { // 发现这里的代码将来是会被锁上的,所以t1进来后,就锁了。(关)
if (tickets > 0) {
try {
Thread.sleep(100); // t1就睡眠了
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
//窗口1正在出售第100张票
}
} //t1就出来可,然后就开门。(开)
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}

4.同步

同步的特点:

  • 前提:多个线程
  • 解决问题的时候要注意:多个线程使用的是同一个锁对象
  • 同步的好处 :同步的出现解决了多线程的安全问题。
  • 同步的弊端: 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

同步的各种问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private Object obj = new Object();
//同步代码块用obj做锁
@Override
public void run() {
while (true) {
synchronized (obj) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
}
}
}
  • 同步代码块的锁对象是:任意对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private Demo d = new Demo();
//同步代码块用任意对象做锁
@Override
public void run() {
while (true) {
synchronized (d) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
}
}
}
class Demo {}
  • 同步方法的格式及锁对象:把同步关键字加在方法上。

  • 同步方法是: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
@Override
public void run() {
while (true) {
if(x%2==0){
synchronized (this) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
}
}else {
sellTicket();
}
x++;
}
}
private synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
}
  • 静态方法及锁对象是:类的字节码文件对象。(反射)(要比静态先存在,class文件)
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
private static int tickets = 100;
@Override
public void run() {
while (true) {
if(x%2==0){
synchronized (SellTicket.class) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
}
}else {
sellTicket();

}
x++;
}
}
private static synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
}

如果锁对象是this,就可以考虑使用同步方法。否则能使用同步代码块的尽量使用同步代码块

5.以前的线程安全的类

线程安全:StringBuffer、Vector、Hashtable
如何把一个线程不安全的集合类变成一个线程安全的集合类?
用Collections工具类的方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ThreadDemo {
public static void main(String[] args) {
// 线程安全的类
// 里面有些方法加synchronized
StringBuffer sb = new StringBuffer();
Vector<String> v = new Vector<String>();
Hashtable<String, String> h = new Hashtable<String, String>();

// Vector是线程安全的时候才去考虑使用的,但是我还说过即使要安全,我也不用你
// 那么到底用谁呢?
// public static <T> List<T> synchronizedList(List<T> list)
List<String> list1 = new ArrayList<String>();// 线程不安全
List<String> list2 = Collections
.synchronizedList(new ArrayList<String>()); // 线程安全
}
}
-------------本文结束感谢您的阅读-------------

本文标题:线程-1

文章作者:Linhuide

发布时间:2020年04月01日 - 00:04

最后更新:2020年04月02日 - 21:04

原始链接:https://linhuide.github.io/post/d11c6ce.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创技术分享,您的支持将鼓励我继续创作!