一、线程安全问题:
当我们使用多个线程操作统一方法内的局部变量的时候,每个局部变量在当前线程里都有自己的副本,这种情况是不会出现线程安全问题的。当我们两个线程同时操作全局变量的时候,有可能会引发线程安全的问题。
①.业务类
package com.multiThread.bean;
publicclassAservise{
privateString name;
publicvoid doBusiness(String name){
this.name = name;
System.out.println("大家好,我是"+this.name);
}
}
②.线程类
package com.multiThread.thread;
import com.multiThread.bean.Aservise;
publicclassUnSafeThreadimplementsRunnable{
privateAservise aServise;
privateString name;
publicUnSafeThread(Aservise aServise,String name){
this.aServise = aServise;
this.name = name;
}
@Override
publicvoid run(){
aServise.doBusiness(this.name);
}
}
③.测试类
package com.multiThread.test.common;
import com.multiThread.bean.Aservise;
import com.multiThread.thread.UnSafeThread;
publicclassUnSafeThreadTest{
publicstaticvoid main(String[] args){
Aservise aService =newAservise();
UnSafeThread unSafeThreadZhang =newUnSafeThread(aService,"张三");
UnSafeThread unSafeThreadLi =newUnSafeThread(aService,"李四");
Thread zhang =newThread(unSafeThreadZhang);
Thread li =newThread(unSafeThreadLi);
zhang.start();
li.start();
}
}
预期输出结果
大家好,我是张三
大家好,我是李四
多次运行实际输出结果
大家好,我是李四
大家好,我是李四
大家好,我是张三
大家好,我是李四
大家好,我是张三
大家好,我是张三
这个例子很好的解释了多个线程同时操作全局变量会存在线程安全的问题。
那么这种问题该如何解决呢?在这里我们只讨论服务器为单节点的情况,不考虑集群模式。
解决的方式是加锁,只要保证这两段程序不同时变更全局变量就OK。
将业务类的doBusiness方法更改为使用synchronized关键字修饰就可以:
1.在方法上声明,给当前对象加锁(非静态方法):
publicsynchronizedvoid doBusiness(String name){
this.name = name;
System.out.println("大家好,我是"+this.name);
- }
2.synchronized不仅仅可以在方法上声明,也可以在方法内部声明(参数是Object类型的值,写成this代表给当前对象上锁)。这种情况叫做同步代码块:
publicvoid doBusiness(String name){
synchronized(this){
this.name = name;
System.out.println("大家好,我是"+this.name);
}
}
对象监视器:
synchronize修饰方法或者synchronized(this),对象监视器监视当前类的对象。
synchronize(其他对象),对象监视器给其他对象上锁。
需要注意的问题:
一般不会使用字符串作为监视对象,因为字符串有个常量池的概念,在不同的地方操作可能锁的是同一个对象。
synchronize作用:
1.给对象监视器监视的对象上锁
2.保证同一时间只有一个对象可以获得此对象监视器上的锁。
注意:
①.synchronize关键字如果在非静态方法上声明或者在非静态代码块上声明synchronized(this),代表给当前对象上锁,当一个线程获得此锁的同时,其他线程调用synchronize修饰的方法、代码块均需等待。当此线程执行完毕或者主动释放锁时,根据cpu调度看哪个线程能获得此锁。
②.如果synchronize(其他对象),则代表给参数中的对象上锁,所有参数对象共用同一把锁,只有获得锁的线程可以执行此代码块。
③.如果是静态方法,则代表是给整个类上锁,那么整个类共用同一把锁,只有获得锁的线程可以执行同步方法、代码块。
使用jstack查看死锁:
①.cd到jdk/bin目录下,执行jps命令,得到正在运行的进程id,这块我笔记本上运行不出来。我也不知道哪个是进程id。
②.执行jstack - l 进程id
③.得到jstack后分析一下程序哪个地方设计有bug,修改程序。
二、Thread类相关API操作:
好了,现在我们已经对多线程和线程安全有了一定的认识。下面我们通过具体代码来了解一下java.lang.Thread类相关的API的常用操作。
currentThread():获取当前执行当前线程的线程对象
isAlive():当前线程是否存活(正在执行)
sleep():使当前线程睡眠一段时间,参数单位毫秒
getId():获取当前线程的唯一标识
yield():释放当前线程的控制权,将控制权交由CPU调度。
interrupt():停止线程执行(只调用这个方法无法中止线程的执行,需要配合interrupted()才能中断线程。线程在sleep的情况下中断会抛异常并且清除停止状态的值,使之变为false)
interrupted():测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能。
此方法多次调用会有问题。比如第一次中断线程,再次调用则会清除中断状态。
isInterrupted():测试线程是否已中断。
setPriority ():设置线程的优先级,优先级越高,优先执行的几率越大。取值范围是1~10
jdk中不推荐使用的过期的方法以及原因:
stop():强制中断当前线程。 强制停止某个线程可能会造成某些清理操作无法完成。而且会将对象的锁给清除掉,有可能会造成数据不一致的问题。
suspend():暂停当前线程的执行,此操作并不会释放锁,有锁独占的问题。
另外如果在System.out.println()中暂停了线程,同步锁并未释放。其他有System.out.println()的地方就无法执行。
因为Sysem.out.println()本身底层的实现也是基于synchronized的。一个对象占着锁不放,这边就一直得不到执行。
System.out.println()的实现:
publicvoid println(String x){
synchronized(this){
print(x);
newLine();
}
}
resume():恢复当前线程的执行,此操作并不会释放锁,有锁独占的问题。