CFSDN nhấn mạnh vào việc tạo ra giá trị thông qua mã nguồn mở. Chúng tôi cam kết xây dựng một nền tảng chia sẻ tài nguyên để mọi người làm CNTT có thể tìm thấy thế giới tuyệt vời của riêng mình tại đây.
Bài đăng trên blog CFSDN này cung cấp hiểu biết sâu sắc về cơ chế lập lịch bộ đếm thời gian Java. Bài đăng được tác giả thu thập và biên soạn. Nếu bạn quan tâm đến bài viết này, vui lòng thích bài viết.
Giới thiệu.
Khi triển khai chức năng lập lịch thời gian, chúng ta thường sử dụng các thư viện của bên thứ ba để hoàn thiện chức năng này như: quartz, spring schedule, v.v. Kể từ phiên bản 1.3, JDK đã cung cấp chức năng lập lịch dựa trên bộ đếm thời gian. Trong bộ đếm thời gian, các tác vụ được thực hiện tuần tự. Mặc dù đảm bảo tính an toàn của luồng, tính năng này thường mang lại một số tác dụng phụ nghiêm trọng, chẳng hạn như ảnh hưởng lẫn nhau giữa các tác vụ và hiệu quả thực hiện tác vụ thấp. Để giải quyết những vấn đề này của bộ hẹn giờ, JDK đã cung cấp chức năng lập lịch bộ hẹn giờ dựa trên ScheduledExecutorService kể từ phiên bản 1.5.
Trong phần này chúng ta chủ yếu phân tích chức năng của bộ hẹn giờ. Chúng tôi sẽ mở một bài viết mới để giải thích các chức năng của ScheduledExecutorService.
Cách sử dụng.
Timer cần được sử dụng cùng với timertask để hoàn thành chức năng lập lịch. timer đại diện cho bộ lập lịch và timertask đại diện cho tác vụ được bộ lập lịch thực hiện. Có hai loại lập lịch tác vụ: lập lịch một lần và lập lịch theo chu kỳ. Dưới đây, chúng ta hãy xem một số ví dụ để biết cách sử dụng chúng.
1. Lên lịch một lần.
?
1
2
3
4
5
6
7
8
9
10
11
12
|
công cộng
tĩnh
vô hiệu
main(chuỗi[] args) {
hẹn giờ hẹn giờ =
mới
bộ đếm thời gian();
nhiệm vụ hẹn giờ =
mới
nhiệm vụ hẹn giờ() {
@ghi đè
công cộng
vô hiệu
chạy() {
định dạng simpledateformat =
mới
định dạng ngày tháng đơn giản(
"hh:mm:ss"
);
system.out.println(format.format(scheduledexecutiontime()) +
", gọi điện"
);
}
};
timer.schedule(nhiệm vụ,
1000
);
}
|
2. Lập lịch vòng lặp - schedule().
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
công cộng
tĩnh
vô hiệu
main(chuỗi[] args) {
hẹn giờ hẹn giờ =
mới
bộ đếm thời gian();
nhiệm vụ hẹn giờ =
mới
nhiệm vụ hẹn giờ() {
@ghi đè
công cộng
vô hiệu
chạy() {
định dạng simpledateformat =
mới
định dạng ngày tháng đơn giản(
"hh:mm:ss"
);
system.out.println(format.format(scheduledexecutiontime()) +
", gọi điện"
);
}
};
timer.schedule(nhiệm vụ,
1000
,
1000
);
}
|
3. Lập lịch vòng tròn - scheduleatfixedrate() .
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
công cộng
tĩnh
vô hiệu
main(chuỗi[] args) {
hẹn giờ hẹn giờ =
mới
bộ đếm thời gian();
nhiệm vụ hẹn giờ =
mới
nhiệm vụ hẹn giờ() {
@ghi đè
công cộng
vô hiệu
chạy() {
định dạng simpledateformat =
mới
định dạng ngày tháng đơn giản(
"hh:mm:ss"
);
system.out.println(format.format(scheduledexecutiontime()) +
", gọi điện"
);
}
};
timer.scheduleatfixedrate(nhiệm vụ,
1000
,
1000
);
}
|
4. Sự khác biệt giữa schedule() và schedulefixedrate().
Xét theo kết quả của 2 và 3, hiệu quả mà chúng đạt được có vẻ như giống nhau. Vì hiệu ứng là như nhau, tại sao JDK lại triển khai nó theo hai phương pháp? Chúng phải khác nhau chứ! .
Trong điều kiện bình thường, tác dụng của chúng hoàn toàn giống nhau. Trong trường hợp bất thường - khi thời gian thực hiện nhiệm vụ dài hơn khoảng thời gian, hiệu ứng của chúng sẽ khác nhau.
- phương thức schedule(), thời gian thực hiện tiếp theo của tác vụ liên quan đến thời gian thực hiện cuối cùng thực sự hoàn thành, do đó thời gian thực hiện sẽ liên tục bị trì hoãn
- phương thức scheduleatfixedrate(), thời gian thực hiện tiếp theo của tác vụ sẽ liên quan đến thời gian khi lần thực hiện cuối cùng bắt đầu, do đó thời gian thực hiện sẽ không bị chậm trễ
- Vì bộ đếm thời gian được triển khai theo cách đơn luồng nên không có phương pháp nào trong hai phương pháp này có vấn đề về an toàn luồng.
Trước tiên chúng ta hãy xem xét những tác động bất thường của schedule():
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
hai mươi mốt
hai mươi hai
hai mươi ba
|
công cộng
tĩnh
vô hiệu
main(chuỗi[] args) {
hẹn giờ hẹn giờ =
mới
bộ đếm thời gian();
nhiệm vụ hẹn giờ =
mới
nhiệm vụ hẹn giờ() {
@ghi đè
công cộng
vô hiệu
chạy() {
định dạng simpledateformat =
mới
định dạng ngày tháng đơn giản(
"hh:mm:ss"
);
thử
{
thread.sleep(
3000
);
}
nắm lấy
(interruptedexception e) {
e.printstacktrace();
}
system.out.println(format.format(scheduledexecutiontime()) +
", gọi điện"
);
}
};
timer.schedule(nhiệm vụ,
1000
,
2000
);
}
|
Tiếp theo, chúng ta hãy xem xét những tác động bất thường của scheduleeatfixedrate():
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
hai mươi mốt
hai mươi hai
hai mươi ba
|
công cộng
tĩnh
vô hiệu
main(chuỗi[] args) {
hẹn giờ hẹn giờ =
mới
bộ đếm thời gian();
nhiệm vụ hẹn giờ =
mới
nhiệm vụ hẹn giờ() {
@ghi đè
công cộng
vô hiệu
chạy() {
định dạng simpledateformat =
mới
định dạng ngày tháng đơn giản(
"hh:mm:ss"
);
thử
{
thread.sleep(
3000
);
}
nắm lấy
(interruptedexception e) {
e.printstacktrace();
}
system.out.println(format.format(scheduledexecutiontime()) +
", gọi điện"
);
}
};
timer.scheduleatfixedrate(nhiệm vụ,
1000
,
2000
);
}
|
Tác giả luôn tin rằng thực hành là cách tốt hơn để kiểm tra sự thật. Ví dụ trên xác minh phỏng đoán ban đầu của chúng tôi từ bên ngoài.
Tuy nhiên, điều này lại nảy sinh một câu hỏi khác. Vì bộ đếm thời gian được triển khai trong một luồng duy nhất bên trong, vậy ScheduleAtFixedRate sẽ xuất dữ liệu sau mỗi 2 giây như thế nào khi khoảng thời gian thực hiện là 2 giây và tác vụ thực sự thực hiện trong 3 giây?
【Chú ý đặc biệt】 .
Thực ra đây chỉ là một trò ảo thuật. Điều quan trọng cần lưu ý là giá trị đầu ra của phương thức print được tạo ra bằng cách gọi scheduleexecutiontime(), không nhất thiết là thời gian thực hiện thực tế của tác vụ, mà là thời gian tác vụ hiện tại cần được thực thi.
Đọc mã nguồn.
Quan điểm của tác giả về kiến thức là ngoài việc biết các sự kiện, người ta còn cần biết lý do đằng sau chúng. Đọc mã nguồn là chìa khóa quan trọng để mở ra cánh cửa dẫn đến hiểu biết tại sao một điều gì đó lại xảy ra. Trong JDK, bộ đếm thời gian chủ yếu bao gồm timertask, taskqueue và timerthread.
1. nhiệm vụ hẹn giờ.
timertask biểu thị tác vụ được thực hiện bởi trình lập lịch tác vụ. Nó kế thừa từ runnable và duy trì trạng thái của tác vụ bên trong. Tổng cộng có bốn trạng thái.
- virgin, tên tiếng Anh là virgin, chỉ ra rằng nhiệm vụ vẫn chưa được lên lịch
- đã lên lịch, đã được lên lịch, nhưng vẫn chưa được thực hiện
- đã thực hiện: Đối với một tác vụ được thực hiện một lần, điều đó có nghĩa là tác vụ đó đã được thực hiện; đối với một tác vụ được thực hiện nhiều lần, trạng thái này không hợp lệ.
- đã hủy, nhiệm vụ đã bị hủy
timertask cũng có các biến thành viên sau.
- nextexecutiontime, thời gian thực hiện tiếp theo
- khoảng thời gian: khoảng thời gian thực hiện nhiệm vụ. Số dương biểu thị tỷ lệ cố định; số âm biểu thị độ trễ cố định; 0 biểu thị chỉ thực hiện một lần
Sau khi phân tích các chức năng chung, chúng ta hãy xem xét mã của nó.
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
hai mươi mốt
hai mươi hai
hai mươi ba
hai mươi bốn
25
26
|
/**
* trạng thái của tác vụ này, được chọn từ các hằng số bên dưới.
*/
số nguyên
trạng thái = trinh nguyên;
/**
* nhiệm vụ này vẫn chưa được lên lịch.
*/
tĩnh
cuối cùng
số nguyên
trinh nữ =
0
;
/**
* nhiệm vụ này được lên lịch thực hiện. Nếu đây là nhiệm vụ không lặp lại,
* vẫn chưa được thực hiện.
*/
tĩnh
cuối cùng
số nguyên
đã lên lịch =
1
;
/**
* Nhiệm vụ không lặp lại này đã được thực hiện (hoặc hiện đang được thực hiện)
* đang thực hiện) và chưa bị hủy.
*/
tĩnh
cuối cùng
số nguyên
đã thực hiện =
2
;
/**
* tác vụ này đã bị hủy (bằng cách gọi đến timertask.cancel).
*/
tĩnh
cuối cùng
số nguyên
đã hủy =
3
;
|
Timertask có hai phương thức hoạt động.
- cancel() // hủy tác vụ
- scheduleexecutiontime() // Lấy thời gian thực hiện tác vụ
cancel() tương đối đơn giản, chủ yếu là khóa tác vụ hiện tại và sau đó thay đổi trạng thái thành đã hủy.
?
1
2
3
4
5
6
7
|
công cộng
Boolean
Hủy bỏ() {
đồng bộ
(khóa) {
Boolean
kết quả = (trạng thái == đã lên lịch);
trạng thái = đã hủy;
trở lại
kết quả;
}
}
|
Trong scheduleexecutiontime(), thời gian thực hiện tác vụ được tính bằng cách trừ thời gian khoảng thời gian khỏi thời gian thực hiện tiếp theo.
?
1
2
3
4
5
6
|
công cộng
dài
thời gian thực hiện theo lịch trình() {
đồng bộ
(khóa) {
trở lại
(dấu chấm <
0
? thời gian thực hiện tiếp theo + chu kỳ
:nexexecutiontime - dấu chấm);
}
}
|
2. hàng đợi tác vụ.
Taskqueue là hàng đợi được sử dụng để lưu trữ các tác vụ trong bộ đếm thời gian. Nó được triển khai nội bộ bằng cách sử dụng [thuật toán heap tối thiểu] và tác vụ ở đầu heap sẽ được thực thi trước. Do sử dụng [min heap], taskqueue cực kỳ hiệu quả trong việc xác định thời gian thực thi đã đến hay chưa. Chúng ta hãy cùng xem xét cách nó được triển khai nội bộ.
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
hai mươi mốt
hai mươi hai
hai mươi ba
hai mươi bốn
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
lớp học
hàng đợi tác vụ {
/**
* hàng đợi ưu tiên được biểu diễn như một đống nhị phân cân bằng: hai đứa trẻ
* của hàng đợi [n] là hàng đợi [2 * n] và hàng đợi [2 * n + 1]. hàng đợi ưu tiên là
* được sắp xếp trên trường nextexecutiontime: timertask có giá trị thấp nhất
* nextexecutiontime đang trong hàng đợi[1] (giả sử hàng đợi không trống). đối với
* mỗi nút n trong đống, và mỗi con cháu của n, d,
* n.thời gian thực hiện tiếp theo <= d.thời gian thực hiện tiếp theo.
*
* Sử dụng mảng để lưu trữ các tác vụ
*/
riêng tư
timertask[] hàng đợi =
mới
nhiệm vụ hẹn giờ[
128
];
/**
* số lượng tác vụ trong hàng đợi ưu tiên. (các tác vụ được lưu trữ trong
* hàng đợi [1] lên đến hàng đợi [kích thước]).
*
* Được sử dụng để chỉ số lượng tác vụ trong hàng đợi. Cần lưu ý rằng số lượng tác vụ không bằng độ dài của mảng
*/
riêng tư
số nguyên
kích thước =
0
;
/**
* trả về số lượng tác vụ hiện có trong hàng đợi.
*/
số nguyên
kích cỡ() {
trở lại
kích cỡ;
}
/**
* thêm một nhiệm vụ mới vào hàng đợi ưu tiên.
*
* Thêm một nhiệm vụ vào hàng đợi
*/
vô hiệu
thêm(nhiệm vụ timertask) {
nếu như
(kích thước +
1
== độ dài hàng đợi)
hàng đợi = mảng.copyof(hàng đợi,
2
*queue.length);
hàng đợi[++kích thước] = nhiệm vụ;
sửa chữa(kích thước);
}
/**
* trả về "nhiệm vụ chính" của hàng đợi ưu tiên. (nhiệm vụ chính là
* nhiệm vụ có thời gian thực thi tiếp theo thấp nhất.)
*
* Phần tử đầu tiên của hàng đợi là tác vụ đầu tiên được thực hiện
*/
nhiệm vụ hẹn giờ getmin() {
trở lại
xếp hàng[
1
];
}
/**
* trả về tác vụ thứ i trong hàng đợi ưu tiên, trong đó i có phạm vi từ 1 (
* nhiệm vụ chính, được trả về bởi getmin) cho số lượng nhiệm vụ trên
* xếp hàng, bao gồm.
*
* Lấy phần tử của chỉ mục được chỉ định trong hàng đợi
*/
timertask lấy(
số nguyên
Tôi) {
trở lại
hàng đợi[i];
}
/**
* xóa tác vụ chính khỏi hàng đợi ưu tiên.
*
* Loại bỏ phần tử trên cùng của đống. Sau khi loại bỏ, cần phải điều chỉnh xuống dưới để tạo lại đống tối thiểu
*/
vô hiệu
xóamin() {
xếp hàng[
1
] = hàng đợi[kích thước];
hàng đợi[kích thước--] =
vô giá trị
;
sửa chữa(
1
);
}
/**
* xóa phần tử thứ i khỏi hàng đợi mà không cần quan tâm đến việc duy trì
* bất biến heap. nhớ lại rằng hàng đợi là dựa trên một, vì vậy
* 1 <= i <= kích thước.
*
* Nhanh chóng tháo phần tử ở vị trí đã chỉ định mà không cần điều chỉnh lại ngăn xếp
*/
vô hiệu
xóa nhanh(
số nguyên
Tôi) {
khẳng định
i <= kích thước;
queue[i] = queue[kích thước];
hàng đợi[kích thước--] =
vô giá trị
;
}
/**
* thiết lập thời gian thực thi tiếp theo liên quan đến tác vụ chính thành
* giá trị được chỉ định và điều chỉnh hàng đợi ưu tiên cho phù hợp.
*
* Lên lịch lại và điều chỉnh xuống để tạo lại đống tối thiểu
*/
vô hiệu
lên lịch lại phút(
dài
thời gian mới) {
xếp hàng[
1
].nexexecutiontime = thời gian mới;
sửa chữa(
1
);
}
/**
* trả về true nếu hàng đợi ưu tiên không chứa phần tử nào.
*
* Hàng đợi có trống không?
*/
Boolean
rỗng() {
trở lại
kích thước==
0
;
}
/**
* xóa tất cả các phần tử khỏi hàng đợi ưu tiên.
*
* Xóa tất cả các phần tử khỏi hàng đợi
*/
vô hiệu
thông thoáng() {
vì
(
số nguyên
tôi=
1
; i<=kích thước; i++)
hàng đợi[i] =
vô giá trị
;
kích thước =
0
;
}
/**
* thiết lập bất biến heap (được mô tả ở trên) giả sử heap
* thỏa mãn bất biến ngoại trừ có thể đối với nút lá được lập chỉ mục bởi k
* (có thể có thời gian thực thi tiếp theo ngắn hơn thời gian thực thi của chương trình cha).
*
* Phương pháp này hoạt động bằng cách "thăng cấp" hàng đợi [k] lên trên hệ thống phân cấp
* (bằng cách hoán đổi nó với phần tử cha của nó) nhiều lần cho đến khi hàng đợi [k]
* nextexecutiontime lớn hơn hoặc bằng thời gian thực thi của chương trình cha.
*
* Điều chỉnh lên trên để tạo lại đống tối thiểu
*/
riêng tư
vô hiệu
sửa chữa(
số nguyên
k) {
trong khi
(k >
1
) {
số nguyên
j = k >>
1
;
nếu như
(hàng đợi[j].thời gian thực hiện tiếp theo <= hàng đợi[k].thời gian thực hiện tiếp theo)
phá vỡ
;
timertask tmp = hàng đợi[j]; hàng đợi[j] = hàng đợi[k]; hàng đợi[k] = tmp;
k = j;
}
}
/**
* thiết lập bất biến heap (được mô tả ở trên) trong cây con
* có gốc tại k, được cho là thỏa mãn bất biến heap ngoại trừ
* có thể đối với chính nút k (có thể có thời gian thực hiện tiếp theo lớn hơn
* hơn là con của nó).
*
* Phương pháp này hoạt động bằng cách "hạ cấp" hàng đợi [k] xuống dưới thứ bậc
* (bằng cách hoán đổi nó với phần tử con nhỏ hơn của nó) nhiều lần cho đến khi hàng đợi [k]
* nextexecutiontime nhỏ hơn hoặc bằng thời gian thực hiện của các tiến trình con của nó.
*
* Điều chỉnh xuống dưới để tạo lại đống tối thiểu
*/
riêng tư
vô hiệu
sửa chữa(
số nguyên
k) {
số nguyên
j;
trong khi
((j = k <<
1
) <= kích thước && j >
0
) {
nếu như
(j < kích thước &&
hàng đợi[j].thời gian thực hiện tiếp theo > hàng đợi[j+
1
].thời gian thực hiện tiếp theo)
j++;
nếu như
(hàng đợi[k].thời gian thực hiện tiếp theo <= hàng đợi[j].thời gian thực hiện tiếp theo)
phá vỡ
;
timertask tmp = hàng đợi[j]; hàng đợi[j] = hàng đợi[k]; hàng đợi[k] = tmp;
k = j;
}
}
/**
* thiết lập bất biến heap (được mô tả ở trên) trong toàn bộ cây,
* không giả định bất cứ điều gì về thứ tự của các phần tử trước khi gọi.
*/
vô hiệu
làm đống() {
vì
(
số nguyên
i = kích thước/
2
; tôi >=
1
; Tôi--)
sửa lỗi(i);
}
}
|
3. luồng hẹn giờ.
Là một biến thành viên của bộ đếm thời gian, timerthread đóng vai trò hiệu chuẩn bộ lập lịch. Trước tiên chúng ta hãy xem xét phương pháp xây dựng của nó, chủ yếu là giữ hàng đợi tác vụ.
?
1
2
3
|
timerthread(hàng đợi tác vụ) {
cái này
.queue = hàng đợi;
}
|
Tiếp theo, chúng ta hãy xem phương thức run(), đây là điểm vào để thực thi luồng.
?
1
2
3
4
5
6
7
8
9
10
11
|
công cộng
vô hiệu
chạy() {
thử
{
vòng lặp chính();
}
Cuối cùng
{
đồng bộ
(hàng đợi) {
newtasksmaybescheduled =
SAI
;
queue.clear();
}
}
}
|
Logic chính đều nằm trong phương thức mainloop(). Sau khi phương thức vòng lặp chính được thực thi, tài nguyên sẽ được dọn sạch. Chúng ta hãy cùng xem xét phương thức mainloop().
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
hai mươi mốt
hai mươi hai
hai mươi ba
hai mươi bốn
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
|
riêng tư
vô hiệu
vòng lặp chính() {
trong khi
(
ĐÚNG VẬY
) {
thử
{
nhiệm vụ timertask;
Boolean
đã làm nhiệm vụ;
đồng bộ
(hàng đợi) {
trong khi
(queue.isempty() && newtasksmaybescheduled)
hàng đợi.chờ();
nếu như
(queue.isemty())
phá vỡ
;
dài
thời gian hiện tại, thời gian thực hiện;
nhiệm vụ = queue.getmin();
đồng bộ
(task.lock) {
nếu như
(task.state == timertask.cancelled) {
queue.removemin();
Tiếp tục
;
}
currenttime = system.currenttimemillis();
thời gian thực hiện = nhiệm vụ. thời gian thực hiện tiếp theo;
nếu như
(taskfired = (thời gian thực hiện <=thời gian hiện tại)) {
nếu như
(nhiệm vụ.thời gian ==
0
) {
queue.removemin();
task.state = timertask.executed;
}
khác
{
hàng đợi.reschedulemin(
nhiệm vụ.thời gian<
0
? thời gian hiện tại - task.period
: thời gian thực hiện + thời gian tác vụ);
}
}
}
nếu như
(!nhiệm vụ đã được thực hiện)
queue.wait(thời gian thực hiện - thời gian hiện tại);
}
nếu như
(đã hoàn thành nhiệm vụ)
task.run();
}
nắm lấy
(interruptedexception e) {
}
}
}
|
4. bộ đếm thời gian.
Bộ đếm thời gian thực hiện những điều sau thông qua phương pháp xây dựng:
- Điền vào các thuộc tính khác nhau của đối tượng timerthread (chẳng hạn như tên luồng, liệu đó có phải là luồng daemon hay không)
- Bắt đầu một chủ đề
?
1
2
3
4
5
6
7
8
9
10
|
/**
* chuỗi thời gian.
*/
riêng tư
cuối cùng
timerthread luồng =
mới
timerthread(hàng đợi);
công cộng
timer(tên chuỗi,
Boolean
là daemon) {
thread.setname(tên);
thread.setdaemon(isdaemon);
thread.bắt đầu();
}
|
Trong timer, chỉ có hai phương thức lập lịch thực sự được cung cấp cho người dùng, đó là schedule() và scheduleFixedRate(). Chúng ta hãy cùng xem xét.
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
hai mươi mốt
hai mươi hai
hai mươi ba
hai mươi bốn
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
công cộng
vô hiệu
lịch trình(timertask task,
dài
trì hoãn) {
nếu như
(trì hoãn <
0
)
ném
mới
ngoại lệ đối số bất hợp pháp(
"trì hoãn tiêu cực."
);
sched(nhiệm vụ, hệ thống.currenttimemillis()+trì hoãn,
0
);
}
công cộng
vô hiệu
lịch trình(timertask task, ngày giờ) {
sched(nhiệm vụ, thời gian.gettime(),
0
);
}
công cộng
vô hiệu
lịch trình(timertask task,
dài
trì hoãn,
dài
Giai đoạn)
nếu như
(trì hoãn <
0
)
ném
mới
ngoại lệ đối số bất hợp pháp(
"trì hoãn tiêu cực."
);
nếu như
(dấu chấm <=
0
)
ném
mới
ngoại lệ đối số bất hợp pháp(
"giai đoạn không tích cực."
);
sched(nhiệm vụ, hệ thống.currenttimemillis()+trì hoãn, -khoảng thời gian);
}
công cộng
vô hiệu
lịch trình(timertask task, ngày firsttime,
dài
Giai đoạn)
nếu như
(dấu chấm <=
0
)
ném
mới
ngoại lệ đối số bất hợp pháp(
"giai đoạn không tích cực."
);
sched(nhiệm vụ, firsttime.gettime(), -period);
}
công cộng
vô hiệu
scheduleatfixedrate(timertask task,
dài
trì hoãn,
dài
Giai đoạn)
nếu như
(trì hoãn <
0
)
ném
mới
ngoại lệ đối số bất hợp pháp(
"trì hoãn tiêu cực."
);
nếu như
(dấu chấm <=
0
)
ném
mới
ngoại lệ đối số bất hợp pháp(
"giai đoạn không tích cực."
);
sched(nhiệm vụ, hệ thống.currenttimemillis()+trì hoãn, chu kỳ);
}
công cộng
vô hiệu
scheduleatfixedrate(timertask task, date firsttime,
dài
Giai đoạn)
nếu như
(dấu chấm <=
0
)
ném
mới
ngoại lệ đối số bất hợp pháp(
"giai đoạn không tích cực."
);
sched(nhiệm vụ, firsttime.gettime(), thời gian);
}
|
Từ đoạn mã trên, chúng ta có thể thấy những điểm sau.
- Cả hai phương pháp cuối cùng đều gọi phương thức riêng tư sched()
- Khoảng thời gian được truyền vào schedule() là một số âm và khoảng thời gian được truyền vào scheduleatfixedrate() là một số dương.
Tiếp theo chúng ta xem phương thức sched().
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
hai mươi mốt
hai mươi hai
hai mươi ba
hai mươi bốn
25
26
27
28
29
30
31
32
|
riêng tư
vô hiệu
sched(timertask nhiệm vụ,
dài
thời gian,
dài
Giai đoạn)
nếu như
(thời gian <
0
)
ném
mới
ngoại lệ đối số bất hợp pháp(
"thời gian thực hiện bất hợp pháp."
);
nếu như
(math.abs(chu kỳ) > (
dài
.giá_trị_tối_đa >>
1
))
thời kỳ >>=
1
;
đồng bộ
(hàng đợi) {
nếu như
(!thread.newtasksmaybescheduled)
ném
mới
ngoại lệ bất hợp pháp(
"bộ hẹn giờ đã bị hủy."
);
đồng bộ
(task.lock) {
nếu như
(task.state != timertask.virgin)
ném
mới
ngoại lệ bất hợp pháp(
"nhiệm vụ đã được lên lịch hoặc đã hủy"
);
task.nexexecutiontime = thời gian;
task.period = kỳ;
task.state = timertask.scheduled;
}
queue.add(nhiệm vụ);
nếu như
(queue.getmin() == nhiệm vụ)
queue. thông báo();
}
}
|
Phương thức sched() trải qua các bước sau:
- Kiểm tra xem thời gian không thể là số âm
- chu kỳ không thể vượt quá long.max_value >> 1
- Khi bộ hẹn giờ bị hủy, nó không thể được lên lịch
- Khóa tác vụ, sau đó đặt thời gian thực hiện tiếp theo, chu kỳ thực hiện và trạng thái tác vụ của tác vụ để đảm bảo rằng việc lập lịch tác vụ và hủy tác vụ là an toàn cho luồng
- Thêm tác vụ vào hàng đợi
- Nếu phần tử trên cùng trong hàng đợi là tác vụ hiện tại, hàng đợi sẽ được đánh thức để timerthread có thể lên lịch cho tác vụ.
【Lưu ý】:Chúng ta cần đặc biệt chú ý đến điểm 6. Tại sao hàng đợi chỉ thức dậy khi phần tử trên cùng của ngăn xếp là tác vụ hiện tại? Lý do nằm ở ý nghĩa của phần tử trên cùng của heap, nghĩa là: phần tử trên cùng của heap biểu thị tác vụ được thực hiện gần nhất với thời điểm hiện tại! .
[Ví dụ 1]: Nếu thời gian hiện tại là 1 giây, có một tác vụ a trong hàng đợi cần được thực hiện trong 3 giây và tác vụ b mới được thêm vào của chúng ta cần được thực hiện trong 5 giây. Lúc này, vì timerthread có hoạt động wait(timeout) nên nó sẽ tự động thức dậy khi hết thời gian. Do đó, vì lý do hiệu suất, không cần phải đánh thức trong quá trình thực hiện sched().
[Ví dụ 2]: Nếu thời gian hiện tại là 1 giây, có một tác vụ a trong hàng đợi cần được thực hiện trong 3 giây và tác vụ b mới được thêm vào của chúng ta cần được thực hiện trong 2 giây. Lúc này, nếu thao tác đánh thức không được thực hiện trong sched(), tác vụ a sẽ được thực thi trong 3 giây. Tuy nhiên, vì tác vụ B cần được thực hiện trong 2 giây nên thời gian thực hiện đã trôi qua, do đó xảy ra sự cố.
Sau khi phân tích phương thức lập lịch tác vụ sched(), chúng tôi tiếp tục phân tích các phương thức khác. Trước tiên chúng ta hãy xem cancel(), được sử dụng để hủy việc thực hiện bộ đếm thời gian.
?
1
2
3
4
5
6
7
|
công cộng
vô hiệu
Hủy bỏ() {
đồng bộ
(hàng đợi) {
thread.newtasksmaybescheduled =
SAI
;
queue.clear();
queue. thông báo();
}
}
|
Từ phân tích mã nguồn ở trên, phương pháp này thực hiện những điều sau:
- Đặt cờ newtasksmaybescheduled của timerthread thành false
- Xóa hàng đợi
- Hàng đợi đánh thức
Đôi khi, có thể có nhiều tác vụ hẹn giờ trong một bộ hẹn giờ. Nếu chúng ta chỉ hủy một số timertask, thay vì tất cả, thì ngoài việc gọi phương thức cancel() trên timertask, chúng ta cũng cần phải dọn dẹp timer. Phương pháp dọn dẹp ở đây là purge(). Chúng ta hãy xem xét logic triển khai của nó.
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
công cộng
số nguyên
thanh lọc() {
số nguyên
kết quả =
0
;
đồng bộ
(hàng đợi) {
vì
(
số nguyên
i = hàng đợi.kích thước(); i >
0
; Tôi--) {
nếu như
(queue.get(i).state == timertask.cancelled) {
queue.quickremove(i);
kết quả++;
}
}
nếu như
(kết quả !=
0
)
queue.heapify();
}
trở lại
kết quả;
}
|
5. Phương pháp đánh thức hàng đợi.
Thông qua phân tích mã nguồn trước đó, chúng ta có thể thấy rằng chức năng đánh thức hàng đợi tồn tại ở những nơi sau:
- timer. hủy()
- timer.sched()
- timer.threadreaper.finalize()
Điểm thứ nhất và thứ hai đã được phân tích. Bây giờ chúng ta hãy xem xét điểm thứ ba.
?
1
2
3
4
5
6
7
8
|
riêng tư
cuối cùng
đối tượng threadreaper =
mới
sự vật() {
được bảo vệ
vô hiệu
hoàn thiện()
ném
có thể ném được
đồng bộ
(hàng đợi) {
thread.newtasksmaybescheduled =
SAI
;
queue. thông báo();
}
}
};
|
Phương pháp này được sử dụng để đánh thức hàng đợi tác vụ trong giai đoạn gc, giai đoạn mà người đọc thường quên.
Vậy, hãy cùng quay lại và suy nghĩ xem tại sao chúng ta cần đoạn mã này?
Khi chúng ta phân tích timerthread, chúng ta thấy rằng nếu bộ đếm thời gian không được lên lịch sau khi được tạo, nó sẽ tiếp tục chờ và rơi vào trạng thái tạm dừng. Để tránh tình huống này, chuyên gia về xử lý đồng thời Doug Lea đã khéo léo nghĩ ra cách đặt cờ trạng thái newtasksmaybescheduled trong finalize() và đánh thức hàng đợi tác vụ (queue.notify()) để giải cứu timerthread khỏi vòng lặp vô hạn.
Tóm tắt.
Đầu tiên, bài viết này trình bày cách sử dụng bộ đếm thời gian, sau đó phân tích sự khác biệt và mối liên hệ giữa các phương thức lập lịch schedule() và scheduleFixedRate().
Sau đó, để hiểu sâu hơn về bộ đếm thời gian, chúng tôi đã tiến hành phân tích chuyên sâu bằng cách đọc mã nguồn. Có thể thấy rằng cách triển khai bên trong của nó rất thông minh và được cân nhắc kỹ lưỡng.
Tuy nhiên, đặc điểm thực thi tuần tự của bộ đếm thời gian hạn chế ứng dụng của nó trong môi trường đồng thời cao. Sau đó chúng ta sẽ phân tích sâu hơn về cách lập lịch tác vụ được triển khai trong môi trường phân tán, đồng thời cao. Chúng ta hãy cùng chờ xem~.
Trên đây là toàn bộ nội dung bài viết này, hy vọng sẽ giúp ích cho việc học tập của mọi người, và mong các bạn ủng hộ tôi.
Liên kết gốc: https://www.jianshu.com/p/f4c195840159.
Cuối cùng, bài viết này về hiểu biết sâu sắc về cơ chế lập lịch thời gian Java (Timer) đã kết thúc tại đây. Nếu bạn muốn biết thêm về hiểu biết sâu sắc về cơ chế lập lịch thời gian Java (Timer), vui lòng tìm kiếm các bài viết CFSDN hoặc tiếp tục duyệt các bài viết liên quan. Tôi hy vọng bạn sẽ ủng hộ blog của tôi trong tương lai! .
Tôi là một lập trình viên xuất sắc, rất giỏi!