上周接到一个需求,需要在页面中获取另一个html页面后嵌入当前页面,由于直接用iframe直接请求会多出一些乱起八糟的标签并且不方便编辑样式,于是选择使用ajax。

最初直接通过ajax请求,firefox中查看返回的状态码是200,但一直静入ajax的error分支,通过firebug查看发现是跨域问题,如果是跨域请求json用jsonp处理就好,可以跨域请求html还没有遇到过。

百度后了解到需要通过后台来使跨域请求变成同域,但没找到具体实施方案,于是先尝试直接后台redirect目标页面,再ajax请求后台,结果ajax接收302状态码,进入error分支,接着把ajax换成用complete接收,接收到一个object对象,打印出来是一堆js,便放弃这一方案,不知道有没变法通过ajax正确处理302?

最后是通过HttpClient请求页面,再通过response.getWriter获取PrintWriter输出到前段(没有使用Spring框架),在Spring中对应的实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.climbran.spring.controller;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.apache.http.client.HttpClient;


import javax.servlet.http.HttpServletResponse;

/**
* Created by swe on 15/10/26.
*/

@Controller
@RequestMapping("ajax")
public class AjaxTestController {

@RequestMapping("getHTML")
@ResponseBody
public String getHTML(HttpServletResponse response){

HttpClient httpclient = new DefaultHttpClient(new ThreadSafeClientConnManager());//创建一个客户端,类似打开一个浏览器
HttpGet httpGet = new HttpGet("http://404.58.com/");
String html = null;
try {
HttpResponse res = httpclient.execute(httpGet);
HttpEntity entity = res.getEntity();
html = EntityUtils.toString(entity);
System.out.println(res.getStatusLine().getStatusCode());
System.out.println(html);
}
catch (Exception e){
e.printStackTrace();

}
response.setContentType("text/html;charset=UTF-8");
return html;
}
}

前端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<script type="text/javascript" src="http://localhost:8080/static/js/jquery-1.8.3.min.js"></script>

<title>Test</title>
</head>
<body>
<div class="dd"></div>

<script type="text/javascript">

$.ajax({
type : 'get',
url : '/ajax/getHTML',
dataType : 'html',
data:{
},
success : function(data) {
$(".dd").html(data);
}
});
</script>

</body>
</html>

HttpClient依赖如下:

1
2
3
4
5
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.1</version>
</dependency>

面向切面编程(AOP = Aspect Oriented Programming)核心就是动态代理机制。动态代理在实现阶段不需要关系代理哪一个类,不用实现具体某个类的代理类。本文通过AOP框架的实现来学习动态代理。

1
2
3
4
5
6
7
8
9
10
package com.climbran.designPatterns.proxy;

/**
* Created by swe on 15/9/22.
* 业务接口
*/

