如果多线程下使用这个类,不过多线程如何使用和调度这个类,这个类总是表示出正确的行为,这个类就是线程安全的;不做正确的同步,在多个线程之间共享状态的时候,就会出现线程不安全;
类的线程安全表现为:
操作的原子性
内存的可见性
栈封闭
所有的变量都是在方法内部声明的,这些变量都处于栈封闭状态;
无状态
没有任何成员变量的类
加final关键字,所有的成员变量应该是私有的,同样的只要有可能,所有的成员变量应该加上final关键字,但是加上final
public class FinalRef {
private final int a;
private final int b;
private final User user;//这里不能保证线程安全啦
public FinalRef(int a, int b) {
this.a = a;
this.b = b;
this.user = new User();
}
public int getA() {
return a;
}
public int getB() {
return b;
}
public User getUser() {
return user;
}
public static class User{
private int age;
public User(int age) {
super();
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public static void main(String[] args) {
FinalRef ref = new FinalRef(12,23);
User u = ref.getUser();
//这里能修改user的值
//u.setAge(35);
}
}
2.不提供任何可供修改成员变量的地方,同时成员变量也不作为方法的返回值;
volatile
volatile可用于在多线程环境下,保证类的可见性,即一个线程修改了,别的线程能够读取到,但volatile并不能保证原子性;
Java内存模型规定了所有的变量都存储在主内存中;每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝(如果局部变量是一个引用类型,它引用的对象在Java堆中可被各个线程共享,但是引用本身在Java栈的局部变量表中,它是线程私有的),线程对变量的所有操作(读取,赋值等)都必须在工作内存中进行,而不能直接读写主内存的变量;不同的线程之间也无法直接访问工作内存中的变量,线程间变量的值传递均需要通过主内存完成;
public class RunThread extends Thread {
private boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean running) {
isRunning = running;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 进入run");
while (isRunning) {
}
System.out.println(Thread.currentThread().getName() + " 线程停止");
}
public static void main(String[] args) throws InterruptedException {
RunThread thread = new RunThread();
thread.start();
Thread.sleep(1000);
thread.setRunning(false);
System.out.println(Thread.currentThread().getName() + " 已经赋值false");
}
}
变量isRunning存在于公共堆栈和线程私有堆栈中(这里的公共堆栈指的是主内存,线程的私有堆栈指的是线程的工作内存),程序运行后一直在线程的私有堆栈中取得isRunning的值为true,虽然在主线程中执行thread.setRunning(false),更新的是公共堆栈的isRunning变量,线程间对于变量的修改是无感知的,操作的是两块内存地址的数据,如下图:
如果将上面的休眠注释掉,程序运行结果可能会正常结束,thread.setRunning(false)操作执行完成,isRunning为false的值能被另外的线程读取;
当一个变量被volatile修饰后,它将具备两种特性,第一是保证此变量对所有线程的可见性,这里的可见性是指当一个线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的;而普通变量不能做到这一点,普通变量的值在线程间传递均需要通过主内存来完成,例如线程A修改一个普通变量的值,然后往主内存进行回写,另外一条线程B在线程A回写完成后再从主内存进行读取操作,读取至B线程的工作内存,新变量才会对线程B可见;
使用volatile关键字,可以强制从主内存(公共内存)中读取变量的值,绕过了线程的工作内存,如下图:

使用原子操作类,synchronized,Lock锁
使用线程本地变量
Servlet不是线程安全类,如需共享资源,会出现线程不安全;Servlet的生命周期是接收到请求,创建一个Servlet,返回一个应答时,销毁Servlet,都是由一个线程负责的;
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁;
当资源多于1个,同时小于等于竞争的线程数;获取锁的顺序不一致会导致死锁;当资源只有一个,只会产生激烈的竞争;解决方法:jstack 查看应用的锁的持有情况;保证加锁的顺序性;
动态顺序死锁,在实现时按照某种顺序加锁了,但是因为外部调用的问题,导致无法保证加锁顺序而产生的;解决: 通过内在排序,保证加锁的顺序性;也可以通过尝试拿锁;
活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程;处于活锁的实体是在不断的改变状态,活锁有可能自行解开;如下例子:
/**
*类说明:不会产生死锁的安全转账方法,尝试拿锁
*/
public class SafeOperate implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException {
Random r = new Random();
while(true) {
if(from.getLock().tryLock()) {
try {
System.out.println(Thread.currentThread().getName()
+" get "+from.getName());
if(to.getLock().tryLock()) {
try {
System.out.println(Thread.currentThread().getName()
+" get "+to.getName());
//两把锁都拿到了
from.flyMoney(amount);
to.addMoney(amount);
break;
}finally {
to.getLock().unlock();
}
}
}finally {
from.getLock().unlock();
}
}
//错开线程拿锁的时间
//Thread.sleep(r.nextInt(10));
}
}
}
上面的线程休眠是用于错开线程拿锁的时间,休眠看起来会耗费时间,但效率会得到提高,能够减少出现重复尝试——失败的次数;
原文:https://www.cnblogs.com/coder-zyc/p/12650557.html