Javaの並行プログラミングノート5

スレッドセーフな分析変数

メンバ変数と静的変数はスレッドセーフですか?

  • 彼らは、セキュリティスレッドを共有していない場合
  • それらが共有されている場合、状態はその変更できるかどうかに応じて、これは、2つのケースに分けられます
    • 場合にのみ、読み出し動作、セキュリティスレッド
    • あなたは、読み取りおよび書き込み操作を持っている場合、これはコードのクリティカルセクションで、私たちは、スレッドの安全性を検討する必要があります

ローカル変数は、スレッドセーフですか?

  • ローカル変数は、スレッドセーフです
  • しかし、オブジェクトは必ずしもローカル変数の参照ではありません
    • オブジェクトがアクセス・メソッドの役割をエスケープしていない場合は、スレッドセーフであります
    • オブジェクトがスコープのメソッドを逃げることであるならば、我々は、スレッドの安全性を検討する必要があります

一般的なスレッドセーフなクラス

  • ストリング
  • 整数
  • StringBufferの
  • ランダム
  • ベクター
  • ハッシュ表
  • クラスjava.util.concurrentパッケージ

彼らはここで言うとき、彼らに同じインスタンスにメソッドを呼び出す複数のスレッドが、スレッドセーフであることをスレッドセーフな手段です。これは、として理解することができます

  • それらのそれぞれは、メソッド原子であります
  • しかし、彼らは複数の方法の原子組成ではないことに注意してください。

それのハイライトで直接見て。

ケーススタディ

例1:

public class MyServlet extends HttpServlet {
	 // 是否安全?
	 Map<String,Object> map = new HashMap<>();
	 // 是否安全?
	 String S1 = "...";
	 // 是否安全?
 	final String S2 = "...";
	 // 是否安全?
	 Date D1 = new Date();
	 // 是否安全?
	 final Date D2 = new Date();

 public void doGet(HttpServletRequest request, HttpServletResponse response) {
  		// 使用上述变量
	 }
}
Map不安全
String安全
final String 安全
 Date 不安全 因为可修改
 final Date d2 不安全 
 	1final 固定了Data的引用值
 	2、Data其他属性 比如:年月日可变

例2:

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++;
 }
}
private UserService userService = new UserServiceImpl(); 线程不安全
1、userService 是UserServiceImpl中的成员变量,所以会有多个线程共享使用userService 
2、UserServiceImpl中的count只有一份,所以是共享资源
3、UserServiceImpl中的update()属于临界区,多个线程可能对update()进行修改操作

例3:

@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));
 }
}
这段代码有线程安全问题
1、没有加@Scope(),所以他是单例
2、因为是单例,需要被共享,所以成员变量需要被共享
3、使用多例无法解决问题,因为使用前置通知可以是一个对象,然后后置通知有可能是另一个对象,无法统计时间
解决方法
1、使用环绕通知,可以把获取时间的变量做成局部变量

例4:

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) {
		 // ...
	 }
 }
}
1、UserDaoImpl没有成员变量,线程安全
2、 Connection conn也是安全的,因为是局部变量。有多个线程同时访问,线程一创建conn1,线程二创建conn2,互不干扰
3、UserServiceImpl 使用userDao ,线程安全   因为UserDao虽然被共享,但是没有可修改的属性(无状态,没有成员变量)
4、MyServlet 使用userService 线程安全  因为userService 虽然有userDao 成员变量,但是他是private,而且userDao不能被修改

例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 {
 		// 是否安全
 		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();
 	}
}
1、跟例4一样的不分析
2private Connection conn = null;作成了UserDaoImpl 的成员变量
3、然而UserDaol ,UserService,MyServlet 都只有一份,所以UserDaoImpl 会被多个线程共享,所以conn被多个线程共享
4、所以private Connection conn = null;要做成私有的局部变量,而不是共享的成员变量
  • スレッドあたりのスタックフレームメモリ内に作成されることを意味します。
  • 複数のコピーが各スレッドのスタックフレームメモリ内に作成されている場合は、そうは共有されません

例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() {
		 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();
	 }
}
1、在UserServiceImpl每次都会创建新的userDao 作为方法内的局部变量,没有线程安全问题
2、线程一调用Service中的update,创建一个新的userDao,于是Connection也是新的;线程二调用后也是新的。
3、不推荐这种做法,在此方法没有问题,其他例子里可能有隐患。把Connection做成线程内的局部变量最佳

例7:

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();
	 }
}
1、sdf 是局部变量,传递给抽象方法,子类可能做不恰当的事情
2、子类中的父方法在一个新线程被使用,造成并发访问同一个对象

JDKのStringクラスを達成比較

最終プライベートおよびセキュリティスレッドを保護するためにある程度。クロージングの開閉の原則、親クラスの動作に影響を与えるためにサブクラスをしましょう
公開された93元の記事 ウォン称賛31 ビュー30000 +

おすすめ

転載: blog.csdn.net/weixin_43866567/article/details/104544087