当前位置: 首页 > news >正文

东莞h5网站建设中国疫情最新消息

东莞h5网站建设,中国疫情最新消息,国内最好的视频剪辑培训机构,外贸网站平台排名一、变量的线程安全分析 成员变量和静态变量是否线程安全? ● 如果它们没有共享,则线程安全 ● 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况 —— 如果只有读操作,则线程安全 —— 如果有读写操作&am…

一、变量的线程安全分析

成员变量和静态变量是否线程安全?

● 如果它们没有共享,则线程安全
● 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况
—— 如果只有读操作,则线程安全
—— 如果有读写操作,则这段代码是临界区,需要考虑线程安全

局部变量是否线程安全?
● 局部变量是线程安全的
● 但局部变量引用的对象则未必
—— 如果该对象没有逃离方法的作用访问,它是线程安全的
—— 如果该对象逃离方法(eg:使用return)的作用范围,需要考虑线程安全

1.1 线程安全分析-局部变量

public static void test1() {int i = 10;i++;
}

每个线程调用 test1() 方法时局部变量 i,会在每个线程的栈帧内存中被创建多份,因此不存在共享

反编译后的二进制字节码:

public static void test1();descriptor: ()V
flags: ACC_PUBLIC, ACC_STATICCode:stack=1, locals=1, args_size=00: bipush 10        // 准备常数10赋值给i2: istore_0         // 赋值给i3: iinc 0, 1        // 在局部变量i的基础上自增 6: return           // 方法运行结束返回LineNumberTable:line 10: 0line 11: 3line 12: 6LocalVariableTable:Start Length Slot Name Signature3 4 0 i I

每个方法调用时都会创建一个栈帧,每个线程有自己独立的栈和栈帧内存(局部变量会在栈帧中被创建多份)
如图:
在这里插入图片描述
若局部变量的为对象,则稍有不同
观察一个成员变量的例子

