技术知识
1. go相关知识
由于网络或者人为原因,多方系统数据不同步,为了分析本地日志然而日志分布在不同机器上,为了解决这个问题,学习go语言写了第三方插件上传日志。以便在出问题时候能够更方便的查询日志并处理相关问题
Go语言优缺点:
优点:
在网络编程方面比较有优势,自带网络库比较全,做轻量级的应用比较有优势。可以根据平台去build,生成可执行文件。如windows上生成exe文件,直接可以打开安装。并发实现比较容易(轻量级线程)。用什么引入什么库(如:go get log),
1.脚本化的语法(容易上手,开发效率非常高)
2.静态类型+编译型,程序运行有保障
3.原生的支持并发编程(在服务端开发具有强大的优势,降低了开发和维护的成本)
4 标准库比较强大
5 编译简单,最后就一个可执行文件
6,目前运行程度赶上java
缺点:1.语法糖并没有其他语言那么多
2.目前程序的运行速度不及c,当今的操作系统有c构造
3.第三方库暂时不像java语言的编程那么多
代码编写如下:
变量命名:var age=40 或者 age:=40
funcGetResident(w http.ResponseWriter, r *http.Request) {
gHexId := util.GetPathParam(r, "rid")
curUser := util.GetCurrentUser(r.Context())
resident, err := services.GetResidentById(gHexId,curUser.Id.Hex())
if err != nil {
util.ErrorWithJSON(w, "Residentnot found", http.StatusNotFound)
return
}
body, err := json.Marshal(resident)
if err != nil {
glog.Errorln(err)
util.ErrorWithJSON(w, "Residentdata can't marshal", http.StatusInternalServerError)
return
}
util.ResponseWithJSON(w, body, http.StatusOK)
}
funcGetResidentById(gHexId, uHexId string) (*types.Resident, error) {
session := mongo.GetSession()
defer session.Close()
c := getResidentCollection(session)
var resident types.Resident
err :=c.FindId(bson.ObjectIdHex(gHexId)).One(&resident)
if err != nil {
return nil, err
}
if resident.Creator_id != uHexId {
return nil, errors.New("Notfound")
}
return &resident, nil
}
2. dubbo zookeeper
Dubbo:是rpc框架,主要完成服务注册服务发现、路由、客服端负载均衡。
负载均衡:主要是客户端做负载均衡,客户端将需要服务
负载均衡策略:轮询、权重、随机
Dubbo注册中心包括zookeeper、redis、数据库
Dubbo最主要的组件:注册中心、服务提供者、服务消费者
Zookeeper:分布式注册中心,一般都是用于配置信息(无ledaer就选主,与follower同步),主机数是2n+1,至少是三台主机。
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
Dubbo配置项:
服务提供方配置项:服务名称、协议(dubbo)、ip地址、端口
<dubbo:annotation package="com.kaolafm.statistics.service"/>
<dubbo:application name="${dubbo.application.name}"/>
<dubbo:protocol name="${dubbo.protocol.name}"port="${dubbo.protocol.port}"register="${dubbo.protocol.register}"/>
<dubbo:registry address="${dubbo.registry.address}"register="${dubbo.protocol.register}"/>
服务消费方配置项:服务名称、ip地址
dubbo.registry.register=false
dubbo.registry.address=zookeeper://192.168.4.140:2181?backup=192.168.4.139:2181,192.168.4.141:2181
dubbo.registry.application_name=statistics_consumer_develop
ZooKeeper的工作原理
Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。
当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。
按我的理解,您可以把dubbo服务想象成学校里的一个学生,并且对应有一个学号,zookeeper则是想象成一个教务网管理系统。我们可以通过教务网管理系统,
查找到对应的学生。我们首先通过注册入学,将学生和学号对应绑定。 比方说项目是一个分布式的项目,web层与 service层被拆分了开来,
部署在不同的tomcat中,我在web层需要调用 service层的接口,但是两个运行在不同tomcat下的服务无法直接互调接口,那么就可以通过zookeeper和dubbo实现。
我们通过dubbo 建立ItemService这个服务,并且到zookeeper上面注册,填写对应的zookeeper服务所在的IP及端口号。
按照我上面的比喻就是,学生注册入学(接口是学号,学生本人是impl实现),填写学校教务网网址(就是zookeeper)】
3. cas(单点登录)
case原理:
多个不同的应用系统,使用同一个用户名密码去登录,方便用户记住用户名密码。
经过分析,Cookie单点登录认证太过于分散,每个网站都持有一份登陆认证代码。于是我们将认证统一化http://passport.com。形成一个独立的服务。当我们需要登录操作时,则重定向到统一认证中心
第一步:用户访问www.qiandu.com。过滤器判断用户是否登录,没有登录,则生成认证码重定向(302)到网站http://passport.com,
第二步:重定向到passport.com,输入用户名密码。用户名密码认证成功之后,认证中心生成认证码的令牌,passport.com将用户登录的信息记录到服务器的session中。重新定向到www.qiandu.com(callback:携带重新定向url)
第三步:passport.com给浏览器发送一个特殊的凭证,浏览器将凭证交给www.qiandu.com,www.qiandu.com则拿着浏览器交给他的凭证去passport.com验证凭证是否有效,从而判断用户是否登录成功(在本地session中创建用户登录信息)。
第四步:登录成功,浏览器与网站之间进行正常的访问。
第五步:访问 www.qiandu2.com,过程一致,不同的是不会弹出登录信息,直接生成凭证跳转
<beans:bean id="casEntryPoint"
class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
<beans:property name="loginUrl" value="${cas.server.host}/login"/>
<beans:property name="serviceProperties"ref="serviceProperties"/>
</beans:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="casAuthenticationProvider"/>
</authentication-manager>
4.websocket
一.Websocket机制
WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是:WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样;WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信。
二. websocket与传统轮询的区别
轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给客户端的浏览器。这种传统的HTTP request 的模式带来很明显的缺点– 浏览器需要不断的向服务器发出请求,然而HTTP request 的header是非常长的,里面包含的有用数据可能只是一个很小的值,这样会占用很多的带宽。
三.Springwebsocket与tomcatwebsocket的对比
经过测试发现tomcat websocket的一些问题
1、tomcat websocket官方文档上建议使用nio模式,tomcat 默认启动是bio,
BIO多线程对某资源进行IO操作时会出现阻塞,即一个线程进行IO操作完才会通知另外的IO操作线程,必须等待。
2、tomcat websocket支持兼容性不是很好,
ie8以下的浏览器都不支持。http://tomcat.apache.org/tomcat-7.0-doc/web-socket-howto.html
Springwebsocke的优点
1.兼容性比较好,支持多种模式去兼容低版本的浏览器(如ie8),js会根据不同的浏览器版本选择不同的通讯方式。
2.可以在spring websocket中设置允许访问的域
registry.addHandler(handler,"/ws").addInterceptors(newHandShake(userService)).setAllowedOrigins(“www.kaolafm.com”);
3. 与spring无缝集成,不依赖于具体容器(jetty, tomcat, weblogic等)
4. 方便扩展。可以与RabbitMQ,ActiveMQ等消息服务器集成。
四.websocket安全问题
模拟websocket攻击,跟上次主播站注册的攻击方式一样,无限的连接。建议使用防火墙机制防止攻击,如一段时间内同一ip不断重试次数超过某一个阈值时把ip加入黑名单。
五.Websocket并发
- 并发测试(websocket client库,多线程)
- 攻击测试(连接后立即断开再重试)
- Websocket并发参数调优
- linux服务器socket相关参数调优
Websocket做了集群(使用两台机子),web端使用js去连接websocket服务
服务中使用:spring websocket
Js中使用:
<scriptsrc="js/sockjs.js"></script>
<scriptsrc="js/stomp.js"></script>
5. http加密传输方式
https传输过程最为安全,可以在tomcat下面配置ssl证书
客户端:生成数字签名(双方约定签名格式,使用md5进行数字签名),签名方式进行传输
服务端:通过传过来的参数及双方约定的格式进行md5算法实现签名是否有效。签名具有时效性,需要超时判断
代码:
/**
* 获取检查检验项
*/
publicString getPersonalDetail(CheckUpReportSearchDTO dto) throws IOException{
Stringnowtime=DateUtil.Date2Str(new Date(),"yyyyMMddHHmmss");//当前系统时间,有效时间为10分钟
Stringmd5ParmeXingMing=dto.getXingMing();
StringXingMing=URLEncoder.encode(dto.getXingMing(),"UTF-8");
Stringparame="iCode=900005"+"&guid="+SystemConstant.guid+"&ShenFenZH="+dto.getShenFenZH()+"&XingMing="+XingMing+"×pan="+nowtime;
Stringmd5Parme="iCode=900005"+"&guid="+SystemConstant.guid+"&ShenFenZH="+dto.getShenFenZH()+"&XingMing="+md5ParmeXingMing+"×pan="+nowtime;
Stringsign=StringUtils.getSign(md5Parme);
Stringurl=ApiPropertiesUtils.getValue("inspectionReportUrl")+"/chs/api_chs.ashx"+"?"+parame+"&"+sign;
logger.info("url="+url);
StringjsonResult=HttpClientUtil.JSONData(url);//获取第三方数据
logger.info("jsonResult="+jsonResult);
returnjsonResult;
}
/**
* 生成数字签名
*@param guid
*@return
*@throws UnsupportedEncodingException
*/
public static String getSign(String qStr)throws UnsupportedEncodingException{
System.out.println(qStr);
String qStr1 =URLEncoder.encode(qStr,"UTF-8").toUpperCase();对qStr字符串进行编码,编码后字母转成大写
Stringsign=SUtil.MD5ToUpCase32(qStr1+ "yiluobaba#987");
return "sign="+sign;
}
6.spring知识
6.1 spring mvc理解
1、 用户发送请求至前端控制器DispatcherServlet。
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet调用HandlerAdapter处理器适配器。
5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、 Controller执行完成返回ModelAndView。
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、 ViewReslover解析后返回具体View。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet响应用户。
配置代码:
Spring filter
字符编码过滤
<filter-name>SpringEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SpringEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 自动扫描且只扫描@Controller-->
<context:component-scanbase-package="com.app.controller"use-default-filters="false">
<context:include-filtertype="annotation"expression="org.springframework.stereotype.Controller"/>
<context:include-filtertype="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
<!--容器默认的DefaultServletHandler处理所有静态内容与无RequestMapping处理的URL-->
<mvc:default-servlet-handler/>
<!--定义无需Controller的url<->view直接映射 -->
<mvc:view-controllerpath="/" view-name="redirect:/login"/>
6.2 spring 两大特征aop 和ioc
Aop:
AOP(Aspect OrientProgramming),我们一般称为面向方面(切面)编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,
比如事务管理、日志、缓存等等。AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,
静态代理的代表为AspectJ;而动态代理则以SpringAOP为代表。本文会分别对AspectJ和Spring AOP的实现进行分析和介绍。
引入第三方jar包需要中间添加一段代码使用aop模式
事物aop如下:
<!-- AOP 事务配置 -->
<aop:config>
<aop:pointcutid="serviceOpenration"
expression="execution(*com.app.service.* .* .* (..));" />
<aop:advisoradvice-ref="txAdvice" pointcut-ref="serviceOpenration"/>
</aop:config>
以下配置日志切入,调用接口执行时长
<aop:config>
<!-- 用 AspectJ 的语法定义Pointcut,这里拦截 service 包中的所有方法 -->
<aop:advisorid="methodTimeLog" advice-ref="methodTimeAdvice"
pointcut="execution(*com.kaolafm.mams.api.service.impl..*.*(..))" />
</aop:config>
<!--db配置 start-->
<bean id="dataSource"class="com.kaolafm.dynamicDataSource.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="slave"value-ref="slaveDataSource" />
<entry key="master"value-ref="masterDataSource"/>
<entrykey="oldResources" value-ref="oldDataSource"/>
</map>
</property>
<property name="defaultTargetDataSource"ref="masterDataSource" />
</bean>
<!-- db配置end-->
<!-- 配置数据库注解aop -->
<bean id="manyDataSourceAspect"class="com.kaolafm.dynamicDataSource.DataSourceAspect" />
<aop:config>
<aop:aspect id="c"ref="manyDataSourceAspect">
<aop:pointcut id="tx"expression="execution(* com.kaolafm.mams.api..*.*(..))and execution(*com.kaolafm.mams.api.*.*(..))"/>
<aop:beforepointcut-ref="tx" method="before"/>
</aop:aspect>
</aop:config>
<!-- 配置数据库注解aop -->
Ioc:依赖注入
解决最初到处new的问题很难维护(改起来就特别麻烦)。Spring容器中bean没有特殊配置默认就是单实例。都是无状态的实例(如dao),如果涉及到状态就涉及到并发。
Spring 两种注入的方式(Setter和构造)实例
家庭医生中的操作日志setter方式
<!-- 日志start -->
<beanid="operateLogDAOImpl" parent="DaoTemplate"
class="com.app.dao.logimpl.OperateLogDAOImpl"/>
<beanid="operateLogSERImpl" parent="serviceTemplate"
class="com.app.service.logimpl.OperateLogSERImpl">
<propertyname="operateLogDao" ref="operateLogDAOImpl" />
</bean>
<!-- 日志end -->
注:上面这两种写法都可以,spring会将name值的每个单词首字母转换成大写,然后再在前面拼接上”set”构成一个方法名,然后去对应的类中查找该方法,通过反射调用,实现注入。
Set注入
@Resource
IOperateLogDAO operateLogDao;
public voidsetOperateLogDao(IOperateLogDAO itemDao) {
operateLogDao = itemDao;
}
@Qualifier的使用:
@Service("service")
public class EmployeeServiceImpl implementsEmployeeService {
public EmployeeDto getEmployeeById(Long id) {
return new EmployeeDto();
}
}
@Service("service1")
public class EmployeeServiceImpl1implements EmployeeService {
public EmployeeDto getEmployeeById(Long id) {
return new EmployeeDto();
}
}
@Controller
@RequestMapping("/emplayee.do")
public class EmployeeInfoControl {
@Autowired
@Qualifier("service")
EmployeeService employeeService;
@RequestMapping(params = "method=showEmplayeeInfo")
public void showEmplayeeInfo(HttpServletRequest request,HttpServletResponse response, EmployeeDto dto) {
#略
}
}
一、基础概念
singleton: 在Spring的IoC容器中只存在一个对象实例,所有该对象的引用都共享这个实例。Spring 容器只会创建该bean定义的唯一实例,这个实例会被保存到缓存中,并且对该bean的所有后续请求和引用都将返回该缓存中的对象实例,一般情况下,无状态的bean使用该scope。
prototype:每次对该bean的请求都会创建一个新的实例,一般情况下,有状态的bean使用该scope。
request:每次http请求将会有各自的bean实例,类似于prototype。
session:在一个http session中,一个bean定义对应一个bean实例。
global session:在一个全局的httpsession中,一个bean定义对应一个bean实例。典型情况下,仅在使用portlet context的时候有效。
其次说明spring的默认scope(bean作用域)是singleton
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
publicMyPrototypeBean prototypeBean(){
returnnewMyPrototypeBean();
}
public class MyController implements ApplicationContextAware {
public MyService getMyService() {
return applicationContext.getBean( MyService.class );
}
protected ApplicationContext applicationContext;
@Override
public void setApplicationContext( ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
6.3 spring 事物
传播方式:
1、PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
2、PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘
3、PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
4、PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
5、PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
6、PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
7、PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
事物模式:spring支持编程式事务管理和声明式事务管理两种方式。
编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。
代码:
<!-- 使用注解方式定义事务 -->
<aop:aspectj-autoproxyproxy-target-class="true" />
<tx:annotation-drivenproxy-target-class="true"
transaction-manager="transactionManager"/>
事务隔离级别:
TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。
比如PostgreSQL实际上并没有此级别。
TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。
通常情况下也不会用到该级别。
Spring事务主要是aop和线程实现
6.4 springboot与spring 区别
1.Spring Boot可以建立独立的Spring应用程序;
2.内嵌了如Tomcat,Jetty和Undertow这样的容器,也就是说可以直接跑起来,用不着再做部署工作了。
3.无需再像Spring那样搞一堆繁琐的xml文件的配置;
4.可以自动配置Spring;
5.提供了一些现有的功能,如量度工具,表单数据验证以及一些外部配置这样的一些第三方功能;
6.提供的POM可以简化Maven的配置;
说得更简便一些:Spring 最初利用“工厂模式”(DI)和“代理模式”(AOP)解耦应用组件。大家觉得挺好用,于是按照这种模式搞了一个 MVC框架(一些用Spring 解耦的组件),用开发 web 应用( SpringMVC )。然后有发现每次开发都写很多样板代码,为了简化工作流程,于是开发出了一些“懒人整合包”(starter),这套就是 Spring Boot
Pom引入:
Springboot启动方式:
@SpringBootApplication
控制层注入
@RestController
7. 数据库
7.1. MySQL索引方式
BTree索引
B-Tree是最常见的索引类型,所有值(被索引的列)都是排过序的,每个叶节点到跟节点距离相等。所以B-Tree适合用来查找某一范围内的数据,而且可以直接支持数据排序(ORDER BY)
B-Tree在MyISAM里的形式和Innodb稍有不同:
MyISAM表数据文件和索引文件是分离的,索引文件仅保存数据记录的磁盘地址
InnoDB表数据文件本身就是主索引,叶节点data域保存了完整的数据记录
Hash索引
1.仅支持"=","IN"和"<=>"精确查询,不能使用范围查询:
由于Hash索引比较的是进行Hash运算之后的Hash值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的Hash算法处理之后的Hash
2.不支持排序:
由于Hash索引中存放的是经过Hash计算之后的Hash值,而且Hash值的大小关系并不一定和Hash运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算
3.在任何时候都不能避免表扫描:
由于Hash索引比较的是进行Hash运算之后的Hash值,所以即使取满足某个Hash键值的数据的记录条数,也无法从Hash索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果
4.检索效率高,索引的检索可以一次定位,不像B-Tree索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以Hash索引的查询效率要远高于B-Tree索引
5.只有Memory引擎支持显式的Hash索引,但是它的Hash是nonunique的,冲突太多时也会影响查找性能。Memory引擎默认的索引类型即是Hash索引,虽然它也支持B-Tree索引
full-text全文索引
R-Tree索引
7.2索引类型
Normal: 是最基本的索引,它没有任何限制
Unique: 与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一
Full Text: 全文索引,用来对大表的文本域(char,varchar,text)进行索引。语法和普通索引一样。
7.3 mysql数据量过大
第一优化你的sql和索引;
第二加缓存,memcached,redis;
第三以上都做了后,还是慢,就做主从复制或主主复制,读写分离,可以在应用层做,效率高,也可以用三方工具,第三方工具推荐360的atlas,其它的要么效率不高,要么没人维护;
第四如果以上都做了还是慢,不要想着去做切分,mysql自带分区表,先试试这个,对你的应用是透明的,无需更改代码,但是sql语句是需要针对分区表做优化的,
sql条件中要带上分区条件的列,从而使查询定位到少量的分区上,否则就会扫描全部分区,另外分区表还有一些坑,在这里就不多说了;
第五如果以上都做了,那就先做垂直拆分,其实就是根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;
第六才是水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key,为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,
sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表;
7.4哪些情况需要创建索引
①主键自动建立唯一索引
②频繁作为查询条件的字段应该创建索引
③查询中与其他表关联的字段,外键关系建立索引
④频繁更新的字段不适合建立索引,因为每次更新不单单是更新了记录还会更新索引
⑤WHERE条件里用不到的字段不创建索引
⑥单键/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
⑦查询中排序的字段,排序的字段若通过索引去访问将大大提高排序速度
⑧查询中统计或者分组字段
几种索引无效:
索引字段上使用(!= 或者 < >)判断时,会导致索引失效而转向全表扫描
索引字段上使用 is null / is not null 判断时,会导致索引失效而转向全表扫描
索引字段使用like以通配符开头(‘%字符串’)时,会导致索引失效而转向全表扫描
索引字段是字符串,但查询时不加单引号,会导致索引失效而转向全表扫描
索引字段使用 or 时,会导致索引失效而转向全表扫描
8.缓存
8.1 redis配置使用
安装:
安装redis服务,
tarxzf redis-2.8.17.tar.gz
cd redis-2.8.17
cd src
make install(安装)
配置:
启动服务:./redis-server
使用:
Redis是单线程的服务nosql服务器,c写的读写特别快,读每秒接近10万次,写每秒接近10万次。
Redis所有的数据都在内从里,也有持久化的配置,有不同的策略(周期性:多长时间存储一次。数据变化存储:达到多少条时进行存储)
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,redis支持的数据结构包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合 如:竞拍的数据从高到底进行排序)和hash(哈希类型-存对象的时候用,hash到里面读取速度快不会改变数据结构)。
这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,
数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步(slaveof master ip:co从机子上执行这个命令从就会自动同步数据,其中主上要配置从的密码)。
主备切换:使用keepalived工具进行主从切换
实际操作:
//注入redis对象
@Resource
private TwemRedisOperationstwemRedisOperations;
//redis设置专辑对象
twemRedisOperations.setex(RedisKeyConstant.KEY_ALBUM+albumId,RedisKeyConstant.ALIVETIME_ONEWEEK, tbAlbumDTO);
// 清掉专辑基本信息缓存
twemRedisOperations.del(RedisKeyConstant.KEY_ALBUM+ albumDTO.getId());
//获取专辑对象
TbAlbumDTO tbAlbumDTO =twemRedisOperations.get(RedisKeyConstant.KEY_ALBUM + albumId,TbAlbumDTO.class);
配置:
9. JVM
9.1 jvm定义
JVM是基于栈的体系结构来执行class字节码的。
Java虚拟机(Jvm)是可运行Java代码的假想计算机
Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。
1.Java源文件—->编译器—->字节码文件(JAVA编译器是把java的源码编译成class文件(字节码),)
2.字节码文件—->Jvm—->机器码(字节码ClassLoader到内存里生存机器码)
9.2 JVM内存调优:
静态内存:加载class文件放置内存(几乎不怎么变动),策略:很久不用就从内存中移出去,需要时再加载
Jvm的数据结构:如果明确知道大小new数据结构时就把大小写上,减少内存的浪费。(如果15000的数据,假设jvm分配到14000,存储一万14000时再增倍内存。)
减少垃圾回收开销:如果同样的创建很多次对象的时候,最好使用对象池(对象放置循环外面)。
如:for循环里面循环完一条存入数据库一条这种使用对象池,减小gc开销,使用循环外的list对象存储对象之后存入数据库这时候不能用对象池,因为list本身就是引用。
首先需要注意的是在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。
Pemanet Generation空间不足
增大Perm Gen空间,避免太多静态对象
System.gc()被显示调用
垃圾回收不要手动触发,尽量依靠JVM自身的机制
9.3 GC垃圾回收
最早的机制:一块存储空间有的是有用的对象,有的是需要释放或者说过期的对象(new对象是有生命周期的),当new对象多内存不够用时需要释放对象,这个线程就来整理这块存储空间,把有用的对象放前面,无用的整理到最后,这样new对象时就有用的对象后面开始new。这种模式就是全权由一个线程去负责
新机制:支持多线程(并行并发),每个线程只负责自己的片区(整理新生代、中生代、老生代),自己片区整理好后由统一的线程来整合。
并行:多个线程同事运行在多核上
并发:多个线程跑在单核机制上
一、垃圾回收机制的意义
Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理。
由于有个垃圾回收机制,Java中的对象不再有“作用域”的概念,只有对象的引用才有“作用域”。垃圾回收可以有效的防止内存泄露,有效的使用空闲的内存。
二、垃圾回收机制中的算法
Java语言规范没有明确地说明JVM使用哪种垃圾回收算法,但是任何一种垃圾回收算法一般要做2件基本的事情:
(1)发现无用信息对象;(2)回收被无用对象占用的内存空间,使该空间可被程序再次使用。
1.引用计数法(ReferenceCounting Collector)
1.1算法分析
引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),
但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。
任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减
1.2优缺点
优点:
引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。
缺点:
无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.
1.3引用计数算法无法解决循环引用问题,例如:
public class Main {
public static void main(String[] args) {
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
object1.object = object2;
object2.object = object1;
object1 = null;
object2 = null;
}
}
最后面两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,
导致它们的引用计数器都不为0,那么垃圾收集器就永远不会回收它们。
9.4 jvm设置过大或过小会怎样
考虑GC效率的话,开小了会使GC发生的很频繁,开大了虽然会减少GC的次数,但是会增加每次GC的时间,当新生代使用串行回收时,GC时间过长会造成程序所有线程暂停时间过长。sun公司给的例子是(4G内存,32个线程并发能力)-Xmx3800m -Xms3800m -Xmn2G -Xss128k
当然不是越大越好,需要根据具体情况权衡。内存越大,JVM 进行 Full GC 所需的时间越久,由于 Full GC 时 stop whole world 的,
如果是用于响应HTTP 请求的服务器,这个时候就表现为停止响应,对于需要低延迟的应用来说,这是不可接受的。对于需要高吞吐量的应用来说,
可以不在乎这种停顿,比如一些后台的应用之类的,那么内存可以适当调大一些。
如果设置内存过小,内存不足,不足余运行就会跑异常。
9.5 JVM内存不要超过32G
事实上jvm在内存小于32G的时候会采用一个内存对象指针压缩技术。
在java中,所有的对象都分配在堆上,然后有一个指针引用它。指向这些对象的指针大小通常是CPU的字长的大小,不是32bit就是64bit,这取决于你的处理器,指针指向了你的值的精确位置。
对于32位系统,你的内存最大可使用4G。对于64系统可以使用更大的内存。但是64位的指针意味着更大的浪费,因为你的指针本身大了。浪费内存不算,更糟糕的是,更大的指针在主内存和缓存器(例如LLC, L1等)之间移动数据的时候,会占用更多的带宽。
Java 使用一个叫内存指针压缩的技术来解决这个问题。它的指针不再表示对象在内存中的精确位置,而是表示偏移量。这意味着32位的指针可以引用40亿个对象,而不是40亿个字节。最终,也就是说堆内存长到32G的物理内存,也可以用32bit的指针表示。
一旦你越过那个神奇的30-32G的边界,指针就会切回普通对象的指针,每个对象的指针都变长了,就会使用更多的CPU内存带宽,也就是说你实际上失去了更多的内存。事实上当内存到达40-50GB的时候,有效内存才相当于使用内存对象指针压缩技术时候的32G内存。
这段描述的意思就是说:即便你有足够的内存,也尽量不要超过32G,因为它浪费了内存,降低了CPU的性能,还要让GC应对大内存。
10堆栈
要点:堆,队列优先,先进先出(FIFO—first in first out) [1] 。栈,先进后出(FILO—First-In/Last-Out)。
从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的.而这种不同又主要是由于堆和栈的特点决定的:
虽然分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,不是在运行时
堆是应用程序在运行的时候请求操作系统分配给自己内存,由于从操作系统管理的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低.但是堆的优点在于,编译器不必知道要从堆里分配多少存储空间
10.1 JVM中的堆和栈:
JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。
堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。
Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,
在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。
10.2 java堆栈实际列子
heap 堆 放 对象 也就是new 出来的东西
stack 栈 放局部变量,调用(运行)线程、方法等
static segment 静态区 用来放 静态变量 和字符串常量
data segement 代码区 用来放代码的
如果一个字符串是 String s = "abc";它放在栈里
如果一个字符串用创建对象的方式 String s = new String("abc");
那它是放在了堆里 而如果单纯的 一个 "abc" 这个输入字符串常量 是放在staticsegement里
ArrayList是对象 既然是对象那肯定全都分配在堆上了 只有引用会分配的栈上 只要你使用new 关键字实例化了一个ArrayList对象
1. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。)
2. 堆:存放所有new出来的对象。
3. 常量池:存放字符串常量和基本类型常量(publicstatic final)。
11. linux相关知识
Linux命令:
ll 列出当前目录下的所有文件,包括每个文件的详细信息
vi 打开当前文件
i {insert写输入}
esc 退出insert
:q! 不保存退出vi模式
:wq! write 保存并退出vi模式
history 20 (显示最后20条命令)
mkdir+文件夹名创建某个文件夹的命令
rm 删除文件
rm -rf *删除目录下所有文件
rmdir 删除目录(rmdir [选项] dir-name)
#mv file1 dir 注:把一个文件移动到一个事实存在的目录
cp -rv A B:拷贝A文件夹到B目录
mv test.txt test1.txt 由原来的test.txt修改成test1.txt:重新命名
rz 从本机向服务器端传送文件的命令
ps -ef|grep tomcat 查看tomcat的进程数量
kill 16688
解压war包到某一目录:unzip -o CMS-WEB.war -d/usr/local/tomcat/webapps/CMS-WEB
.zip
解压:unzip FileName.zip
压缩:zip FileName.zip DirNam
.rar
解压:rar -x FileName.zip
压缩:rar -a FileName.rar DirName
.tar.gz或tgz
解压:tar -zxvf FileName.tar.gz
压缩:tar -zcvf FileName.tar.gz DirName
ps -aux | grep'zookeeper' 查看zookeeper进程
tac catalina.out |grep "09:39"|more根据时间点查询
1、tail -f filename
说明:监视filename文件的尾部内容(默认10行,相当于增加参数 -n 10),刷新显示在屏幕上。退出,按下CTRL+C。
2、tail -n 20 filename
说明:显示filename最后20行。
12线程
12.1什么是线程?
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速。比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒。
Java在语言层面对多线程提供了卓越的支持,它也是一个很好的卖点。
12.2 用Runnable还是Thread?
这个问题是上题的后续,大家都知道我们可以通过继承Thread类或者调用Runnable接口来实现线程,问题是,那个方法更好呢?什么情况下使用它?这个问题很容易回答,
如果你知道Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口好了。
12.3 Thread 类中的start()和 run()方法有什么区别?
这个问题经常被问到,但还是能从此区分出面试者对Java线程模型的理解程度。start()方法被用来启动新创建的线程(线程看起来是java的线程,实际是操作系统的线程),而且start()(启动之后时系统的线程)内部调用了run()方法,这和直接调用run()方法的效果不一样。
当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
12.4 Java多线程中调用wait() 和 sleep()方法有什么不同?
Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。
12.4 现在有T1、T2、T3三个线程,怎样保证T2在T1执行完后执行,T3在T2执行完后执行?使用Join
public static void main(String[] args)
{
Thread t1 = new MyThread("线程1");
Thread t2 = new MyThread("线程2");
Thread t3 = new MyThread("线程3");
try
{
//t1先启动
t1.start();
t1.join();
//t2
t2.start();
t2.join();
//t3
t3.start();
t3.join();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
public classTestJoin2
{
public static void main(String[] args)
{
final Thread t1 = new Thread(newRunnable() {
@Override
public void run() {
System.out.println("t1");
}
});
final Thread t2 = new Thread(newRunnable() {
@Override
public void run() {
try {
//引用t1线程,等待t1线程执行完
t1.join();
} catch (InterruptedExceptione) {
e.printStackTrace();
}
System.out.println("t2");
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
//引用t2线程,等待t2线程执行完
t2.join();
} catch (InterruptedExceptione) {
e.printStackTrace();
}
System.out.println("t3");
}
});
t3.start();
t2.start();
t1.start();
}
}
两个列子的区别:Thread依赖的是当前线程,Runnable依赖的是外层线程
12.5 synchronized(锁)
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
修饰代码块
一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。我们看下面一个例子:
public classSyncThread implements Runnable{
private static int count;
@Override
public void run(){
synchronized(this){
for(int i=0;i<5;i++){
try{
System.out.println(Thread.currentThread().getName()+":"+(count++));
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
public static void main(String[] args){
SyncThread testTrhead=new SyncThread();
Thread thread1=newThread(testTrhead,"testThread1");
Thread thread2=newThread(testTrhead,"testThread2");
thread1.start();
thread2.start();
}
}
运行结果:
testThread1:0
testThread1:1
testThread1:2
testThread1:3
testThread1:4
testThread2:5
testThread2:6
testThread2:7
testThread2:8
testThread2:9
当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。
我们再把SyncThread的调用稍微改一下:
Thread thread1 = new Thread(newSyncThread(), "SyncThread1");
Thread thread2 = new Thread(newSyncThread(), "SyncThread2");
thread1.start();
thread2.start();
运行结果:
testThread1:0
testThread2:1
testThread1:2
testThread2:3
testThread2:5
testThread1:4
testThread1:6
testThread2:7
testThread2:8
testThread1:8
不是说一个线程执行synchronized代码块时其它的线程受阻塞吗?为什么上面的例子中thread1和thread2同时在执行。
这是因为synchronized只锁定对象,每个对象只有一个锁(lock)与之相关联,而上面的代码等同于下面这段代码:
SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1,"SyncThread1");
Thread thread2 = new Thread(syncThread2,"SyncThread2");
thread1.start();
thread2.start();
这时创建了两个SyncThread的对象syncThread1和syncThread2,线程thread1执行的是syncThread1对象中的synchronized代码(run),而线程thread2执行的是syncThread2对象中的synchronized代码(run);我们知道synchronized锁定的是对象,这时会有两把锁分别锁定syncThread1对象和syncThread2对象,而这两把锁是互不干扰的,不形成互斥,所以两个线程可以同时执行
指定要给某个对象加锁
/**
* 银行账户类
*/
class Account {
String name;
float amount;
public Account(String name, float amount) {
this.name = name;
this.amount = amount;
}
//存钱
public void deposit(float amt) {
amount += amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取钱
public void withdraw(float amt) {
amount -= amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public float getBalance() {
return amount;
}
}
/**
* 账户操作类
*/
class AccountOperator implements Runnable{
private Account account;
public AccountOperator(Account account) {
this.account = account;
}
public void run() {
synchronized (account) {
account.deposit(500);
account.withdraw(500);
System.out.println(Thread.currentThread().getName() + ":" +account.getBalance());
}
}
}
调用代码:
Account account = new Account("zhangsan", 10000.0f);
AccountOperator accountOperator = newAccountOperator(account);
final int THREAD_NUM = 5;
Thread threads[] = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i ++) {
threads[i] = new Thread(accountOperator, "Thread" + i);
threads[i].start();
}
结果如下:
Thread3:10000.0
Thread2:10000.0
Thread1:10000.0
Thread4:10000.0
Thread0:10000.0
在AccountOperator 类中的run方法里,我们用synchronized 给account对象加了锁。这时,当一个线程访问account对象时,
其他试图访问account对象的线程将会阻塞,直到该线程访问account对象结束。也就是说谁拿到那个锁谁就可以运行它所控制的那段代码。
当有一个明确的对象作为锁时,就可以用类似下面这样的方式写程序。
修饰一个方法
Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,publicsynchronized void method(){//todo};
synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。
public synchronized void run() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" +(count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Synchronized作用于整个方法的写法。
写法一:
public synchronized void method()
{
//todo
}
写法二:
public void method()
{
synchronized(this) {
// todo
}
}
写法一修饰的是一个方法,写法二修饰的是一个代码块,但写法一与写法二是等价的,都是锁定了整个方法时的内容。
在用synchronized修饰方法时要注意以下几点:
1. synchronized关键字不能继承。
虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。
修饰一个静态的方法
/**
* 同步线程
*/
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public synchronized static void method() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" +(count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void run() {
method();
}
}
调用代码:
SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1,"SyncThread1");
Thread thread2 = new Thread(syncThread2,"SyncThread2");
thread1.start();
thread2.start();
结果如下:
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
syncThread1和syncThread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。这与Demo1是不同的。
修饰一个类
修饰一个类
Synchronized还可作用于一个类,用法如下:
class ClassName {
publicvoid method() {
synchronized(ClassName.class) {
// todo
}
}
}
我们把Demo5再作一些修改。
【Demo6】:修饰一个类
/**
* 同步线程
*/
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public static void method() {
synchronized(SyncThread.class) {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" +(count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public synchronized void run() {
method();
}
}
其效果和静态的方法是一样的,synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁。
总结:
A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,
则它取得的锁是对类,该类所有的对象同一把锁。
B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
13.闭包
闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
一、为什么要使用闭包?
那么闭包既然是为了共享某个变量,那么为什么不搞个全局变量?如果是个小项目,就那么几个全局变量,还真不需要什么闭包。
但是如果是大的项目,很多需要共享的变量都弄成全局的话,那么起名字都非常头疼,代码多了都不知道这个变量干嘛的了,
甚至出现多个函数修改一个变量导致混乱。因此我们需要模块化,变量私有化,这个时候可以用到闭包,让私有变量驻留在内存中,
无关的模块无法访问。太多的闭包也会带来问题,因为太多的变量没法回收会占用更多的内存,因此需要我们手动释放闭包产生的变量,
例如上面我们使用var c = a()引用了变量i,不需要的时候要让c = null或者其他值,释放函数a的引用让函数a里面的所有变量回收。
代码运行:
//定义函数
function a(){
varn = 0;
function inc(){
n++;
console.log(n);
}
return inc;
}
var c = a();
c(); //控制台输出1
c(); //控制台输出2
闭包:外部无法读取方法里面的局部变量,但外部通过方法可以读取到但是无法直接改变它。
14.注解、反射
反射就是把Java类中的各个成分映射成相应的Java类。如:spring的bean(spring创建完容器中放置bean对象)
线程变量:事物中除了aop外传递方式都是线程变量(不管跨了多少变量从头到尾都能调用到它)
反射,一种计算机处理方式。是程序可以访问、检测和修改它本身状态或行为的一种能力。
java反射使得我们可以在程序运行时动态加载一个类,动态获取类的基本信息和定义的方法,构造函数,域等。除了检阅类信息外,
还可以动态创建类的实例,执行类实例的方法,获取类实例的域值。反射使java这种静态语言有了动态的特性。
定义一个UserAnnotation注解类
@Target(value = { ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAnnotation {
publicint id() default 0;
publicString name() default "";
publicint age() default 18;
publicString gender() default "M";
}
其中@target个@Retention本身就是注解
【@target】这个注解来指定给哪一类java成员注解,指定注解目标该是什么样的东西
注解@Target的源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE) //这不是在作弊,这确实是自己注解自己,所以说注解也可以被字节给注解
public @interface Target {
ElementType[] value(); //值可以使数组 value={...}
}
public enum ElementType {
TYPE, //给类(型)注解
FIELD, //给字段注解,不要忘了,字段可以是对象
METHOD, //给方法注解
PARAMETER, //给参数注解
CONSTRUCTOR, //给构造方法注解
LOCAL_VARIABLE, //给局部变量注解
ANNOTATION_TYPE,//给注解注解(这貌似把自己不当类来看)
PACKAGE, //给包注解
TYPE_PARAMETER, //不知道,等知道了我再写在这里
TYPE_USE //这个也不知道
}
public class TestMain
{
@UserAnnotation(age=20,gender="F",id=2014,name="zhangsan")//注解的使用
private Object obj;
public static void main(String[] args) throws Exception
{
Filed objField = TestMain.class.getField("obj");
UserAnnotation ua = objField.getAnnotation(UserAnnotation.class);//得到注解,起到了标记的作用
System.out.println(ua.age()+","+ua.gender()+","+ua.id()+","+ua.name());
//***进一步操作的话,假设Object要指向一个User类,那么可以讲注解的值给他
TestMain tm = new TestMain();
objFiled.set(tm,new User(ua.age(),ua.gender(),ua.id(),ua.name())); //不错吧,将自己的信息送给obj,起到了附加信息的作用
//-----------请自由遐想吧~~,下面来说说注解怎么能获得注解自己的注解-------------
Target t = ua.annotationType().getAnnotation(Target.class)
ElementType[] values = t.value();
//~~~~~~~~~~~~~~完了,再一次自由遐想吧~~~~~~~~~~~~~~
Sysout.out.println("注意:是遐想,不是瞎想!!");
}
}
15.队列
Queue: 基本上,一个队列就是一个先入先出(FIFO)的数据结构
Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList实现了Deque接口。
15.1 有界队列
15.2 无界队列
当你线程满了后。别的加进池的线程在队列里。这个队列长度无限。如果你只跑一个线程,那么别的线程你execute进去的时候。会自动排序的。不用你处理。
15.2什么是阻塞队列
阻塞队列是一个在队列基础上又支持了两个附加操作的队列。
2个附加操作:
支持阻塞的插入方法:队列满时,队列会阻塞插入元素的线程,直到队列不满。
支持阻塞的移除方法:队列空时,获取元素的线程会等待队列变为非空。
阻塞队列的应用场景
阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。简而言之,阻塞队列是生产者用来存放元素、消费者获取元素的容器。
JAVA里的阻塞队列
JAVA 7 提供了7个阻塞队列,如下
①:ArrayBlockingQueue 数组结构组成的有界阻塞队列。
此队列按照先进先出(FIFO)的原则对元素进行排序,但是默认情况下不保证线程公平的访问队列,即如果队列满了,那么被阻塞在外面的线程对队列访问的顺序是不能保证线程公平(即先阻塞,先插入)的。
②:LinkedBlockingQueue一个由链表结构组成的有界阻塞队列
此队列按照先出先进的原则对元素进行排序
③:PriorityBlockingQueue支持优先级的无界阻塞队列
④:DelayQueue支持延时获取元素的无界阻塞队列,即可以指定多久才能从队列中获取当前元素
⑤:SynchronousQueue不存储元素的阻塞队列,每一个put必须等待一个take操作,否则不能继续添加元素。并且他支持公平访问队列。
⑥:LinkedTransferQueue由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列,多了tryTransfer和transfer方法
16 java8特性
16.1Lambda表达式
Lambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,只能使用匿名内部类代替Lambda表达式。
jdk中lambda表达式的典型用法
Arrays.asList( "a","b", "d" ).forEach( e -> System.out.println( e ) );
16.2 Stream 接口
//定义list
List<Integer>list=Arrays.asList(1,2,3,4);
list.stream().forEach(e->System.out.println(e));
List<String> keyList =entityList.stream().map(TblSleep::getSleepId).collect(Collectors.toList());
16.3 Filter接口
List<Integer>list=Arrays.asList(1,2,3,4);
List listMap=list.stream().filter(e->e.equals(1)).collect(Collectors.toList());
TblReport tblSleep = sleepList.stream()
.filter(s ->DateUtil.getDay(s.getReportDate()) == j)
.filter(s ->s.getReportDate().getMonth() == setDate.getMonth())
.findFirst()
.orElse(new TblReport());
16.4 groupBy
List<MstMusicHistoryBean> histories =mstMusicHistoryLogic.getByUserId(user.getAppUserId());
Map<String,List<MstMusicHistoryBean>> groupBy =histories.stream().collect(Collectors.groupingBy(MstMusicHistoryBean::getMusicId));
16.5 skip、limit
limit: 对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素
skip: 返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream
List<Integer>list=Arrays.asList(1,2,3,4);
Listsl=list.stream().skip(0).limit(2).collect(Collectors.toList());
输出结果: 1 2
17 CAP理论
CAP定律(Consistency,Availability,PartitionTolerance theorem),说的是在一个分布式计算机系统中,一致性,
可用性和分区容错性这三种保证无法同时得到满足,最多满足两个。该定律作为猜想在2000年提出,2002年被证实。其中,一致性说的是分布式系统中,所有节点在同一时刻看到同一个值。
可用性说的是每个请求都会收到一个应答,无论该应答是成功还是失败。分区容错性指的是无论任何消息丢失,系统都可用。
18. mybatis
MyBatis 是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录
19 tcp/ip协议
OSI模型中Tcp 建立在传输层,http建立在应用层
19.1 TCP与UDP的区别
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保 证可靠交付
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
TCP和UDP是OSI模型中的运输层中的协议。TCP提供可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输
20 双链表
双链表的主要优点是对于任意给的结点,都可以很轻易的获取其前驱结点或者后继结点,而主要缺点是每个结点需要添加额外的next域,
因此需要更多的空间开销,同时结点的插入与删除操作也将更加耗时,因为需要更多的指针指向操作。双链表的结构图如下:
21.冒泡排序
设数组的长度为N:
(1)比较前后相邻的二个数据,如果前面数据大于后面的数据,就将这二个数据交换。
(2)这样对数组的第0个数据到N-1个数据进行一次遍历后,最大的一个数据就“沉”到数组第N-1个位置。
(3)N=N-1,如果N不为0就重复前面二步,否则排序完成。
java冒泡的三种实现方式
public static void bubbleSort1(int [] a,int n){
for(int i=0;i<n;i++){
for(int j=1;j<n-i;j++){
if(a[j-1]>a[j]){
int temp=a[j-1];
a[j-1]=a[j];
a[j]=temp;
}
}
}
}
public staticvoid main(String[] args){
int[] arr = {1,1,2,0,9,3,12,7,8,3,4,65,22};
bubbleSort1(arr,arr.length);
for(int i:arr){
System.out.println(i);
}
}
运行结果为:
0,1,1,2,3,3,4,7,8,9,12,22,65,
下面开始考虑优化,如果对于一个本身有序的序列,或则序列后面一大部分都是有序的序列,上面的算法就会浪费很多的时间开销,这里设置一个标志flag,如果这一趟发生了交换,则为true,否则为false。明显如果有一趟没有发生交换,说明排序已经完成。
public staticvoid bubbleSort2(int []a,int n){
int i,j=n;
boolean flag=true;
while (flag){
flag=false;
for(i=1;i<j;i++){
if(a[i-1]>a[i]){
int temp=a[i-1];
a[i-1]=a[i];
a[i]=temp;
flag=true;
}
}
j--;
}
}
运行结果为:
0,1,1,2,3,3,4,7,8,9,12,22,65,
再进一步做优化。比如,现在有一个包含1000个数的数组,仅前面100个无序,后面900个都已排好序且都大于前面100个数字,那么在第一趟遍历后,最后发生交换的位置必定小于100,且这个位置之后的数据必定已经有序了,也就是这个位置以后的数据不需要再排序了,于是记录下这位置,第二次只要从数组头部遍历到这个位置就可以了。如果是对于上面的冒泡排序算法2来说,虽然也只排序100次,但是前面的100次排序每次都要对后面的900个数据进行比较,而对于现在的排序算法3,只需要有一次比较后面的900个数据,之后就会设置尾边界,保证后面的900个数据不再被排序。
public staticvoid bubbleSort3(int []a,int n){
int k;
int flag=n;//flag来记录最后交换的位置,也就是排序的尾边界
while(flag>0){//排序未结束标志
k=flag;//k 来记录遍历的尾边界
flag=0;
for(int i=1;i<k;i++){//前面的数字大于后面的数字就交换
if(a[i-1]>a[i]){
//交换a[j-1]和a[j]
int temp=a[i-1];
a[i-1]=a[i];
a[i]=temp;
//表示交换过数据;
flag=i;//记录最新的尾边界.
}
}
}
}
运行结果为:
0,1,1,2,3,3,4,7,8,9,12,22,65,
22 hash
22.1 Hash的应用
1、Hash主要用于信息安全领域中加密算法,它把一些不同长度的信息转化成杂乱的128位的编码,这些编码值叫做Hash值. 也可以说,Hash就是找到一种数据内容和数据存放地址之间的映射关系。
2、查找:哈希表,又称为散列,是一种更加快捷的查找技术。我们之前的查找,都是这样一种思路:集合中拿出来一个元素,看看是否与我们要找的相等,如果不等,缩小范围,继续查找。而哈希表是完全另外一种思路:当我知道key值以后,我就可以直接计算出这个元素在集合中的位置,根本不需要一次又一次的查找!
举一个例子,假如我的数组A中,第i个元素里面装的key就是i,那么数字3肯定是在第3个位置,数字10肯定是在第10个位置。哈希表就是利用利用这种基本的思想,建立一个从key到位置的函数,然后进行直接计算查找。
22.2 优缺点
优点:不论哈希表中有多少数据,查找、插入、删除(有时包括删除)只需要接近常量的时间即0(1)的时间级。实际上,这只需要几条机器指令。
哈希表运算得非常快,在计算机程序中,如果需要在一秒种内查找上千条记录通常使用哈希表(例如拼写检查器)哈希表的速度明显比树快,树的操作通常需要O(N)的时间级。哈希表不仅速度快,编程实现也相对容易。
如果不需要有序遍历数据,并且可以提前预测数据量的大小。那么哈希表在速度和易用性方面是无与伦比的。
缺点:它是基于数组的,数组创建后难于扩展,某些哈希表被基本填满时,性能下降得非常严重,所以程序员必须要清楚表中将要存储多少数据(或者准备好定期地把数据转移到更大的哈希表中,这是个费时的过程)。
23.tomcat 优化配置
Tomcat默认模式是bio模式,并发两最多200
1.优化线程
<Connectorport="8080"protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443"acceptCount="500"maxThreads="400" />
其中:
• maxThreads:tomcat可用于请求处理的最大线程数,默认是200
• minSpareThreads:tomcat初始线程数,即最小空闲线程数
• maxSpareThreads:tomcat最大空闲线程数,超过的会被关闭
• acceptCount:当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理.默认100
2.使用线程池
<Executorname="tomcatThreadPool"namePrefix="req-exec-"maxThreads="1000"minSpareThreads="50"maxIdleTime="60000"/>
<Connectorport="8080" protocol="HTTP/1.1"executor="tomcatThreadPool"/>
其中:
• namePrefix:线程池中线程的命名前缀
• maxThreads:线程池的最大线程数
• minSpareThreads:线程池的最小空闲线程数
• maxIdleTime:超过最小空闲线程数时,多的线程会等待这个时间长度,然后关闭
• threadPriority:线程优先级
3、Tomcat Connector三种运行模式(BIO,NIO, APR)
1)BIO:一个线程处理一个请求。缺点:并发量高时,线程数较多,浪费资源。Tomcat7或以下在Linux系统中默认使用这种方式。
2)NIO:利用Java的异步IO处理,可以通过少量的线程处理大量的请求。Tomcat8在Linux系统中默认使用这种方式。Tomcat7必须修改Connector配置来启动(conf/server.xml配置文件):
<Connectorport="8080"protocol="org.apache.coyote.http11.Http11NioProtocol"connectionTimeout="20000"redirectPort="8443"/>