public interface Subject {

public void doSomething(String str);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.climbran.designPatterns.proxy;

/**
* Created by swe on 15/9/22.
* 被代理类
*/

public class RealSubject implements Subject {

//实际业务
public void doSomething(String str){
System.out.println("do something: " +str);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.climbran.designPatterns.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
* Created by swe on 15/9/22.
* 业务处理类,对被代理对象操作进行处理
*/

public class DynamicProxyHandle implements InvocationHandler {
private Object targer = null;

public DynamicProxyHandle(Object obj){
targer = obj;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{

if(method.getName().equalsIgnoreCase("doSomething")) //设置point cut
new BeforeAdvice().exec(); //执行前置通知(advie)

return method.invoke(targer, args);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.climbran.designPatterns.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
* Created by ywq on 15/9/22.
* 代理类
*/

public class DynamicProxy<T> {
public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler){
//这里也可以设置一个aspect(aspect = point cut + advice)

//根据传入的loader和interfaces生成代理对象的方法,并且生成的方法通过handler的invoke函数处理,返回代理对象
return (T) Proxy.newProxyInstance(loader, interfaces, handler);
}
}
1
2
3
4
5
6
7
8
package com.climbran.designPatterns.proxy;

/**
* Created by ywq on 15/9/22.
*/

public interface IAdvice {
public void exec();
}
1
2
3
4
5
6
7
8
9
10
11
package com.climbran.designPatterns.proxy;


/**
* Created by ywq on 15/9/22.
*/

public class BeforeAdvice implements IAdvice {
public void exec(){
System.out.println("do before advice");
}
}

最后客户端进行调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.climbran.designPatterns.proxy;

import java.lang.reflect.InvocationHandler;

/**
* Created by swe on 15/9/22.
*/

public class Client {

public static void main(String[] args){
Subject subject = new RealSubject();

InvocationHandler handler = new DynamicProxyHandle(subject);

Class<?> cla = subject.getClass();
Subject proxy = DynamicProxy.newProxyInstance(cla.getClassLoader(), cla.getInterfaces(),handler);

proxy.doSomething("test");
}

}

一、代理模式

代理模式是通过一个代理类来控制对象的操作,下面是代理模式的一个简单实现:

Subject.java
1
2
3
public interface Subject{
public void doSomething();
}

RealSubject.java
1
2
3
4
5
6
7
8
9
10
public class RealSubject implements Subject{
private String topic = "";

public RealSubject(String topic){
this.topic = topic;
}
public void doSomething(){
System.out.println("do " + topic);
}
}

Proxy.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Proxy implements Subject{
private Subject subject = null;

public Proxy(Subject subject){
this.subject = subject;
}

public void doSomething(){
this.before();
this.subject.doSomething();
this.after();
}

private void before(){
//TODO: do something
}

private void after(){
//TODO: do something
}

}

Client.java
1
2
3
4
5
6
7
public class Client{
public void main(String[] args){
Subject subject = new RealSubject("test");
Subject proxy = new Proxy(subject);
proxy.doSomething();
}
}

代理模式中真实角色只负责实际的业务逻辑,不用关心其他非本职的事务(如权限、日志等),其他事务都有代理类去实现(这就是一个简单的aop,更完善的aop实现可以使用动态代理)而且由于实现了接口,可以随时替换真实角色

二、普通代理

普通代理是代理模式的一个扩展,普通代理只要关注代理类,调用者不用关注被代理对象,因此被代理类只需要实现接口就可以随便修改,具有很高的扩展性,其实现对RealSubject和Proxy类做出如下修改:

RealSubject.java
1
2
3
4
5
6
7
8
9
10
11
12
public class RealSubject implements Subject{
private String topic = "";

public RealSubject(Subject _subject,String topic){
if(_subject == null){
throw new Exception("不能创建真实角色");//传入参数用来约束不能直接new一个实力,也可以通过团队内约定来实现
}
}
public void doSomething(){
System.out.println("do " + topic);
}
}

Proxy.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Proxy implements Subject{
private Subject subject = null;

public Proxy(String topic){
try{
subject = new RealSubject(this,topic);
}catch(Exception e){
//TODO: 异常处理
}
}

public void doSomething(){
this.before();
this.subject.doSomething();
this.after();
}

private void before(){
//TODO: do something
}

private void after(){
//TODO: do something
}

}

Client.java
1
2
3
4
5
6
public class Client{
public void main(String[] args){
Subject proxy = new Proxy("test");
proxy.doSomething();
}
}

三、强制代理

强制代理也是代理模式的一个扩展,是通过被代理对象获取对应的代理,并且必须通过代理来访问方法。另外代理类可以实现多个接口,完成不同模块整合,在下面强制代理的示例中一起展示多接口的实现:

Subject.java
1
2
3
4
public interface Subject{
public void doSomething();
public Subject getProxy();
}

Accountant.java
1
2
3
public interface Accountant{
public void count(); //计算花费
}

RealSubject.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class RealSubject implements Subject{
private String topic = "";
private Subject proxy = null;

public RealSubject(String topic){
this.topic = topic;
}

public Subject getProxy(){
this.proxy = new Proxy(this);
return this.proxy;
}

public void doSomething(){
if(isProxy())
System.out.println("do " + topic);
else
System.out.println("error: please use proxy");
}

//判断是否是通过代理调用,其实调用getProxy方法后不通过代理也可以直接调用其他方法,和普通代理不能直接new一个被代理对象的约束一样,可通过团队内约定来约束
private boolean isProxy(){
return proxy==null?false:true;
}
}

Proxy.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Proxy implements Subject,Accountant{
private Subject subject = null;

public Proxy(Subject subject){
this.subject = subject;
}

public doSomething(){
subject.doSomething();
this.count();
}
publiv Subject getProxy(){
return this;
}

public void count(){
//TODO: do something
}
}

Client.java
1
2
3
4
5
6
7
public class Client{
public void main(String[] args){
Subject subject = new RealSubject("test");
Subject proxy = subject.getProxy();
proxy.doSomething();
}
}

四、动态代理

和AOP编程密切相关,见《动态代理及简单的AOP框架的实现》

一、下载与安装ActiveMQ
http://activemq.apache.org/download.html
我下载的是最新的5.12.0版,解压后安装目录下运行bin/activemq start

二、配置POM文件
看包括springside在内的很多demo的时候看到都是配置的active-core的依赖,而官网5.12.0下面推荐的配置是activemq-all,没太弄明白,后来突然发现页面有一行小字:

If you need more fine grained control of your dependencies (activemq-all is an uber jar) pick and choose from the various components activemq-client, activemq-broker, activemq-xx-store etc.

英文不好惹的祸啊,如果是中文早就看到了…往前几个版本的文档看了下,从5.8就开始用activemq-client + activemq-broker替代activemq-core了,大概看了一下active-all,把几个不同版本的active-xx-store包含进去了。

最后我的配置:

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<activemq.version>5.12.0</activemq.version>

......

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
</dependency>

<!-- 我配置了slf4j和logback,所以把log4j的依赖exclusion -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-client</artifactId>
<version>${activemq.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
<version>${activemq.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-jdbc-store</artifactId>
<version>${activemq.version}</version>
</dependency>

......

接下来要实现Producer和Consumer

首先是Producer,可以实现MessageProducer接口,但接口中有很多方法不需要实现,所以没有继承该接口:

Producer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.climbran.jms;

import javax.jms.Destination;
import org.springframework.jms.core.JmsTemplate;

/**
* JMS消息生产者.
* @author swe
*/


public class Producer {

private JmsTemplate jmsTemplate;
private Destination notifyQueue;
private Destination notifyTopic;

public void sendQueue(final String message) {
sendMessage(message, notifyQueue);
}

public void sendTopic(final String message) {
sendMessage(message, notifyTopic);
}

private void sendMessage(String message, Destination destination) {

jmsTemplate.convertAndSend(destination, message);
}

public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}

public void setNotifyQueue(Destination notifyQueue) {
this.notifyQueue = notifyQueue;
}

public void setNotifyTopic(Destination nodifyTopic) {
this.notifyTopic = nodifyTopic;
}
}

接下来实现消费者(Consumer),Spring通过DefaultMessageListenerContainer内部初始化一个taskExecutor来执行监听的任务,这里实现监听器就好了:

MyMessageListener.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.climbran.jms;

import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* 消息的异步被动接收者.
* @author swe
*/

public class MyMessageListener implements MessageListener {

private static Logger logger = LoggerFactory.getLogger(NotifyMessageListener.class);


/**
* MessageListener回调函数.
*/

@Override
public void onMessage(Message message) {
try {

TextMessage textMessage = (TextMessage) message;
// 打印消息详情
logger.info(""+textMessage.getText());

} catch (Exception e) {
logger.error("处理消息时发生异常.", e);
}
}
}

接下来就是配置文件,配置了1个生产者,3个消费者,其中一个监听queue,两个监听topic:

applicationContext-jms.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">


<description>JMS配置</description>

<!-- ActiveMQ 连接工厂 -->
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>

<!-- Spring Caching 连接工厂 -->
<bean id="cachingConnectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<property name="targetConnectionFactory" ref="connectionFactory" />
<property name="sessionCacheSize" value="10" />
</bean>

<!-- Queue定义 -->
<bean id="notifyQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="q.notify" />
</bean>

<!-- Topic定义 -->
<bean id="notifyTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="t.notify" />
</bean>

<!-- Spring JMS Template -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="cachingConnectionFactory" />
</bean>

<!-- 使用Spring JmsTemplate的消息生产者 -->
<bean id="notifyMessageProducer" class="com.climbran.jms.Producer">
<property name="jmsTemplate" ref="jmsTemplate" />
<property name="notifyQueue" ref="notifyQueue" />
<property name="notifyTopic" ref="notifyTopic" />
</bean>

<!-- 异步接收Queue消息Container,DefaultMessageListenerContainer内部初始化建立的一个taskExecutor执行监听的任务-->
<!-- 相当于配置Consumer,并为Consumer设置了Listenser,同时为Consumer设置了destination-->
<bean id="queueContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="notifyQueue" />
<property name="messageListener" ref="notifyMessageListener" />
<property name="concurrentConsumers" value="10" />
</bean>

<!-- 异步接收Topic消息Container -->
<bean id="topicContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="notifyTopic" />
<property name="messageListener" ref="notifyMessageListener" />
</bean>

<bean id="topicContainer2" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="notifyTopic" />
<property name="messageListener" ref="notifyMessageListener" />
</bean>

<!-- 异步接收消息处理类 -->
<bean id="notifyMessageListener" class="com.climbran.jms.MyMessageListener" />
</beans>

最后业务层调用:

1
2
producer.sendTopic("a topic message");
producer.sendQueue("a queue message");

结果打印出2条”a topic message”,1条”a queue message”

一、什么是JMS

JMS(java messgae service)是SUN开发的java消息系统的api,提供异步通信的服务。可用于订单处理、邮寄发送等耗时较长的操作,在减少加快响应时间的同时又能避免开多线程带来的同步问题(多线程在等待请求的io操作或其他服务器响应时一直占有临界资源,而异步调用将请求发送出去后就不用再关心)。JMS还能最大化降低应用程序与应用系统之间的耦合度。(消息发送方和接收方之间解耦)

应用场景示例:
电商网站大量用户提交订单时,使用正常的同步方式处理订单可能受订单处理服务器性能瓶颈影响,导致用户等待时间过长,当并发量太大时甚至会导致服务器的崩溃。这时使用JMS可以将用户请求都加入到消息队列中,然后给用户返回订单处理中。然后订单处理服务器逐个的处理消息队列中的订单。

二、JMS两种消息模型

  1. 点到点(P2P)模型
    生产者将消息发送到队列(Queue)中,消费者从队列中取出消息(eg:订单处理中,多台订单处理服务器从消息队列中取出订单)

特点:
(1)每条只能被接收一次,如果一条消息被一个消费者接收,那么其他的消费者就不能得到这条消息了。
(2)只要消息没过期且没被其他消费者接受,消费者可能在任何时间接收消息队列中的消息,与生产者消费者运行先后无关。
(3)收到消息后消费者必须确认消息已被接收,否则JMS服务提供者会认为该消息没有被接收。程序可以自动进行确认,不需要人工干预。
(4)非持久的消息最多只发送一次,持久的消息严格发送一次。

  1. 发布/订阅(Pub/Sub)模型
    生产者发布消息到某一主题(Topic),与主题相关的消费者都会收到消息(eg:系统发送系统邮件都某个用户组,该用户组的用户都会收到消息)

特点:
(1)每条消息可以有多个消费者,如果报纸和杂志一样,谁订阅了谁都可以获得。
(2)订阅者只能接受他们订阅之后出版的消息,即订阅者必须先运行,再等待生产者的运行。

三、主要对象

  • Destination:消息发送的目的地,即Queue或Topic
  • Message:被发送的消息,有StreamMessage、MapMessage、TextMessage、ObjectMessage、BytesMessage、XMLMessage,最常用的是TextMessage(普通字符串,包含一个String)和ObjectMessage(包含一个可序列化的java对象)
  • Session:与JMS建立的会话,通过Session才能创建Message
  • Connection:与JMS建立的连接,可以从连接创建Session
  • ConnectionFactory:用来创建Connection
  • Producer:消息的生产者,用来发送消息,通过Session创建
  • MessageConsumer:消息的消费者,用来接收消息,通过Session创建

基于ActiveMQ的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ConnectionFactory factory = new ActiveMQConnectionFactory("vm://localhost");
Queue queue = new ActiveMQQueue("demoQueue");

Connection connection = factory.createConnection();
connection.start();

Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);

Message message = session.createTextMessage("Hello world");
MessageProducer producer = session.createProducer(queue);
producer.send(message);

MessageConsumer consumer = session.createConsumer(queue);
Message receive = comsumer.receive();

四、JMS的同步调用与异步调用

  1. 同步调用

    1
    consumer.receive()或consumer.receive(init timeout)

    消息接受者会一直等待,直到有消息或超时

  2. 异步调用
    通过MessageListener

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    MessageConsumer comsumer = session.createConsumer(queue);  
    comsumer.setMessageListener(new MessageListener(){
    @Override
    public void onMessage(Message m) {
    TextMessage textMsg = (TextMessage) m;
    try {
    System.out.println(textMsg.getText());
    } catch (JMSException e) {
    e.printStackTrace();
    }
    }
    });

    通过监听器监听,当有消息到达时调用onMessage方法

单例模式分为饿汉式和懒汉式,饿汉式写起来简单,且不用考虑初始化时的同步问题;懒汉式的优点在于一开始不用初始化,可以节约内存,但需要为初始化操作进行同步。

一直不理解为了这一点内存给一个方法加锁值得吗?影响并发,可能是做的项目还不够多,没有遇到需要大量内存的情况?

不过昨天发现了一个比较好的方法可以在节省内存的同时减少阻塞,同样的逻辑也可以用在其他需要同步的情况下,特此记录下

饿汉式标准版:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton{
private static final Singleton instance = new Singleton();

private Singleton(){
}

public static Singleton getInstance(){
return instance;
}

//other method

}

懒汉标准版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton{
private static Singleton instance = null;

private Singleton(){
}

public static Singleton getInstance(){
synchronized(Singleton.class){
return instance==null?new Singleton:instance;
}
}

//other method

}

懒汉优化版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Singleton{
private static Singleton instance = null;

private Singleton(){
}

public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
if(instance==null)
return new Singleton();
}
}
return instance
}

//other method

}

优化后,只有未初始化时才会加锁,初始化后instance不为null,直接返回单例对象,不需要再次进入同步块。

很多人都知道synchronized可以用来加锁,但对于synchronized(Object o)里的参数不太了解。

java的每个对象隐含一个内部锁,这个内部锁对不同线程是互斥的。synchronized(object){}的本质就是对{}区域中的代码加上object对象的对象锁,只有获取了object的对象锁才能执行该代码块。

如果有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Object o1 = new Object();
Object o2 = new Object();

synchronized(o1){
//block1
}

synchronized(o1){
//block2
}

synchronized(o2){
//block3
}

当有一个线程1在执行block1时,线程2想要执行block2同样会被阻塞,应为o1的对象锁已经被线程1获取;而此时线程3想执行block3则不受影响。

对于类锁,其实就是Class实例的对象锁。因为一个类只有一个Class对象,所以同一个类的所有对象共享同一个类锁。

还有对于方法锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
class SynchronizedDemo{

public void synchronized method1() {//TODO}

public void method2() {synchronized1(this){//TODO} }
//method1效果等价于method2

public static void synchronized method3(){//TODO}

public static void method4(){synchronized(SynchronizedDemo.class){//TODO} }
//method3效果等价于method4

}

总结:
大多数情况下同步代码块比同步方法要好,因为一个同步方法被执行完之前,该对象的所有同步方法都会被阻塞,而同步代码块可以通过加不同的对象锁来减少阻塞。

一、Collection接口
首先是Collection接口,继承Collection接口的有List、Set和Queue接口,Collection接口又继承自Iterable接口,所以List、Set、Queue的实现类都可以使用迭代器。

Interable中使用interator()方法返回一个Iterator接口类型的对象,Iterator有三个方法:

1
2
3
boolean hasNext(); //是否有下一个元素
E next(); //返回的下一个元素
remove() //移除迭代器返回的最后一个元素

Collection增加以下方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int size();
boolean isEmpty();
boolean contains(Object o);
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
/*retainAll于从列表中移除未包含在指定collection中的所有元素
如果List集合对象由于调用retainAll方法而发生更改,则返回 true*/

void clear();
boolean equals(Object o);
int hashCode();

set对Collection基本没有新增方法

List比Collection增加了以下方法:

1
2
3
4
5
6
7
8
get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator<E> listIterator(); //比Interator多了hasPrevious、previous、nextIndex、previousIndex、set、add;
List<E> subList(int fromIndex, int toIndex); //返回一个子list,注意对子list的操作会影响到原来的list

Queue比Collection增加以下方法:

1
2
3
4
5
boolean offer(E e);		//添加一个元素并返回true       如果队列已满,则返回false
E remove(); // 移除并返问队列头部的元素,如果队列为空,则抛出一个NoSuchElementException
E poll(); //移除并返问队列头部的元素,如果队列为空,则返回null
E element(); //返回队列头部的元素,如果队列为空,则抛出一个NoSuchElementException异常
E peek(); //返回队列头部的元素,如果队列为空,则返回null

二、Map接口
然后是Map接口,Map接口没有继承自任何接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int size();
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
V remove(Object key);
void putAll(Map<? extends K, ? extends V> m);
void clear();
Set<K> keySet(); //返回key组成的一个set,多次调用返回同一个引用,该Set支持删除操作,不支持add、addAll,能影响到Map;
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
default boolean replace(K key, V oldValue, V newValue);
default V replace(K key, V value) //如果map中已有key则覆盖,没有则返回null

三、常用容器
(1)List
ArrayList 非线程安全,能快速进行随机访问,顺序添加效率也高于LinkedList
LinkedList 非线程安全,底层是双向链表,增删比ArrayList快,能更好的支持栈,同时实现了Queue接口,能支持队列操作,适合变动较为频繁的存储。
Vector 相当于线程安全版ArrayList,如果不考虑并发,ArrayList效率较高,当数组长度不够时,Vector增加100%,ArrayList增加50%

其中LinkedList有些独有的方法:

1
2
3
4
5
6
E getFirst();
E getLast();
E removeFirst();
E removeLast();
void addFirst(E e);
void addLast(E e);

(2)Map
HashMap 非线程安全,基于散列表实现,时间开销为O(1),一般效率比TreeMap高
TreeMap 非线程安全,基于红黑树实现,时间开销为O(logN),效率比HashMap低,但内部是有序的,TreeMap是唯一带有subMap()方法的Map,可以返回一个子树
ConcurrentHashMap 线程安全,基于散列表实现ConcurrentMap接口,根据hash值来桶加锁。
WeakHashMap 非线程安全,基于散列表是实现,其中的键是弱键,当map外没有引用时可能被GC回收。
四、优化
1、ArrayList默认生成一个长度为10的数组,当超过会生成一个更长的数组对象,然后将数据复制过去,如果估计使用ArrayList时将是很大一个数组可以预先设置数组大小来提高效率, eg: List list = new ArrayList(1000);
2、(备注:后来查看ArrayList和LinkedList的代码后发现size()方法只直接return size字段的值,所以两种方法应该效率没差别)
通过循环遍历List时,如果没有add、remove操作改变数组大小,不用每次都读取数组大小,eg:

for (int i = 0; i < vector.size (); i++) 
改为 
for (int i = 0,n=list.size (); i < n; i++) 

3、ArrayList用get遍历,用iterator遍历也可以
LinkedList应避免用get遍历(随机存取效率低),尽量用iterator

之前做听说可以不维持服务器的状态来减轻服务器压力,以为是指整个服务端都没有状态,一直没弄明白除非用cookie或者一次性的token,怎么维持客户端的状态。

今天看到无状态服务(stateless service)才明白无状态是指对服务器集群中每台服务器(web server)来说是没有状态的。

一般可以认为session就是有状态的,session主要用于两种情况:
1、一个事务的多次请求间暂存数据
2、维持登陆状态
对于第一种情况,可以通过将数据回传到客户端,放在表单中隐藏或用sessionStorage、cookie来实现。
对于第二种情况,可以将session从web server中剥离,存放在共享的地方(如redis)。

附:
理解服务的无状态性
状态和无状态--2种服务器架构之间的比较

首先安装redis:Mac下安装Redis

java客户端选用Jedis,加入pom文件依赖

pom.xml
1
2
3
4
5
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.3</version>
</dependency>

下面是一个简化的Template Demo,用来实现redis各种命令的映射:
com.climbran.redis.JedisTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package com.climbran.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisPool;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Tuple;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisDataException;
import redis.clients.jedis.exceptions.JedisException;

/**
* JedisTemplate 提供了一个template方法,负责对Jedis连接的获取与归还。
* JedisAction<T> 和 JedisActionNoResult两种回调接口,适用于有无返回值两种情况。
*/

public class JedisTemplate {

private static Logger logger = LoggerFactory.getLogger(JedisTemplate.class);

private JedisPool jedisPool;

public JedisTemplate(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}

/**
* Callback interface for template.
*/

public interface JedisAction<T> {
T action(Jedis jedis);
}

/**
* Callback interface for template without result.
*/

public interface JedisActionNoResult {
void action(Jedis jedis);
}

/**
* Execute with a call back action with result.
*/

public <T> T execute(JedisAction<T> jedisAction) throws JedisException {
Jedis jedis = null;
boolean broken = false;
try {
jedis = jedisPool.getResource();
return jedisAction.action(jedis);
} catch (JedisException e) {
broken = handleJedisException(e);
throw e;
} finally {
closeResource(jedis, broken);
}
}

/**
* Execute with a call back action without result.
*/

public void execute(JedisActionNoResult jedisAction) throws JedisException {
Jedis jedis = null;
boolean broken = false;
try {
jedis = jedisPool.getResource();
jedisAction.action(jedis);
} catch (JedisException e) {
broken = handleJedisException(e);
throw e;
} finally {
closeResource(jedis, broken);
}
}


/**
* Handle jedisException, write log and return whether the connection is broken.
*/

protected boolean handleJedisException(JedisException jedisException) {
if (jedisException instanceof JedisConnectionException) {
logger.error("Redis connection " + jedisPool + " lost.", jedisException);
} else if (jedisException instanceof JedisDataException) {
if ((jedisException.getMessage() != null) && (jedisException.getMessage().indexOf("READONLY") != -1)) {
logger.error("Redis connection " + jedisPool + " are read-only slave.", jedisException);
} else {
// dataException, isBroken=false
return false;
}
} else {
logger.error("Jedis exception happen.", jedisException);
}
return true;
}

/**
* Return jedis connection to the pool, call different return methods depends on the conectionBroken status.
*/

protected void closeResource(Jedis jedis, boolean conectionBroken) {
try {
if (conectionBroken) {
jedisPool.returnBrokenResource(jedis);
} else {
jedisPool.returnResource(jedis);
}
} catch (Exception e) {
logger.error("return back jedis failed, will fore close the jedis.", e);
JedisUtils.destroyJedis(jedis);
}

}

// / Common Actions ///
public Boolean del(final String... keys) {
return execute(new JedisAction<Boolean>() {

@Override
public Boolean action(Jedis jedis) {
return jedis.del(keys) == keys.length ? true : false;
}
});
}

public void flushDB() {
execute(new JedisActionNoResult() {

@Override
public void action(Jedis jedis) {
jedis.flushDB();
}
});
}

// / String Actions ///
public String get(final String key) {
return execute(new JedisAction<String>() {

@Override
public String action(Jedis jedis) {
return jedis.get(key);
}
});
}

public void set(final String key, final String value) {
execute(new JedisActionNoResult() {

@Override
public void action(Jedis jedis) {
jedis.set(key, value);
}
});
}

//Other Actions

......
}

com.climbran.redis.JedisUtils
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.climbran.redis;

import com.climbran.redis.JedisTemplate.JedisAction;
//import com.climbran.redis.pool.JedisPool;
import redis.clients.jedis.JedisPool;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisException;

public class JedisUtils {

private static final String OK_CODE = "OK";
private static final String OK_MULTI_CODE = "+OK";

/**
* 判断 返回值是否ok.
*/

public static boolean isStatusOk(String status) {
return (status != null) && (OK_CODE.equals(status) || OK_MULTI_CODE.equals(status));
}

/**
* 在Pool以外强行销毁Jedis.
*/

public static void destroyJedis(Jedis jedis) {
if ((jedis != null) && jedis.isConnected()) {
try {
try {
jedis.quit();
} catch (Exception e) {
}
jedis.disconnect();
} catch (Exception e) {
}
}
}
}

启动redis服务,执行main函数:

com.climbran.redis.JedisUtils
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import com.climbran.redis.JedisTemplate;

import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisTest {

public static void main(String[] args){
JedisPool pool = new JedisPool(new JedisPoolConfig(),"localhost",6379);
JedisTemplate template = new JedisTemplate(pool);
template.set("foo","bar");
System.out.println(template.get("foo"));
}
}

如果将JedisTemplate注入dao层调用方法则可对entity层与redis进行映射。