Basic Thread Synchronization

Introduction

In a concurrent application, it is normal that multiple threads read or write the same data or have access to the same file or database connection.

The solution for these problems comes with the concept of critical section.

A critical section is a block of code that accesses a shared resource and can't be executed by more than one thread at the same time.

When a thread wants access to a critical section, it uses one of those synchronization mechanisms to find out if there is any other thread executing the critical section. Otherwise, the thread is suspended by the sunchronization mechanism until the thread that is executing the critical section ends it.

Synchronizing a method

the use of the synchronized keyword to control the concurrent access to a method.

Only one execution thread will access one of the methods of an object declared with the synchronized keyword. If another thread tries to access any method declared with the synchronized keyword of the same object, it will suspended until the first thread finishes the execution of the method.

Static methods have a different behavior. Only one execution thread will access one of the static methods declared with the synchronized keyword, but another thread can access other non-static methods of an object of that class.

Two threads can access two different synchronized methods if one is static and the other one is not.

Only a thread can access the methods of an object that use the sychronized keyword in their declaration. If a thread(A) is executing a synchronized method and another thread(B) wants to execute other synchronized methods of the same object, it will be blocked until the thread(A) ends. But if threadB has access to different objects of the same class, none of them will be blocked.

The synchronized keyword penalizes the performance of the application, so you must only use it on methods that modify shared data in a concurrent environment.

If you have multiple threads calling a synchronized method, only one will execute them at a time while the others will be waiting. If the operation doesn't use the synchronized keyword, all the threads can execute the operation at the same time, reducing the total execution time.

If you know that a method will not be called by more than one thread, don't use the synchronized keyword.

We can use the synchronized keyword to protect the access to a block of code instead of an entire method. We should use the synchronized keyword in this way to protect the access to the shared data, leaving the rest of operations out of this block, obtaining a better performance of the application. The objective is to have the critical secion be as short as possible.

Normally, we will use the this keyword to reference the object that is executing the method.

synchronized (this) {
    // Java code
}

Arranging independent attributes in synchronized classes

When you use the synchronized keyword to protect a block of code, you must pass an object reference as a parameter. Normally, you will use the this keyword to reference the object that executes the method, but you can use other object references.

For example, if you have two independent attributes in a class shared by multiple threads, you must synchronize the access toe each variable, but there is no problem if there is one thread accessing one of the attributes and another thread accessing the other at the same time.

Using conditions in synchronized code

A classic problem in concurrent programming is the producer-consumer problem. We have a data buffer, one or more producers of data that save it in the buffer and one or more consumers of data that take it from the buffer.

As the buffer is a shared data structure, we have to control the access to it using a synchronization mechanism such as the synchronized keyword, but we have more limitations. A producer can't save data in the buffer if it's full and the consumer can't take data from the buffer if it's empty.

For these types of situations, Java provides the wait(), notify() and notifyAll() methods implemented in the Object class.

When the thread calls the wait() method, the JVM puts the thread to sleep and release the obejct that controls the synchronized block of code that it's executing and allows the other threads to execute other blocks of synchronized code protected by that object. To wake up the thread, you must call the notify() or notifyAll() method inside a block of code protected by the same object.

Synchronizing a block of code with a Lock

based on the Lock interface and classes that implement it (as ReentrantLock). This mechanism presents some advantages, which are as follows:

  • It allows the structuring of synchronized blocks in a more flexible way. With the synchronized keyword, you have to get and free the control over a synchronized block of code in a structured way. The Lock interfaces allow you to get more complex structures to implement your ciritical section.
  • The Lock interfaces provide additional functionalities over the synchronized keyword. One of the new functionalities is implemented by the tryLock() method. With the synchronized keyword, when a threadA tries to execute a synchronized block of code, if there is another threadB executing it, the threadA is suspended until the threadB finishes the execution of the synchronized block. With locks, you can execute the tryLock() method. This meothod returns a Boolean value indicating if there is another thread running the code protected by this lock.
  • The Lock interfaces allow a separation of read and write operations having multiple readers and only one modifier.
  • The Lock interfaces offer better performance than the synchronized keyword.

When we want to implement a critical section using locks and guarantee that only one execution thread runs a block of code, we have to create a ReentrantLock object.

At the beginning of the critical section, we have to get the control of the lock using the lock() method. When a threadA calls this method, if no other thread has the control of the lock, the method gives the threadA the control of the lock and returns immediately to permit the execution of the critical secion to this thread.

Otherwise, if there is another thread B executing the critical section controlled by this lock, the lock() method puts the thread A to sleep until the thread B finishes the execution of the critical section.

At the end of the critical section, we have to use the unlock() method to free the control of the lock and allow the other threads to run this critical section. If you don't call the unlock() method at the end of the critical section, the other threads that are waiting for that block will be waiting forever, causing a deadlock situation. If you use try-catch blocks in you critical section, don't forget to put the sentence containing the unlock() method inside the finally section.

