在通过例子分析线程安全之前先 看了解这些 知识
在去看例子会清晰一点
Java有三大变量
静态变量:在方法区。
实例变量:在堆当中
局部变量:在栈中。
以上三大变量中:
局部变量永远都不会存在线程安全问题。
因为局部变量不共享。(一个线程一个栈。)
局部变量在栈中。所以局部变量永远都不会共享。
实例变量在堆中,堆只有1个。
静态变量在方法区中,方法区只有1个.
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。
【成员变量包括实例变量(存储在堆内存)和静态变量(存储在方法区内存)】
局部变量存储在栈内存当中,栈不共享,不会有线程安全的问题
常见线程安全类
String
Integer
Random
Vector
Hashtable
java.util.concurrent 包下的类
ArrayList是非线程安全的。vector是线程安全的。
HashMap Hashset是非线程安全的。Hashtable是线程安全的。
如果使用局部变量的话:
建议使用: stringBuilder.
因为局部变量不存在线程安全问题。
选择stringBuilder.stringBuffer效率比较低。
总结: 实现线程安全有三种方式:1.无共享变量2.共享变量不可变3.同步
下面请跟我分析一下代码是否存在线程安全的问题,全部看完相信会有很大帮助
例一:
public class MyServlet extends HttpServlet {
// 是否安全?
Map<String,Object> map = new HashMap<>(); //不安全 因为HashMap当中没有synchronized修饰 是非安全的
// 是否安全?
String S1 = "..."; //安全 因为String的不可变类 属于共享变量不可变
// 是否安全?
final String S2 = "..."; //安全的 本身String就是线程安全的 加上final就更是了
// 是否安全?
Date D1 = new Date(); //不安全 日期类不属于线程安全的
// 是否安全?
final Date D2 = new Date(); //不安全 虽然final Date D2 的 final修饰的
//但是他new Date(); 里面 的年月日会变 总结 日期是可变类 而字符串是不可变的
}
例二
public class MyServlet extends HttpServlet {
// 是否安全?
private UserService userService = new UserServiceImpl();
public void doGet(HttpServletRequest request, HttpServletResponse response) {
userService.update(...);
}
}
public class UserServiceImpl implements UserService {
// 记录调用次数
private int count = 0;
public void update() {
// ...
count++;
}
}
/*不安全,因为servlet只有一个,而UserServiceImpl 作为 servlet的成员变量也只有一个,
所以是可以被多个线程共享使用的,而在实现类当中有一个**count计数**,
在这里 当多个线程执行的时候就存在线程安全的问题
注意不要写出这样的代码 要不然对count对线程进行保护,要不然不要这样写*/
例三
spring IOC
@Aspect
@Component
public class MyAspect {
// 是否安全?
private long start = 0L;
@Before("execution(* *(..))") //前置通知
public void before() {
start = System.nanoTime(); //记录开始时间
}
@After("execution(* *(..))") //后置通知
public void after() {
long end = System.nanoTime(); //记录结束时间
System.out.println("cost time:" + (end-start)); //计算耗时 通过切面的功能,把耗时功能给抽离出来
}
}
/*不安全 :spring当中如果没有加@Scope关键字 的话 就是单例的,既然是单例就需要被共享,
里面的成员变量也是需要被共享的,就有并发问题
在编码当中就尽量少出现
解决:可以改成环绕通知,将开始时间 结束时间 变成局部变量 就保证了 线程安全*/
例四
//MVC的三层调用
public class MyServlet extends HttpServlet {
// 是否安全
private UserService userService = new UserServiceImpl();
public void doGet(HttpServletRequest request, HttpServletResponse response) {
userService.update(...);
}
}
public class UserServiceImpl implements UserService {
// 是否安全
private UserDao userDao = new UserDaoImpl();
public void update() {
userDao.update();
}
}
public class UserDaoImpl implements UserDao {
public void update() {
String sql = "update user set password = ? where username = ?";
// 是否安全
try (Connection conn = DriverManager.getConnection("","","")){
// ...
} catch (Exception e) {
// ...
}
}
}
/*dao层 没有成员变量 是线程安全的
Service 层 有成员变量会被共享 但是没有可更改的属性 所以 是线程安全的
Contoller 层 注意在service层 是私有的成员变量 没有其他地方去需改他
因此也是不可变的 所以 在controller当中的userService 这个成员变量也是线程安全的*/
例五
public class MyServlet extends HttpServlet {
// 是否安全
private UserService userService = new UserServiceImpl();
public void doGet(HttpServletRequest request, HttpServletResponse response) {
userService.update(...);
}
}
public class UserServiceImpl implements UserService {
// 是否安全
private UserDao userDao = new UserDaoImpl();
public void update() {
userDao.update();
}
}
public class UserDaoImpl implements UserDao {
// 是否安全
private Connection conn = null;
public void update() throws SQLException {
String sql = "update user set password = ? where username = ?";
conn = DriverManager.getConnection("","","");
// ...
conn.close();
}
}
/*不安全
注意 在dao层里面 “ private Connection conn = null;”并不是方法当中的局部变量
而是 dao 的成员变量 就导致这个成员变量被多个线程共享 就有线程安全问题
所以以后 conn这种对象做成线程内私有的局部变量 而不是共享的成员变量
例六
public class MyServlet extends HttpServlet {
// 是否安全
private UserService userService = new UserServiceImpl();
public void doGet(HttpServletRequest request, HttpServletResponse response) {
userService.update(...);
}
}
public class UserServiceImpl implements UserService {
public void update() {
UserDao userDao = new UserDaoImpl();
userDao.update();
}
}
public class UserDaoImpl implements UserDao {
// 是否安全
private Connection = null;
public void update() throws SQLException {
String sql = "update user set password = ? where username = ?";
conn = DriverManager.getConnection("","","");
// ...
conn.close();
}
}
/*安全
例六和例五 Connection 还是成员变量
但是在service当中 每次调用dao的时候会创建一个新的connection对象 而不是 作为service的成员变量的
但是不推荐这样写 还是要把connection作为局部变量来使用*/
例七
public abstract class Test {
public void bar() {
// 是否安全
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
foo(sdf);
}
public abstract foo(SimpleDateFormat sdf);
public static void main(String[] args) {
new Test().bar();
}
}
//其中 foo 的行为是不确定的,可能导致不安全的发生,被称之为外星方法
public void foo(SimpleDateFormat sdf) {
String dateStr = "1999-10-11 00:00:00";
for (int i = 0; i < 20; i++) {
new Thread(() -> {
try {
sdf.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}
}
/*不安全:
SimpleDateFormat sdf 是局部 变量,本改是线程安全的呀
虽然是局部变量还要看他会不会 暴露给其他线程
比如说这个类 他是抽象的 他的方法是抽象的
所以就有机会将局部变量的这个对象传递给抽象方法 public abstract foo(SimpleDateFormat sdf);
那么他的子类 就可能会 有不确定的行为 所以就有并发问题
因此局部变量也要看对象的引用是否暴露了
请比较 JDK 中 String 类的实现
在string当中是不可变的为什么还要用final修饰?
当不是final的时候 他的子类有可能就覆盖掉父类当中的一些行为导致不安全 是经典的闭合原则*/
看完相信你一定有所收获,一起加油!