jQuery火箭图标返回顶部代码

变量的线程安全分析-实例分析

实例分析

例1

Servlet 是运行在 tomcat 下面的,只有一个实例,会被 tomcat 的多个线程所共享使用,所以里面的成员变量都有可能存在线程安全问题。

public class MyServlet extends HttpServlet {
    // 是否安全?  不安全
    Map<String,Object> map = new HashMap<>();
    // 是否安全?  安全:字符串不可变,是线程安全的
    String S1 = "...";
    // 是否安全?  安全
    final String S2 = "...";
    // 是否安全?  不安全:会被共享
    Date D1 = new Date();
    // 是否安全?  不安全:D2成员变量的引用值固定了,不能变。但是日期里面的其他属性是可以变的
    final Date D2 = new Date();

    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        // 使用上述变量
    }
}
例2

UserService 是 MyServlet的成员变量,但是 UserService 里面又存在 count 成员变量。所以可以理解为 UserService 是有状态的,count 会被多个线程共享、修改。

public class MyServlet extends HttpServlet {
    // 是否安全?  不安全:UserService 里面存在 count 成员变	// 量,可以被其他线程共享
    private UserService userService = new UserServiceImpl();

    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        userService.update(...);
    }
}

public class UserServiceImpl implements UserService {
    // 记录调用次数:要对count做一些保护
    private int count = 0;

    public void update() {
        // ...
        count++;
    }
}
例3

解决办法:可以做成环绕通知,把 start 变为局部变量

@Aspect
@Component
public class MyAspect {
    // 是否安全? 不安全:spring默认是单例,单例就是要被共享的,里面的成员变量也是要被共享的。
    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));
    }
}
例4

方法内的局部变量,是线程安全的, 成员变量,要看他是否有状态,是否有其他方法会对它进行修改。

public class MyServlet extends HttpServlet {
    // 是否安全  安全:虽然 UserServiceImpl 中有一个成员变量,但是它是私有的,没有其他地方能修改它
    private UserService userService = new UserServiceImpl();

    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        userService.update(...);
    }
}

public class UserServiceImpl implements UserService {
    // 是否安全 安全:虽然是成员变量,会被多个线程所共享,但是 UserDao 中没有可更改的属性,里面没有成员变量,也叫无状态的。
    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) {
            // ...
        }
    }
}
例5
public class MyServlet extends HttpServlet {
    // 是否安全,否:里面有成员变量,是有状态的
    private UserService userService = new UserServiceImpl();

    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        userService.update(...);
    }
}

public class UserServiceImpl implements UserService {
    // 是否安全:否:UserDaoImpl 是成员变量,会被多个线程所共享
    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();
    }
}
例6
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() {
        //局部变量,每次都新建一个对象,UserDaoImpl里面的成员变量每次都是新建,不会被共享
        UserDao userDao = new UserDaoImpl();
        userDao.update();
    }
}

public class UserDaoImpl implements UserDao {
    // 是否安全,是:但是不推荐这种写法,还是把 Connection 作为局部变量最为安全
    private Connection = null;
    public void update() throws SQLException {
        String sql = "update user set password = ? where username = ?";
        conn = DriverManager.getConnection("","","");
        // ...
        conn.close();
    }
}
例7

局部变量对象引用泄漏

public abstract class Test {

    public void bar() {
        // 是否安全,否:虽然是局部变量,但是它会传个其他方法,比如这里传递给了 foo() 方法,foo() 中可能对它进行修改
        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();
    }
}

请比较 JDK 中 String 类的实现,不可变,并且类是 final 的,确保没有子类去修改它

例8
private static Integer i = 0;
public static void main(String[] args) throws InterruptedException {
    List<Thread> list = new ArrayList<>();
    for (int j = 0; j < 2; j++) {
        Thread thread = new Thread(() -> {
            for (int k = 0; k < 5000; k++) {
                synchronized (i) {
                    i++;
                }
            }
        }, "" + j);
        list.add(thread);
    }
    list.stream().forEach(t -> t.start());
    list.stream().forEach(t -> {
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    log.debug("{}", i);
}
posted @ 2022-11-07 10:27  天下没有收费的bug  阅读(62)  评论(0编辑  收藏  举报