The Lock interface includes another method to get the control of the lock. It's the tryLock() method. The biggest difference with the lock() method is that this method, if the thread that uses it can't get the control of the Lock interface, returns immediately and doesn't put the thread to sleep.

Synchronizing data access with read/write locks

ReentrantReadWriteLock has two locks, one for read operations and one for write operations. There can be more than one thread using read operations simultaneously, but only one thread can be using write operations. When a thread is doing a write operation, there can't be any thread doing read operations.

Modifying Lock fairness

The constructor of the ReentrantLock and ReentrantReadWriteLock classes admits a boolean parameter named fair that allows you to control the behavior of both classes.

The false value is the default value and it's called the non-fair mode. When there are some threads waiting for a lock and the lock has to select one of them to get the access to the critial section, it selects one without any criteria.

The true value is called the fair mode. When there are some threads waiting for a lock and the lock has to select one to get access to a critical section, it selects the thread that has been waiting for the most time.

As the tryLock() method doesn't put the thread to sleep if the Lock interface is used, the fair attribute doesn't affect its funcionality.

While Thread 0 is running the first block of code protected by the lock, we have nine threads waiting to execute that block of code. When Thread 0 releases the lock, immediately, it requests the lock again, so we have 10 threads trying to get the lock. As the fair mode is enabled, the Lock interface will choose Thread 1, so it's the thread that has been waiting for more time for the lock.

Using multiple conditions in a Lock

A lock may be associated with one or more conditions. The purpose of these conditions is to allow threads to have control of a lock and check whether a condition is true or not and, if it's false, be suspended until another thread wakes them up.

All the Condition objects are associated with a lock and created using the newCondition() method declared in the Lock interface. Before we can do any operation with a condition, you have to have the control of the lock associated with the condition, so the operations with conditions must by in a block of code that begins with a call to a lock() method of a Lock object and ends with an unlock() method of the same Lock object.

When a thread calls the await() method of a condition, it automatically frees the control of the lock, so that another thread can get it and begin the execution of the same, or another critical section protected by that lock.

When a thread calls the singal() or signalAll() methods of a condition, one or all of the theads that were waiting for that condition are woken up, but this doesn't guarantee that the condition that made them sleep is now true, so you must put the await() calls inside a while loop. You can't leave that loop until the condition is true. While the condition is false, you must call await() again.

2016/4/7 posted in  Java7 Concurrency Cookbook

AOP

为某个对象提供一个代理,以控制对这个对象的访问。代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。

静态代理和动态代理

  • 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了

    • 优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。
    • 缺点:
      • 一个代理类只能为一个接口服务
      • 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法

代理接口

public interface Subject {
    public void dealTask(String taskName);
}

真正执行任务的类

public class RealSubject implements Subject {  
    @Override  
    public void dealTask(String taskName) {  
     System.out.println("正在执行任务:"+taskName);  
     try {  
        Thread.sleep(500);  
     } catch (InterruptedException e) {  
        e.printStackTrace();  
     }  
    }  
}  

代理类

public class ProxySubject implements Subject {  
    //代理类持有一个委托类的对象引用  
    private Subject delegate;  
       
    public ProxySubject(Subject delegate) {  
        this.delegate = delegate;  
    }  
      
    /** 
    * 将请求分派给委托类执行,记录任务执行前后的时间,时间差即为任务的处理时间 
    *  
    * @param taskName 
    */  
    @Override  
    public void dealTask(String taskName) {  
        long stime = System.currentTimeMillis();   
        //将请求分派给委托类处理  
        delegate.dealTask(taskName);  
        long ftime = System.currentTimeMillis();   
        System.out.println("执行任务耗时"+(ftime - stime)+"毫秒");  
    }  
}  

静态代理类工厂

public class SubjectStaticFactory {  
    //客户类调用此工厂方法获得代理对象。  
    //对客户类来说,其并不知道返回的是代理类对象还是委托类对象。  
    public static Subject getInstance(){   
        return new ProxySubject(new RealSubject());  
    }  
}  

客户类

public class Client1 {  
    public static void main(String[] args) {  
        Subject proxy = SubjectStaticFactory.getInstance();  
        proxy.dealTask("DBQueryTask");  
    }   
}
  • 动态代理:在程序运行时,运用反射机制动态创建而成
    1. jdk动态代理 只能代理实现接口的类,不能实现接口的类就不能实现JDK的动态代理
    2. CGLib动态代理

Spring AOP:前置增强、后置增强、环绕增强,抛出增强

  • 前置增强类实现了org.springframework.aop.MethodBeforeAdvice接口
  • 后置增强类实现了org.springframework.aop.AfterReturningAdvice接口
  • 环绕增强类需要实现org.aopalliance.intercept.MethodInterceptor接口
  • 抛出增强类需要实现org.springframework.aop.ThrowsAdvice接口

201604061031

  • 引入增强类,扩展了org.springframework.aop.support.DelegatingIntroductionInterceptor

201604061035

