多线程
3-线程的调度-状态-同步-安全问题
2021-07-07 524 1
简介 介绍线程的调度策略、线程的生命周期、线程同步、线程安全问题
7. 线程中设置CPU调度策略
调度策略
时间片
抢占式: 高优先级的线程抢占CPU
Java的调度方法
同优先级线程组成先进先出队列(先到先服务),使用时间片策略
对高优先级,使用优先调度的抢占式策略
线程的优先级等级
MAX_PRIORITY: 10
MIN _PRIORITY: 1
NORM_PRIORITY: 5
涉及的方法
getPriority() : 返回线程优先值
setPriority(int newPriority) : 改变线程的优先级
说明
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
//ThreadPriorityTest.java
package
com.ylaihui.thread;
class
ThreadPriority
extends
Thread{
@Override
public
void
run() {
for
(
int
i =
0
; i <
100
; i++) {
System.out.println(Thread.currentThread().getName()+
":"
+ i);
}
}
}
public
class
ThreadPriorityTest {
public
static
void
main(String[] args) {
ThreadPriority t1 =
new
ThreadPriority();
// t1 设置为最高优先级, 主线程设置为最低优先级
t1.setPriority(Thread.MAX_PRIORITY);
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
t1.start();
for
(
int
i =
0
; i <
100
; i++) {
System.out.println(Thread.currentThread().getName()+
":"
+ i);
}
}
}
2. 线程的生命周期
JDK中用Thread.State类定义了线程的几种状态
要想实现多线程, 必须在主线程中创建新的线程对象。 Java语言使用Thread类
及其子类的对象来表示线程, 在它的一个完整的生命周期中通常要经历如下的五
种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建
状态 NEW
就绪: 处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已
具备了运行的条件,只是没分配到CPU资源
运行: 当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线
程的操作和功能
阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中
止自己的执行,进入阻塞状态 BLOCKED
死亡: 线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束 TERMINATED
3. 线程的同步
线程的安全问题
多个线程执行的不确定性引起执行结果的不稳定
多个线程对数据的共享,会造成操作的不完整性,会破坏数据。
多线程处理缓冲区的消息时,存在重复处理和错误处理问题,比如重复处理消息100, 错误处理了消息0或-1等。
//ProcessMessageTest.java
package
com.ylaihui.thread;
class
ProcessMessage
implements
Runnable{
private
int
message =
100
;
@Override
public
void
run() {
while
(
true
) {
if
(message >
0
) {
System.out.println(
"process message: "
+ message);
message--;
}
else
break
;
}
}
}
public
class
ProcessMessageTest {
public
static
void
main(String[] args) {
ProcessMessage p =
new
ProcessMessage();
Thread t1 =
new
Thread(p);
Thread t2 =
new
Thread(p);
Thread t3 =
new
Thread(p);
t1.start();
t2.start();
t3.start();
}
}
4. 解决线程安全问题(解决线程同步问题)
同步代码块实现线程安全问题
synchronized(同步监视器){
//需要被同步的代码
}
说明:
1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
2.共享数据:多个线程共同操作的变量。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。使用继承Thread创建多线程的方式,可以使用继承类的Xxx.class方式充当同步监视器
同步的方式,解决了线程的安全问题。---好处
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 ---局限性
//ThreadSynchronizedBlock.java
package
com.ylaihui.thread;
class
ProcessMessage1
implements
Runnable{
private
int
message =
100
;
Object obj =
new
Object();
@Override
public
void
run() {
// synchronized(obj){
synchronized
(
this
){
while
(
true
){
if
(message >
0
){
System.out.println(
"process message: "
+ message);
message--;
}
else
break
;
}
}
}
}
public
class
ThreadSynchronizedBlock {
public
static
void
main(String[] args) {
ProcessMessage1 p =
new
ProcessMessage1();
Thread t1 =
new
Thread(p);
Thread t2 =
new
Thread(p);
Thread t3 =
new
Thread(p);
t1.start();
t2.start();
t3.start();
}
}
5. 解决继承Thread类的线程的安全问题
//ThreadSynchronizedBlock1.java
package
com.ylaihui.thread;
class
ProcessMsg
extends
Thread{
private
static
int
message =
100
;
private
static
Object obj =
new
Object();
@Override
public
void
run() {
// 方式一
// synchronized(obj){
// ProcessMsg 在运行时只加载一次, 在底层也是一个对象
synchronized
(ProcessMsg.
class
){
while
(
true
){
if
(message >
0
){
System.out.println(
"process message: "
+ message);
message--;
}
else
break
;
}
}
}
}
public
class
ThreadSynchronizedBlock1 {
public
static
void
main(String[] args) {
ProcessMsg t1 =
new
ProcessMsg();
ProcessMsg t2 =
new
ProcessMsg();
ProcessMsg t3 =
new
ProcessMsg();
t1.start();
t2.start();
t3.start();
}
}
6. 同步方法实现Runnable创建线程的安全问题
同步代码块的方式:
synchronized(同步监视器){
//需要被同步的代码
}
同步方法的方式:
将需要被同步的代码抽取出来,作为一个单独的方法, 同时将该方法设置为 synchronized
void synchronized fun_name{
//需要被同步的代码
}
举例如下:
//SynchronizedMethodTest.java
package
com.ylaihui.thread;
import
static
java.lang.Thread.sleep;
class
ProcessMessage2
implements
Runnable{
private
int
message =
100
;
@Override
public
void
run() {
while
(
true
) {
proc();
}
}
private
synchronized
void
proc(){
// 同步监视器是 this
try
{
sleep(
100
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
if
(message >
0
) {
System.out.println(
"process message: "
+ Thread.currentThread().getName() +
"-"
+ message);
message--;
}
}
}
public
class
SynchronizedMethodTest {
public
static
void
main(String[] args) {
ProcessMessage2 p =
new
ProcessMessage2();
Thread t1 =
new
Thread(p);
Thread t2 =
new
Thread(p);
Thread t3 =
new
Thread(p);
t1.start();
t2.start();
t3.start();
}
}
7. 同步方法实现继承Thread类创建的线程的安全问题
1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
2. 非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身(Xxx.class,类Xxx加载时产生的对象)
//SynchronizedMethodTest1.java
package
com.ylaihui.thread;
class
ProcMsg
extends
Thread{
private
static
int
message =
100
;
@Override
public
void
run() {
while
(
true
){
proc();
}
}
// private synchronized void proc(){ // 同步监视器是 this, 是 t1 t2 t3
private
static
synchronized
void
proc(){
// 同步监视器是 ProcMsg.class 对象
if
(message >
0
){
System.out.println(Thread.currentThread().getName() +
":"
+ message);
message--;
}
}
}
public
class
SynchronizedMethodTest1 {
public
static
void
main(String[] args) {
ProcMsg t1 =
new
ProcMsg();
ProcMsg t2 =
new
ProcMsg();
ProcMsg t3 =
new
ProcMsg();
t1.start();
t2.start();
t3.start();
}
}
8. 死锁问题
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于
阻塞状态,无法继续
解决方法
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,
都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
2.说明:
1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
2)我们使用同步时,要避免出现死锁。
9. 死锁的举例
//DeadLock1.java
package
com.ylaihui.thread;
public
class
DeadLock1 {
public
static
void
main(String[] args) {
StringBuffer s1 =
new
StringBuffer();
StringBuffer s2 =
new
StringBuffer();
new
Thread(){
@Override
public
void
run() {
synchronized
(s1){
s1.append(
"111"
);
s2.append(
"aaa"
);
try
{
Thread.sleep(
100
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
synchronized
(s2){
s1.append(
"bbb"
);
s2.append(
"222"
);
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new
Thread(
new
Runnable() {
@Override
public
void
run() {
synchronized
(s2){
s1.append(
"ccc"
);
s2.append(
"333"
);
try
{
Thread.sleep(
100
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
synchronized
(s1){
s1.append(
"ddd"
);
s2.append(
"444"
);
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
10. 死锁的举例2
//DeadLock1.java
package
com.ylaihui.thread;
public
class
DeadLock1 {
public
static
void
main(String[] args) {
StringBuffer s1 =
new
StringBuffer();
StringBuffer s2 =
new
StringBuffer();
new
Thread(){
@Override
public
void
run() {
synchronized
(s1){
s1.append(
"111"
);
s2.append(
"aaa"
);
try
{
Thread.sleep(
100
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
synchronized
(s2){
s1.append(
"bbb"
);
s2.append(
"222"
);
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new
Thread(
new
Runnable() {
@Override
public
void
run() {
synchronized
(s2){
s1.append(
"ccc"
);
s2.append(
"333"
);
try
{
Thread.sleep(
100
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
synchronized
(s1){
s1.append(
"ddd"
);
s2.append(
"444"
);
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
11. Lock 方式解决线程安全问题
解决线程安全问题的方式三:Lock锁 --- JDK5.0新增
1. 面试题:synchronized 与 Lock的异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
2.优先使用顺序: 优先使用灵活的
Lock 同步代码块(已经进入了方法体,分配了相应资源) 同步方法(在方法体之外)
//SynchronizedLockTest.java
package
com.ylaihui.thread;
import
java.util.concurrent.locks.ReentrantLock;
class
ProcPicture
implements
Runnable{
private
static
int
num =
100
;
private
ReentrantLock lock =
new
ReentrantLock();
@Override
public
void
run() {
while
(
true
) {
try
{
lock.lock();
if
(num >