public class TestThreadSafe {// 创建两个线程(每个线程调用method1循环200次)static final int THREAD_NUMBER = 2;static final int LOOP_NUMBER = 200;public static void main(String[] args) {ThreadUnsafe test = new ThreadUnsafe();for (int i = 0; i < THREAD_NUMBER; i++) {new Thread(() -> {test.method1(LOOP_NUMBER);}, "Thread" + (i+1)).start();}}
}
class ThreadUnsafe {ArrayList<String> list = new ArrayList<>();public void method1(int loopNumber) {for (int i = 0; i < loopNumber; i++) {// { 临界区, 会产生竞态条件// method2()、method3()访问的为共享资源(多个线程执行时会发生指令交错)method2();method3();// } 临界区}}private void method2() {// 往集合中加一个元素list.add("1");}// 往集合中移除一个元素private void method3() {list.remove(0);}
}

多个线程执行时会发生指令交错会产生问题

运行结果:其中一种情况是线程1的method2()还未add,线程2的method3()尝试移除,此时集合为空就会报错
在这里插入图片描述

分析
● 无论哪个线程中的 method2 引用的都是同一个对象中的 list 成员变量
● method3 与 method2 分析相同
在这里插入图片描述
若将 list 修改为局部变量就不会存在上述问题

class ThreadSafe {public final void method1(int loopNumber) {ArrayList<String> list = new ArrayList<>();for (int i = 0; i < loopNumber; i++) {method2(list);method3(list);}}public void method2(ArrayList<String> list) {list.add("1");}private void method3(ArrayList<String> list) {System.out.println(1);list.remove(0);}
}

分析
● list 是局部变量,每个线程调用时会创建其不同实例,没有共享
● 而 method2 的参数是从 method1 中传递过来的,与 method1 中引用同一个对象(均引用的为堆中的对象)
● method3 的参数分析与 method2 相同
在这里插入图片描述

1.2 线程安全分析-局部变量引用

方法访问修饰符带来的思考,如果把 method2 和 method3 的方法修改为 public 会不会代理线程安全问题?
● 情况1:有其它线程调用 method2 和 method3
● 情况2:在 情况1 的基础上,为 ThreadSafe 类添加子类,子类覆盖 method2 或 method3 方法,即

class ThreadSafe {public final void method1(int loopNumber) {ArrayList<String> list = new ArrayList<>();for (int i = 0; i < loopNumber; i++) {method2(list);method3(list);}}private void method2(ArrayList<String> list) {list.add("1");}private void method3(ArrayList<String> list) {list.remove(0);}
}
// 添加子类继承ThreadSafe,在子类中覆盖/重写method3
class ThreadSafeSubClass extends ThreadSafe{@Overridepublic void method3(ArrayList<String> list) {// 重写后重启一个新的线程new Thread(() -> {list.remove(0);}).start();}
}

此时会带来线程安全问题,新的线程可以访问到共享变量
从这个例子可以看出 private 或 final 提供【安全】的意义所在,可以体会开闭原则中的【闭】(使用private修饰符避免子类改变覆盖其行为)

1.3 线程安全分析-常见类-组合调用

常见线程安全类
● String
● Integer
● StringBuffer
● Random
● Vector
● Hashtable
● java.util.concurrent 包下的类

这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的。也可以理解为

Hashtable table = new Hashtable();   // 查看源码,发现其底层被synchronized关键字修饰new Thread(()->{table.put("key", "value1");
}).start();new Thread(()->{table.put("key", "value2");
}).start();

● 它们的每个方法是原子的
● 但注意它们多个方法的组合不是原子的,见后面分析

线程安全类方法的组合

分析下面代码是否线程安全?
get()、put()底层均有synchronized修饰

Hashtable table = new Hashtable();
// 线程1,线程2
if( table.get("key") == null) {table.put("key", value);
}

将两个方法组合到一起使用就不是线程安全的,中间会受到线程上下文切换的影响,其只能保证每一个方法内部代码是原子的。要使其组合后仍可以保证原子性,还需在外层加以线程安全的保护!

eg:线程1、2均执行方法内的代码,线程1执行get(“key”) == null,还未执行完,线程发生上下文切换轮到线程2执行,线程2也执行到此处得到的get(“key”) == null,线程2发现为null后put(“key”, v2),完成后又切换为线程1,线程1又put(“key”, v1)。理论上判断为空时,我们只存放一个键值对,实际上put(“key”, value)被执行两次,导致后一个执行的put将前一个执行put的结果覆盖,不是我们锁预期的效果。
在这里插入图片描述

1.3 线程安全分析-常见类-不可见

不可变类线程安全
String、Integer 等都是不可变类,因为其内部的状态(属性)不可以改变,因此它们的方法都是线程安全的(只可读不可修改)

那么,String 有 replace,substring 等方法【可以】改变值,那么这些方法又是如何保证线程安
全的?(其没有改变字符串的值,而是创建了一个新的字符串对象对原有的字符串复制,里面包含截取后的结果)用新的对象实现对象的不可变效果

public class Immutable{private int value = 0;public Immutable(int value){this.value = value;}public int getValue(){return this.value;}
}

如果想增加一个增加的方法应该如何实现?

public class Immutable{private int value = 0;public Immutable(int value){this.value = value;}public int getValue(){return this.value;}public Immutable add(int v){return new Immutable(this.value + v);} 
}

1.4 线程安全分析-实例分析

例1
Servlet运行Tomcat环境下,只有一个实例(会被Tomcat多个线程所共享使用)

public class MyServlet extends HttpServlet {// 是否安全?/*Map不是线程安全的,线程安全的实现有HashTable,而HashMap并非线程安全,若多个请求线程访问同一个Servlet,有的存储内容而有的读取内容,会造成混乱*/Map<String,Object> map = new HashMap<>();// 是否安全?/*是线程安全的,字符串属于不可变量*/String S1 = "...";// 是否安全?(是)final String S2 = "...";// 是否安全?(不是)Date D1 = new Date();// 是否安全?/*final修饰后只能说明D2这个成员变量的引用值固定,而Date中的其它属性还可以可变的)*/final Date D2 = new Date();public void doGet(HttpServletRequest request, HttpServletResponse response) {// 使用上述变量}
}

例2:Servlet调用Service

public class MyServlet extends HttpServlet {// 是否安全?/*不是===>Servlet只有一份,而userService是Servlet的一个成员变量,因此也只有一份,会有多个线程共享使用*/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++;}
}

例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));}
}