以上定义了一个引入增强类,扩展了 org.springframework.aop.support.DelegatingIntroductionInterceptor类,同时也实现了新定义的Apology接口。在类中首先覆盖了父类的invoke()方法,然后实现了Apology接口的方法

20160406103701

需要注意proxyTargetClass属性,它表明是否代理目标类,默认为false,也就是代理接口了,此时Spring就用JDK动态代理。如果为true,那么Spring就用CGLib动态代理

201604061038

saySorry()方法是可以被greetingImpl对象来直接调用的,只需将其强制转换为该接口即可

Spring AOP :切面

通过切面,将增强类与拦截匹配条件组合在一起,然后将这个切面配置到ProxyFactory中,从而生成代理

Advisor(切面)封装了Advice(增强)与Pointcut(切点 )

201604061048
201604061049

注意以上代理对象的配置中的interceptorNames,它不再是一个增强,而是一个切面,因为已经将增强封装到该切面中了。此外,切面还定义了一个切点(正则表达式),其目的是为了只将满足切点匹配条件的方法进行拦截。

需要强调的是,这里的切点表达式是基于正则表达式的。
示例中的aop.demo.GreetingImpl.good.*表达式后面的.*表示匹配所有字符,翻译过来就是“匹配 aop.demo.GreetingImpl 类中以 good 开头的方法”

除了RegexpMethodPointcutAdvisor以外,在Spring AOP中还提供了几个切面类,比如:

  • DefaultPointcutAdvisor:默认切面(可扩展它来自定义切面)

  • NameMatchMethodPointcutAdvisor:根据方法名称进行匹配的切面

  • StaticMethodMatcherPointcutAdvisor:用于匹配静态方法的切面

Spring AOP:自动代理

  • 扫描Bean名称

201604061054

以上使用BeanNameAutoProxyCreator只为后缀为ImplBean生成代理。

需要注意的是,这个地方我们不能定义代理接口,也就是interfaces属性,因为我们根本就不知道这些Bean到底实现了多少接口。此时不能代理接口,而只能代理类。

所以这里提供了一个新的配置项,它就是optimize。若为true时,则可对代理生成策略进行优化(默认是false的)。也就是说,如果该类有接口,就代理接口(使用JDK动态代理);
如果没有接口,就代理类(使用CGLib动态代理)。而并非像之前使用的proxyTargetClass属性那样,强制代理类,而不考虑代理接口的方式。可见Spring AOP确实为我们提供了很多很好地服务

CGLib创建代理的速度比较慢,但创建代理后运行的速度却非常快,而JDK动态代理正好相反。如果在运行的时候不断地用CGLib去创建代理,系统的性能会大打折扣,所以建议一般在系统初始化的时候用CGLib去创建代理,并放入SpringApplicationContext中以备后用。

  • 扫描切面配置

201604061057

这里无需再配置代理了,因为代理将会由DefaultAdvisorAutoProxyCreator自动生成。也就是说,这个类可以扫描所有的切面类,并为其自动生成代理。

Spring + AspectJ

  • 基于注解:通过 AspectJ execution 表达式拦截方法

201604061103

类上面标注的@Aspect注解,这表明该类是一个Aspect(其实就是Advisor)。

该类无需实现任何的接口,只需定义一个方法(方法叫什么名字都无所谓),只需在方法上标注@Around注解,在注解中使用了AspectJ切点表达式。方法的参数中包括一个ProceedingJoinPoint对象,它在AOP中称为Joinpoint(连接点),可以通过该对象获取方法的任何信息

execution(* aop.demo.GreetingImpl.*(..))

execution():表示拦截方法,括号中可定义需要匹配的规则。

第一个“*”:表示方法的返回值是任意的。

第二个“*”:表示匹配该类中所有的方法。

(..):表示方法的参数是任意的。 

配置<aop:aspectj-autoproxy proxy-target-class="true"/>

  • 基于注解:通过 AspectJ @annotation 表达式拦截方法 201604061109

201604061111

除了@Around注解外,其实还有几个相关的注解,稍微归纳一下吧:

@Before:前置增强

@After:后置增强

@Around:环绕增强

@AfterThrowing:抛出增强

@DeclareParents:引入增强

除了使用@Aspect注解来定义切面类以外,Spring AOP也提供了基于配置的方式来定义切面类:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans ...">
    <bean id="greetingImpl" class="aop.demo.GreetingImpl"/>
    <bean id="greetingAspect" class="aop.demo.GreetingAspect"/>
    <aop:config>
        <aop:aspect ref="greetingAspect">
            <aop:around method="around" pointcut="execution(* aop.demo.GreetingImpl.*(..))"/>
        </aop:aspect>
    </aop:config>
    </beans>

使用<aop:config>元素来进行AOP配置,在其子元素中配置切面,包括增强类型、目标方法、切点等信息。

201604061419

Reference

http://blog.csdn.net/giserstone/article/details/17199755

2016/4/7 posted in  spring

