线程安全分析

在通过例子分析线程安全之前先 看了解这些 知识
在去看例子会清晰一点

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的时候 他的子类有可能就覆盖掉父类当中的一些行为导致不安全 是经典的闭合原则*/

看完相信你一定有所收获,一起加油!

你可能感兴趣的:(线程安全分析,安全,java,开发语言,后端,多线程)