如何搭建电影网站服务器?制作一个电影网站需要多少预算?
摘要:怎么做电影网站服务器,做网站需要哪些钱,电脑上怎么安装wordpress,禄劝网络推广外包一、变量的线程安全分析 成员变量和静态变量是否线程安全? ● 如果它们没有共享,则线程安全
怎么做电影网站服务器,做网站需要哪些钱,电脑上怎么安装wordpress,禄劝网络推广外包一、变量的线程安全分析
成员变量和静态变量是否线程安全#xff1f;
● 如果它们没有共享#xff0c;则线程安全 ● 如果它们被共享了#xff0c;根据它们的状态是否能够改变#xff0c;又分两种情况 —— 如果只有读操作#xff0c;则线程安全 —— 如果有读写操作
● 如果它们没有共享则线程安全 ● 如果它们被共享了根据它们的状态是否能够改变又分两种情况 —— 如果只有读操作则线程安全 —— 如果有读写操作则这段代码是临界区需要考虑线程安全
局部变量是否线程安全 ● 局部变量是线程安全的 ● 但局部变量引用的对象则未必 —— 如果该对象没有逃离方法的作用访问它是线程安全的 —— 如果该对象逃离方法(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:stack1, locals1, args_size00: 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 (i1)).start();}}
}
class ThreadUnsafe {ArrayListString 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) {ArrayListString list new ArrayList();for (int i 0; i loopNumber; i) {method2(list);method3(list);}}public void method2(ArrayListString list) {list.add(1);}private void method3(ArrayListString 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) {ArrayListString list new ArrayList();for (int i 0; i loopNumber; i) {method2(list);method3(list);}}private void method2(ArrayListString list) {list.add(1);}private void method3(ArrayListString list) {list.remove(0);}
}
// 添加子类继承ThreadSafe在子类中覆盖/重写method3
class ThreadSafeSubClass extends ThreadSafe{Overridepublic void method3(ArrayListString 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 有 replacesubstring 等方法【可以】改变值那么这些方法又是如何保证线程安 全的其没有改变字符串的值而是创建了一个新的字符串对象对原有的字符串复制里面包含截取后的结果用新的对象实现对象的不可变效果
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有的存储内容而有的读取内容会造成混乱*/MapString,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) {// 使用上述变量}
}例2Servlet调用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 {ListThread 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父类中的一些行为导致线程不安全的发生子类可能会破坏父类中某一方法的行为