Thread Management

Introduction

Inside a process, we can have various simultaneous tasks. The concurrent tasks that run inside a process are called threads.

We have two ways of creating a thread in Java:

  • Extending the Thread class and overriding the run() method
  • Building a class that implements the Runnable interface and then creating an object of the Thread class passing the Runnable object as a parameter

Calculator

public class Calculator implements Runnable
{
    private int number;

    public Calculator (int number)
    {
        this.number = number;
    }

    @Override
    public void run ()
    {
        for (int i = 1; i <= 10; i ++)
        {
            System.out.printf("%s: %d * %d = %d \n", Thread.currentThread().getName(), number, i, i * number);
        }
    }
}

Main

public class Main
{
    public static void main (String[] args)
    {
        for (int i = 1; i <= 10; i ++)
        {
            Calculator calculator = new Calculator(i);
            Thread thread = new Thread(calculator);
            thread.start();
        }
    }
}

Every Java program has at least on execution thread. When you run the program, the JVM runs this execution thread that calls the main() method of the program.

When you call the start() method of a Thread object, we are creating another execution thread.

A Java program ends when all its threads finish. If the initial thread ends, the rest of the threads will continue with their execution until they finish. If one of the threads use the System.exit() instruction to end the execution of the program, all the threads end their execution.

Creating an object of the Thread class doesn't create a new execution thread. Also, calling the run() method of a class that implements the Runnable interface doesn't create a new execution thread. Only calling the start() method creates a new execution thread.

Getting and setting thread information

The Thread class saves some information attributes that can help us to identify a thread. These attributes are:

  • ID: stores a unique identifier for each Thread
  • Name: stores the name of the Thread
  • Priority: stores the priority of the Thread object. Threads can have a priority between 1 and 10, where 1 is the lowest.
  • Status: stores the status of Thread. Thread can be in one of these six states: new, runnable, blocked, waiting, time waiting, or terminated.

The threads with the highest priority end before the ones with the lowest priority.

Interrupting a thread

A Java program with more than one execution thread only finishes when the execution of all of its threads end.

Java provides the interruption mechanism to indicate to a thread that we want to finish it. Thread has to check if it has been interrupted or not, and it can decide if it responds to the finalization request or not. Thread can ignore it adn continue with its execution.

The Thread class has an attribute that stores a boolean value indicating whether that thread has been interrupted or not. When you call the interrupt() method of a thread, you set that attribute to true. The isInterrupted() method only returns the value of that attribute.

You can throw InterruptedException when you detect the interruption of the thread and catch it in the run() method.

Sleeping and resuming a thread

Sometimes, the thread does nothing. During this time, the thread doesn't use any resources of the computer. After this time, the thread will be ready to continue with its execution when the JVM chooses it to be executed. You can use the sleep() method of the Thread class for this purpose.

Another possibility is to use the sleep() method of an element of the TimeUnit enumeration. This method uses the sleep() method of the Thread classes to put the current thread to sleep, but it receives the parameter in the unit that it represents and converts it to milliseconds.

When you call the sleep() method, Thread leaves the CPU and stops its execution for a period of time. During this time, it's not consuming CPU time, so the CPU can be executing other tasks.

When Thread is sleeping and is interrupted, the method throws an InterruptedException immediately and doesn't wait until the sleeping time finishes.

Waiting for the finalization of a thread

We may have a program that will begin initializing the resources it needs before proceeding with the rest of the execution. We can run the initializaion tasks as threads and wait for its finialization before continuing with the rest of the program.

For this purpose, we can use the join() method of the Thread class. When we call this method using a thread object, it suspends the execution of the calling thread until the object called finishes its execution.

Instead of waiting indefinitely for the finalization of the thread called, the calling thread waits for the milliseconds specified as a parameter of the method.

join(long milliseconds)

join(long milliseconds, long nanos)

For example, if the object thread1 has the code, thread2.join(1000), the thread thread1 suspends its execution until one of these two conditions is true:

  • thread2 finishes its execution
  • 1000 milliseconds have been passed

Creating and running a daemon thread

Deamon thread: has very low priority and normally only executes when no other thread of the same program is running. When daemon threads are the only threads running in a program, the JVM ends the program finishing thses thread.

They can't do important jobs because we don't know when they are going to have CPU time and they can finish any time if there aren't any other threads running. A typical example of these kind of threads is the Java garbage collector.

You only can call the setDaemon() method before you call the start() method. Once the thread is running, you can't modify its daemon status.

Processing uncontrolled exceptions in a thread

When a checked exception is thrown inside the run() method of a Thread object, we have to catch and treat them, because the run() method doesn't accept a throws clause.

When an unchecked exception is thrown inside the run() method of a Thread object, the default behavior is to write the stack trace in the console and exit the program.

When an exception is thrown in a thread and is not caught, the JVM checks if the thread has an uncaught execption handler set by the corresponding method. If it has, the JVM invokes this method with the Thread object and Exception as arguments.