spring中若未指定scope为非单例。默认为单例模式(需要被共享,其成员变量也需被共享,因此无论是执行复制操作还是下面执行减法运算,都会涉及到对象对成员变量的并发修改,会存在线程安全问题)

如何解决上述问题?
可以使用环绕通知(环绕通知可以将开始时间、结束时间变为环绕通知中的局部变量,此时便可保证线程安全)

例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) {// ...}}
}

① Dao无成员变量,意味着即使有多个线程访问也不能修改它的属性、状态===>没有成员变量的类都是线程安全的
② Connection也是线程安全的,Connection属于方法内的局部变量,即使有多个线程访问,线程1创建的为Connection1而线程2创建的为Connection2,两者独立互不干扰

例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 {// 是否安全(不安全)/*Connection不为方法内的局部变量,而是做为Dao的成员变量(Dao只有一份会被多个线程共享,其内的共享变量也会被线程共享)*//*eg:线程1刚创建Connection还未使用,此时线程2close()*/private Connection conn = null;public void update() throws SQLException {String sql = "update user set password = ? where username = ?";conn = DriverManager.getConnection("","","");// ...conn.close();}
}

对于Connection这种对象应将其变为线程内私有的局部变量,而不是设置为共享的成员变量

例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();}
}

UserDao在Service中作为方法内的局部变量存在,每一个线程调用时都会创建一个新的UserDa0对象,其内部的Connection也为新的。因此线程安全。

例7

public abstract class Test {public void bar() {// 是否安全/*SimpleDateFormat虽然为方法内的局部变量,但其会暴露给其他线程(抽象方法其子类可能会产生一些不恰当的操作)*/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();}
}

不想向外暴露的变量可以使用final、private修饰,这样可以增强类的安全性

可以比较 JDK 中 String 类的实现:String类是不可变的,同时也是final的

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();}});

为何将String类涉及为final?:若不使用final修饰,其子类也许可能覆盖掉String父类中的一些行为,导致线程不安全的发生(子类可能会破坏父类中某一方法的行为)

http://www.hkea.cn/news/346748/

相关文章:

  • 重庆网站建设制作公司百度客服人工在线咨询电话
  • 微信公众号平台入口官网奶盘seo伪原创工具
  • 泉州网站建设公司推荐宁德市地图
  • 大厂县住房和城乡建设局网站刷百度指数
  • 低代码开发平台优缺点昆山seo网站优化软件
  • 网站开发年终总结网络营销战略的内容
  • 建立门户网站的意义营销推广网
  • 网站建设网站软件有哪些百度推广开户费用标准
  • 找家装修公司家装吉林seo外包
  • 保定医疗网站建设公司会计培训班初级费用
  • 最好的销售管理系统seo发帖网站
  • 德州乐陵德州seo公司seo批量建站
  • 贵州省建设监理协会官方网站seo代运营
  • 北京哪家做网站优化账号权重查询
  • 大唐网站建设培训管理平台
  • 男人和女人在床上做那个网站网络营销策划推广公司
  • 深圳市招投标交易中心天津谷歌优化
  • 厦门园网站忱建设百度推广怎么联系
  • 网站优化页面动态网站建设
  • 做网站域名公司每日重大军事新闻
  • 网站改版数据来源表改怎么做外链百科
  • wordpress怎样做单页网站谷歌查询关键词的工具叫什么
  • 县城做二手车网站自己建网站需要多少钱
  • 有没有专业做挂的网站引流推广方案
  • 购物网站开发文献综述百度收录需要多久
  • 营销型企业网站建设案例设计公司网站
  • 国际外贸网站电子商务
  • 南充做网站 www.xinbay.com全国免费发布广告信息
  • 备案 个人网站软件开发培训中心
  • 江苏网站建设网络推广关键词批量调词 软件