零碎笔记(三)

来源:互联网 发布:csgo职业选手优化fps 编辑:程序博客网 时间:2024/06/05 19:36

       平时数据库处理时,总要接触关于数据同步修改的问题,有时候需要在业务处理时保证业务数据同步(一般这种情况都包含统计字段,也就是说需要根据前面的值算出后面的值的字段),例如,两个人同时查询出1条记录后,先后对其加1,保存入数据库中,这样就可能造成保存问题,因此,需要对该数据进行同步。这里有3种方法可以采用:
       1.增加一个版本字段,查询时得到该字段,修改后增加1,以后其他的修改需要进行比较,如果不同,则保存失败  (需要额外比较)
       2.依赖数据库本身的机制,对将要修改的数据进行锁定(该方法会造成数据库访问瓶颈)
       3.在保存时使用复合sql语句进行修改,例如 updata table_name tem1 set tem1.filed=(select tem2.filed+1 from table_name tem2 where tem1.id=tem2.id) where tem1.id=1。


       使用PrintWriter(response.getWriter())会占用一些系统开销,因为它是为处理字符流的输出输出功能。因此PrintWriter应该使用在确保有字符集转换的环境中, servlet容器不用处理字符集转换的时候,应该使用ServletOutputStream(response.getOutputStream()),这样可以消除字符转换开销。
       无论是ServletOutputStream还是PrintWriter在输出数据的时候,都需要在调用了flush()方法之后才可以将数据发送给客户端。如果要传送给客户端的数据量非常大,在最后调用flush()方法输出数据显然是相对低效的。因此可以采用数据部分输出的方式将数据发送给客户端。


       循环遍历二维数组int[][] a = new int[5][10],第一种方法是按行遍历,第二种方式是按列遍历,两者效率上是有很大差异的。
       单个数组的内存空间是连续的,当获取a[0][0]时,Cache line操作通常会将a[0][0]相关的一些数组元素(例如:a[0][1], a[0][2], a[0][3], a[0][4], a[0][5], a[0][6], a[0][7]])全部都Cache到CPU的缓存中。当使用第一种遍历时,这些连续的数组都只需要Cache一次,就可以遍历完成。反过来,如果访问完a[0][0]后不是访问a[0][1],而是访问a[1][0],由于两块区域并非连续的,所以不会一次被Cache,需要重新访问内存才行。


       一个对象创建以及将其赋值给一个引用是两个动作,对象创建还需要经历分配空间和属性初始化的过程,普通的属性初始化允许发生在构造方法return之后(指令重排)。在大部分情况下,对象的使用都是在线程内部定义的,在单线程中是可靠的,或者说在单线程中要求使用对象引用时,该对象已经被初始化好了。但如果在此过程中有另一个线程通过这个未初始化好的对象引用读取相应的属性,那么就可能读取到的并不是真正想要的。在Java中final修饰字段可以保证这一点,避免出现这种逃逸问题。


       Hotspot VM使用对象头部的一个指针指向Class区域的方式来找到对象的Class描述,以及内部方法、属性入口。除此之外,还有:是否加锁、GC标志位、Monir GC次数、对象默认的hashCode等信息,后面部分的空间通常叫做Mark Word。
 简单的对象模型(以字节码的方式存储在方法区中,Class对象在堆中):
Mark Word指向Class的指针对象的Body部分对齐字节

Thread.sleep(0)的作用:
       由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。


如何检测一个线程是否持有对象监视器:
       Thread类提供了一个holdsLock(Object obj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true,注意这是一个static方法,这意味着“某条线程”指的是当前线程。


乐观锁和悲观锁:
       (1)乐观锁:对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-设置这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
       (2)悲观锁:对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized直接上了锁就操作资源了。


Hashtable的size()方法中明明只有一条语句”return count”,为什么还要做同步?
       某个方法中如果有多条语句,并且都在操作同一个类变量,那么在多线程环境下不加锁,势必会引发线程安全问题,这很好理解,但是size()方法明明只有一条语句,为什么还要加锁?
       主要原因有两点:
       (1) 同一时间只能有一条线程执行固定类的同步方法,但是对于类的非同步方法,可以多条线程同时访问。所以,这样就有问题了,可能线程A在执行 Hashtable的put方法添加数据,线程B则可以正常调用size()方法读取Hashtable中当前元素的个数,那读取到的值可能不是最新的,可能线程A添加了完了数据,但是没有对size++,线程B就已经读取size了,那么对于线程B来说读取到的size一定是不准确的。而给size()方法加了同步之后,意味着线程B调用size()方法只有在线程A调用put方法完毕之后才可以调用,这样就保证了线程安全性
       (2)CPU 执行代码,执行的不是Java代码,这点很关键。Java代码最终是被翻译成汇编代码执行的。即使Java代码只有一行,但不意味着对于底层来说这句语句的操作只有一个。一 句”return count”假设被翻译成了三句汇编语句执行,完全可能执行完第一句,线程就切换了。


线程类的构造方法、静态块是被哪个线程调用的?
       线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。举个例子,假设Thread2中new了Thread1,main函数中new了Thread2,那么:
       (1)Thread2的构造方法、静态块是main线程调用的,Thread2的run()方法是Thread2自己调用的
       (2)Thread1的构造方法、静态块是Thread2调用的,Thread1的run()方法是Thread1自己调用的


高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?
       (1)高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换。
       (2)并发不高、任务执行时间长的业务要区分开看:a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务。b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样,线程池中的线程数设置得少一些,减少线程上下文的切换。
       (3) 并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。

0 0
原创粉丝点击