If the thread has not got an uncaught exception handler, the JVM prints the statck trace in the console and exits the program.

Using local thread variables

If you create an object of a class that implements the Runnable interface and then start various Thread objects using the same Runnable object, all the threads share the same attributes. This meanns that, if you change an attribute in a thread, all the threads will be affected by this change.

Thread-local variables: sometimes, you will be interested in having an attribute that won't be shared between all the threads that run the same object.

private static ThreadLocal<Date> startDate = new ThreadLocal<Date>() {
   protected Date initialValue() {
       return new Date();
   }
};

Thread-local variables store a value of an attribute for each Thread that uses one of these variables. You can read the value using the get() method and changes the value using the set() method. The first time you access the value of a thread-local variable, if it has no value for the Thread object that it is calling, the thread-local variable calls the initialValue() method to assign a value for that Thread and returns the inital value.

The thread-local class provides the remove() method that deletes the value stored in the thread-local variable for the thread that it's calling.

If a thread A has a value in a thread-local variable and it creates another thread B, the thread B will have the same value as the thread A in the thread-local varaible. You can override the childValue() method that is called to initalize the value of the child thread in the thread-local variable.

Grouping threads into a group

threat the threads of a group as a single unit and provides access to the Thread objects that belong to a group to do an operation with them.

For example, you have some threads doing the same task and you want to control them, irrespective of how many threads are still running, the status of each one will interrupt all of them with a single call.

Java provides the ThreadGroup class to work with groups of threads. A ThreadGroup object can be formed by Thread objects and by another ThreadGroup object, generating a tree structure of threads.

Processing uncontrolled exceptions in a group of threads

Java implements an exception-based mechanism to manage error situations. Those exceptions are thrown by the Java classes when an error situation is detected.

Java also provides a mechanism to capture and process those exceptions. There are exceptions that must be captured or re-thrown using the throws clause of a method. These exceptions are called checked exceptions. Those not are unchecked exceptions.

When an uncaught exception is thrown in Thread, the JVM looks for three possible handlers for this exception.

  • it looks for the uncaught exception handler of the thread
  • If this handler doesn't exist, then the JVM looks for the uncaught exception handler for the ThreadGroup class of the thread
  • If this method doesn't exist, the JVM looks for the default uncaught exception handler

Creating threads through a factory

With this factory, we centralize the creation of objects with some advantages:

  • It's easy to change the class of the objects created or the way we create these objects
  • It's easy to limit the creation of objects for limited resources. For example, we can only have n objects of a type
  • It's easy to generate statistical data about the creation of the objets

Java provides an interface, the ThreadFactory interfaces to implement a Thread object factory.

The ThreadFactory interface has only one methods called newThread. Most basic ThreadFactory has only one line:

return new Thread(r);

You can improve this implementation by adding some variants by:

  • Creating personalized threads, using a special format for the name or even creating our own thread class that inherits the Java Thread class
  • Saving thread creation statistics
  • Limiting the number of threads created
  • Validating the creation of the threads
  • ...
2016/4/6 posted in  Java7 Concurrency Cookbook

Javac编译原理

如何才能编译程序呢?

  • 首先,要读取源代码,一个字节一个字节地读进来,找出这些字节中哪些是我们定义的语法关键词。
    • 词法分析的结果就是从源码中找到一些规范化的Token流,就像人类语言中,给你一句话,你要能分辨出哪些是一个词语,哪些是标点符号
  • 接着,就是对这些Token流进行语法分析,检查这些关键词组合在一起是不是符合Java语言规范
    • 语法分析的结果就是形成一个符合Java语言规范的抽象语法树
  • 接下来是语义分析,判断语义是不是正确。主要工作是把一些难懂的、复杂的语法转化成更加简单的语法。
  • 最后,通过字节码生成器生成字节码,将会根据经过注解的抽象语法树生成字节码,就像将所有的中文词语翻译成英文单词后,按照英文语法组装成英文语句。

Javac工作原理分析

词法分析器

public class Cifa {
    int a;
    int c = a + 1;
}

Javac的主要词法分析器的默认实现类是com.sun.tools.javac.parser.Scanner,Scanner会逐个读取Java源文件的单个字符,然后解析出符合Java语言规范的Token序列

JavacParser规定了哪些词是符合Java语言规范规定的词具体读取和归类不同语法的操作由Scanner完成。Token规定了所有Java语言的合法关键词,Names用来存储和表示解析后的词法。

词法分析过程是在JavaParser的parseCompilationUnit方法中完成的。

从源文件的第一个字符开始,按照Java语法规范依次找出package、import、类定义,以及属性和方法定义等,最后构建一个抽象语法树。

词法分析器的分析结果就是将这个类中的所有关键词匹配到Token类的所有项中的任何一项。

语法分析器

词法分析器的作用就是将Java源文件的字符流转变成对应的Token流。而词法分析器是将词法分析器分析的Token流组建成更加结构化的语法树,也就是将一个个单词组装成一句话,一个完整的语句。哪些词语组合在一起是主语、哪些是谓语,要做进一步区分。

Javac的语法树使得Java源码更加结构化,这种结构化可以为后面的进一步处理提供方便。每个语法树上的节点都是com.sun.tools.javac.tree.JCTree的一个实例

JCTree类中有如下三个重要的属性项:

  • Tree tag: 每个语法节点都会用一个整形常数表示,并且每个节点类型的数值是在前一个的基础上加1。顶层节点TOPLEVEL是1,而IMPORT节点等于TOPLEVEL加1,等于2
  • pos:也是一个整数,它存储的是这个语法节点在源代码中的起始位置,一个文件的一个位置是0,而-1表示一个不存在的位置
  • type:表示的是这个节点是什么Java类型,如int、float等

下面以Yufa类为例,看看它的最后的语法树是什么:

public class Yufa {
    int a;
    private int c = a + 1;
    
    public int getC() {
        return c;
    }
    
    public void setC(int c) {
        this.c = c;
    }
}

这段代码对应的语法树是

语义分析器

前面介绍了将一个Java源文件先解析成一个一个的Token流,然后再经过语法分析器将Token流解析成更加结构化、可操作的一颗语法树。

将Java类中的符号输入到符号表中

主要由com.sun.tools.javac.comp.Enter类完成,这个类主要完成以下两个步骤。

  • 将所有类中出现的符号输入到类本身的符号表中,所有类符号、类的参数类型符号(泛型参数类型)、超类符号和继承的接口类型符号等都存储到一个未处理列表中
  • 将这个未处理列表中所有的类都解析到各自的类符号列表中

首先一个类中处理类本身会定义一些符号变量外,如类名称、变量名称和方法名称等,还有一些符号是引用其他类的,符号调用其他类的方法或者变量等,还有一些这个类可能会继承或者实现超类和接口等。这些符号都是在其他类中定义的。

第二个步骤就是按照递归向下的顺序解析语法树,将所有的符号都输入符号表中。

在Enter类解析这一步骤中,还有一个重要的步骤是添加默认的构造函数。

处理annotation

这个步骤是由com.sun.tools.javac.processing.JavaProcessingEnvironment类完成的。

标注

这个步骤最重要的是检查语义的合法性和进行逻辑判断,如:

  • 变量的类型是否匹配
  • 变量在使用前是否已经初始化
  • 能够推导出泛型方法的参数类型
  • 字符串常量的合并

还有一些类来协助

  • 检查语法树中的变量类型是否正确,如方法返回的类型是否与接收的引用值类型匹配
  • 检查变量、方法或者类的访问是否合法、变量是否是静态变量、变量是否已经初始化
  • 常量折叠,这里主要针对字符串常量,会将一个字符串常量中的多个字符串合并成一个字符串
  • 帮助推导泛型方法的参数类型等
public class Yufa {
    int a = 0;
    private int c = a + 1;
    private int d = 1 + 1;
    private String s = "hello " + "world";
}

经过Attr解析后,这个源码会变成如下:

public class Yufa {

    public Yufa() {
        super();
    }
    int a = 0;
    private int c = a + 1;
    private int d = 1 + 1;
    private String s = "hello world";
}

字符串s由两个"hello "和"world"合并成一个字符串

数据流分析

标注完成后就是com.sun.tools.javac.comp.Flow类完成数据流分析

  • 检查变量在使用前是否都已经正确赋值
  • 保证final修饰的变量不会被重复赋值
  • 方法的返回值类型都要确定
  • 所有Checked Exception都要捕获或者向上抛出
  • 所有的语句都要被执行到。这里会检查是否有语句出现在一个方法return的后面
进一步度对语法树进行语义分析

语义分析的最后一个步骤是执行com.sun.tools.javac.comp.Flow,这一步是进一步堆语法树进行语义分析,如消除一些无用的代码,永不真的条件判断将会去除。还有就是解除一些语法糖,如将foreach这种语法解析成标准的for循环形式。

  • 去掉无用的代码,如假的if代码块。
  • 变量的自动转换,如将int自动包装成Integer类型或者相反的操作等
  • 去除语法糖,将foreach的形式转化成更简单的for循环

代码生成器

接下来Javac会调用com.sun.tools.javac.jvm.Gen类遍历语法树生成最终的Java字节码

生成Java字节码需要经过两个步骤:

  • 将Java方法中的代码块转成符合JVM语法的命令形式,JVM的操作都是基于栈的,所有的操作都必须经过出栈和进栈来完成
  • 按照JVM的文件组织格式将字节码输出到以class为扩展名的文件中
public class Daima {
    public static void main(String[] args) {
        int rt = add(1, 2);
    }
    
    public static int add(Integer a, Integer b) {
        return a + b;
    }
}

这段代码调用一个函数,这个函数的作用就是将两个int类型的参数相加,然后将相加的结果返回给调用者。

加法表达式:必须先将两个操作数a和b放到操作栈,然后再利用加法操作符执行加法操作,将加法的结果放到当前栈的栈顶,最后将这个结果返回给调用者。

这个过程可以用如下方式描述:

  • 先计算左表达式结果,将左表达式结果转化成int类型
  • 将这个结果放入当前栈中
  • 再计算右表达式结果,将右表达式结果转化成int类型
  • 将这个结果放入当前栈中
  • 弹出‘+’操作符
  • 将操作结果置于当前栈栈顶

设计模式解析之访问者模式

访问者模式的结构

这种模式的基本想法如下:

  • 首先我们拥有一个由许多对象构成的对象结构,这些对象的类都拥有一个accept方法用来接受访问者对象
  • 访问者是一个接口,它拥有一个visit方法,这个方法对访问到的对象结构中不同类型的元素作出不同的反应
  • 在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施accept方法,在每一个元素的accept方法中回调访问者的visiti方法,从而使访问者得以处理对象结构的每一个元素。
  • 我们可以针对对象结构设计不同的实在的访问者类完成不同的操作。

  • 抽象访问者(Visitor):声明所有访问者需要的接口
  • 具体访问者(Concrete Visitor):实现抽象访问者声明的接口
  • 抽象节点元素(Element):提供一个接口能够接受访问者作为参数传递给节点元素
  • 具体节点元素(ConcreteElement):实现抽象节点元素声明的接口
  • 结构对象(Object Structure):提供一个接口能够访问到所有的节点元素,一般作为一个集合特有节点元素的引用
  • 客户端(Client):分别创建访问者和节点元素的对象,调用访问者访问变量节点元素
interface Visitor {
     void visit(Wheel wheel);
     void visit(Engine engine);
     void visit(Body body);
     void visit(Car car);
 }

 class Wheel {
     private String name;
     Wheel(String name) {
         this.name = name;
     }
     String getName() {
         return this.name;
     }
     void accept(Visitor visitor) {
         visitor.visit(this);
     }
 }
  
 class Engine {
     void accept(Visitor visitor) {
         visitor.visit(this);
     }
 }

 class Body {
     void accept(Visitor visitor) {
         visitor.visit(this);
     }
 }

 class Car {
     private Engine  engine = new Engine();
     private Body    body   = new Body();
     private Wheel[] wheels 
         = { new Wheel("front left"), new Wheel("front right"),
             new Wheel("back left") , new Wheel("back right")  };
     void accept(Visitor visitor) {
         visitor.visit(this);
         engine.accept(visitor);
         body.accept(visitor);
         for (int i = 0; i < wheels.length; ++ i)
             wheels[i].accept(visitor);
     }
 }

 class PrintVisitor implements Visitor {
     public void visit(Wheel wheel) {
         System.out.println("Visiting " + wheel.getName()
                             + " wheel");
     }
     public void visit(Engine engine) {
         System.out.println("Visiting engine");
     }
     public void visit(Body body) {
         System.out.println("Visiting body");
     }
     public void visit(Car car) {
         System.out.println("Visiting car");
     }
 }

 public class VisitorDemo {
     static public void main(String[] args) {
         Car car = new Car();
         Visitor visitor = new PrintVisitor();
         car.accept(visitor);
     }
 }
2016/4/6 posted in  深入分析Java Web技术内幕

秒杀系统架构分析与实战

秒杀技术挑战

对现有网站业务造成冲击

将秒杀系统独立部署,甚至使用独立域名,使其与网站完全隔离。

高并发下的应用、数据库负载

重新设计秒杀商品页面,不使用网站原来的商品详细页面,页面内容静态话,用户请求不需要经过应用服务。

突然增加的网络及服务器带宽

因为秒杀新增的网络带宽,必须和运营商重新购买或者租借。为了减轻网站服务器的压力,需要将秒杀商品页面缓存在CDN,同样需要和CDN服务商临时租借新增的出口带宽。

直接下单

下单页面也是一个普通的URL,如果得到这个URL,不用等到秒杀开始就可以下单了。

需要将该URL动态化,即使秒杀系统的开发者也无法在秒杀开始前访问下单页面的URL。方法是在下单页面URL加入由服务器端生成的随机数作为参数,在秒杀开始的时候才能得到。

如果控制秒杀商品页面购买按钮的点亮

在秒杀商品静态页面中加入一个JavaScript文件引用,该JavaScript文件中包含秒杀开始与否标志。这个JavaScript文件非常小,即使每次浏览器刷新都访问JavaScript文件服务器,也不会对服务器集群和网络带宽造成太大压力。

如果只允许第一个提交的订单被发送到订单子系统

假设下单服务器集群有10台服务器,每台服务器只接受最多10个下单请求。

在还没有人提交订单成功之前,如果一个服务器已经有10单了,而有的一单都没处理,可能出现的用户体验不佳的场景是用户第一次点击购买按钮进入已结束页面,再刷新一下页面,有可能被一单都没有处理的服务器处理,进入了填写订单的页面,可以考虑通过cookie的方式来应对,符合一致性原则。当然可以采用最少连接的负载均衡算法,出现上述情况的概率大大降低。

如何进行下单前置检查

  • 下单服务器检查本机已处理的下单请求数目

如果超过10条,直接返回已结束页面给用户

如果未超过10条,则用户可进入填写订单及确认页面

  • 检查全局已提交订单数目

已超过秒杀商品总数,返回已结束页面给用户

未超过秒杀商品总数,提交到子订单系统

秒杀一般是定时上架

减库存的操作

有两种选择,一种是拍下减库存;另一种是付款减库存

库存会带来‘超卖’的问题

采用乐观锁

UPDATE auction_auctions SET
quantity = #inQuantity#
WHERE auction_id = #itemId# and quantity = #dbQuantity#

秒杀器的应对

秒杀器一般下单购买及其迅速,根据购买记录可以甄别出一部分。可以通过校验码达到一定的方法。

秒杀架构原则

尽量将请求拦截在系统上游

读多写少的多使用缓存

秒杀架构设计

  • 秒杀系统的页面设计尽可能简单
  • 购买按钮只有在秒杀活动开始的时候才变亮
  • 下单表单也尽可能简单;只有第一个提交的订单发送给网站的订单子系统,其余用户提交订单后只能看到秒杀结束页面

前端层设计

秒杀页面的展示

各类静态资源首先应分开存放,然后放到CDN节点上分散压力

倒计时

可能出现客户端时钟与服务器时钟不一致,另外服务器之间也是有可能出现时钟不一致。

浏览器层请求拦截

  • 产品层面:用户点击后,按钮置灰
  • JS层面:限制用户在x秒之内只能提交一次请求

站点层设计

  • 同一个uid,限制访问频率:做页面缓存,x秒内到达站点层的请求,均返回同一个页面
  • 同一个item的查询,均返回同一个页面

服务层设计

并发队列的选择

  • ArrayBlockingQueue是初始容量固定的阻塞队列,我们可以用来作为数据库模块成功竞拍的队列。比如有10个商品,那么我们就设定一个大小为10的数组队列。
  • ConcurrentLinkedQueue使用的是CAS无锁队列,是一个异步队列,入队的速度很快,出队进行了加锁,性能稍慢。
  • LinkedBlockingQueue也是阻塞的队列,入队和出队都加了锁,当队空的时候线程会暂时阻塞。

由于我们的系统入队需求要远大于出队需求,一般不会出现队空的情况,所以我们可以选择ConcurrentLinkedQueue来作为我们的请求队列实现。

数据库模块数据库主要是使用一个ArrayBlockingQueue来暂存有可能成功的用户请求。

数据库设计

设计思路

如何保证数据的可用性?

冗余。

如何保证数据库‘读’高可用?

冗余读库

如何保证数据库‘写’高可用?

冗余写库。采用双主互备的方式。

双写同步,数据可能冲突(例如‘自增id’同步冲突),有两种常见解决方案:

  • 两个写库使用不同的初始值,相同的步长来增加id:写库1的id为0,2,,4,6……;写库2的id为1,3,5,7……
  • 不使用数据的id,业务层自己生成唯一的id。保证数据不冲突。

如何扩展读性能

  • 第一种是建立索引:不同的库可以建立不同的索引 - 线上读库建立线上访问索引,例如uid - 线下读库建立线下访问索引,例如time
  • 第二种是增加从库
  • 第三种是增加缓存

如何保证一致性

主从数据库的一致性,通常有两种解决方案:

  • 中间件:如果某一个key有写操作,在不一致时间窗口内,中间件会将这个key的读操作也路由到主库上。
  • 强制读主

DB与缓存间的不一致:有可能“从库读到旧数据,旧数据进入cache”

写操作时顺序升级为:

  • 淘汰cache
  • 写数据库
  • 在经验‘主从同步延时窗口时间’达到了以后,再次发起一个异步淘汰cache的请求。

作弊的手段:

同一个帐号,一次性发出多个请求

在程序入口处,一个帐号只允许接受1个请求,其他请求过滤。

可以通过Redis这种内存缓存服务,写入一个标志位(只允许1个请求写成功),成功写入的则可以继续参加。

或者自己实现一个服务,将同一个帐号的请求放入一个队列中,处理完一个,再处理下一个。

多个帐号,一次性发送多个请求

可以通过检测机器IP请求频率

  • 弹出验证码
  • 直接禁止IP

多个帐号,不同IP发送不同请求

通过帐号行为的‘数据挖掘’来提前清理掉它们。

高并发下的数据安全

悲观锁思路

在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。

FIFO队列思路

强行将多线程变成单线程。

我们直接将请求放入队列中,采用FIFO。

乐观锁思路

这个数据的所有请求都有资格去修改,但是会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。


Reference

http://www.importnew.com/18920.html

2016/4/5 posted in  others