转:java线程安全总结(1)

最近想将java基础的一些东西都整理整理,写下来,这是对知识的总结,也是一种乐趣。已经拟好了提纲,大概分为这几个主题:java线程安全,java垃圾收集,java并发包详细介绍,java profile和jvm性能调优慢慢写吧。本人jameswxx原创文章,转载请注明出处,我费了很多心血,多谢了。关于java线程安全,网上有很多资料,我只想从自己的角度总结对这方面的考虑,有时候写东西是很痛苦的,知道一些东西,但想用文字说清楚,却不是那么容易。我认为要认识java线程安全,必须了解两个主要的点:java的内存模型,java的线程同步机制。特别是内存模型,java的线程同步机制很大程度上都是基于内存模型而设定的。后面我还会写java并发包的文章,详细总结如何利用java并发包编写高效安全的多线程并发程序。暂时写得比较仓促,后面会慢慢补充完善。


浅谈java内存模型
       不同的平台,内存模型是不一样的,但是jvm的内存模型规范是统一的。其实java的多线程并发问题最终都会反映在java的内存模型上,所谓线程安全无非是要控制多个线程对某个资源的有序访问或修改。总结java的内存模型,要解决两个主要的问题:可见性和有序性。我们都知道计算机有高速缓存的存在,处理器并不是每次处理数据都是取内存的。JVM定义了自己的内存模型,屏蔽了底层平台内存管理细节,对于java开发人员,要清楚在jvm内存模型的基础上,如果解决多线程的可见性和有序性。
       那么,何谓可见性?多个线程之间是不能互相传递数据通信的,它们之间的沟通只能通过共享变量来进行。Java内存模型(JMM)规定了jvm有主内存,主内存是多个线程共享的。当new一个对象的时候,也是被分配在主内存中,每个线程都有自己的工作内存,工作内存存储了主存的某些对象的副本,当然线程的工作内存大小是有限制的。当线程操作某个对象时,执行顺序如下:
 (1) 从主存复制变量到当前工作内存 (read and load)
 (2) 执行代码,改变共享变量值 (use and assign)
 (3) 用工作内存数据刷新主存相关内容 (store and write)

JVM规范定义了线程对主存的操作指令:read,load,use,assign,store,write。当一个共享变量在多个线程的工作内存中都有副本时,如果一个线程修改了这个共享变量,那么其他线程应该能够看到这个被修改后的值,这就是多线程的可见性问题。
        那么,什么是有序性呢?线程在引用变量时不能直接从主内存中引用,如果线程工作内存中没有该变量,则会从主内存中拷贝一个副本到工作内存中,这个过程为read-load,完成后线程会引用该副本。当同一线程再度引用该字段时,有可能重新从主存中获取变量副本(read-load-use),也有可能直接引用原来的副本(use),也就是说 read,load,use顺序可以由JVM实现系统决定。
        线程不能直接为主存中中字段赋值,它会将值指定给工作内存中的变量副本(assign),完成后这个变量副本会同步到主存储区(store-write),至于何时同步过去,根据JVM实现系统决定.有该字段,则会从主内存中将该字段赋值到工作内存中,这个过程为read-load,完成后线程会引用该变量副本,当同一线程多次重复对字段赋值时,比如:


Java代码  

  1. for(int i=0;i<10;i++)  
  2.  a++;  

 线程有可能只对工作内存中的副本进行赋值,只到最后一次赋值后才同步到主存储区,所以assign,store,weite顺序可以由JVM实现系统决定。假设有一个共享变量x,线程a执行x=x+1。从上面的描述中可以知道x=x+1并不是一个原子操作,它的执行过程如下:

1 从主存中读取变量x副本到工作内存
2 给x加1
3 将x加1后的值写回主

如果另外一个线程b执行x=x-1,执行过程如下:
1 从主存中读取变量x副本到工作内存
2 给x减1
3 将x减1后的值写回主存

那么显然,最终的x的值是不可靠的。假设x现在为10,线程a加1,线程b减1,从表面上看,似乎最终x还是为10,但是多线程情况下会有这种情况发生:
1:线程a从主存读取x副本到工作内存,工作内存中x值为10
2:线程b从主存读取x副本到工作内存,工作内存中x值为10
3:线程a将工作内存中x加1,工作内存中x值为11
4:线程a将x提交主存中,主存中x为11
5:线程b将工作内存中x值减1,工作内存中x值为9
6:线程b将x提交到中主存中,主存中x为9

阅读剩余部分...

Java基础测试

package cn.com.test;

import static org.junit.Assert.*;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
/**
 * Java基础测试
 */
public class JavaBasicTest {
    /**
     * 字符串对象比较
     * 1.使用字符串字面值直接赋值的方式,内存分配在常量区,只要字面值相同,使用"=="比较时一定相等
     * 2.使用new创建的字符串对象,每次new都会在堆上分配新内存,使用"=="比较时一定不相等,不论字面值是否相等
     * 3.在堆上分配内存的字符串对象和在常量区分配的字符串,使用"=="比较时一定不相等,不论字面值是否相等
     * 4.字符串类和基本类型包装类一样,不提供修改值的方法,值改变通常意味着引用指向的地址也发生了改变
     */
    @Test
    public void testStringEquals() {
        String s = "Hello";
        String s2 = "Hello";
        assertTrue(s == s2);    // [相等]使用直接赋值方式构造的字符串对象占用内存的常量区
       
        String s3 = new String("Hello");
        String s4 = new String("Hello");
        assertFalse(s3 == s4);    // [不相等]使用new方式构造的字符串对象占用内存的堆区,每次都会分配新内存
        assertFalse(s3 == s);    // [不相等]使用new方式构造的字符串对象和使用直接赋值方式构造的字符串对象比较
       
        String s5 = s4;
        assertTrue(s5 == s4);    // [相等]使用另外一个字符串赋值的方式构造,两个引用指向同一个内存地址
        s5 = "Hello";           
        assertFalse(s5 == s4);    // [不相等]s5和s4指向的内存区域不一样,地址肯定也不一样
       
        s4 = "Hello";
        assertTrue(s4 == s5);    // [相等]s4和s5此时都指向内存常量区
    }
   

阅读剩余部分...

javascript contains方法

IE有许多好用的方法,后来都被其他浏览器抄袭了,比如这个contains方法。如果A元素包含B元素,则返回true,否则false。唯一不支持这个方法的是IE的死对头firefox。
<script type="text/javascript">
  window.onload = function(){
    var A = document.getElementById('parent'),
    B = document.getElementById('child');
    alert(A.contains(B));
    alert(B.contains(A));
  }
</script>
<h2 style="text-align:center">contains方法</h2>

<div id="parent">
  <p>
    <strong id="child" >本例子会在火狐中会报错。</strong>
  </p>
</div>
不过火狐支持compareDocumentPosition() 方法,这是W3C制定的方法,标准浏览器都支持,不过实用性性很差,因此没有什么人用,推广不开来。它的使用形式与contains差不多,但返回的不是一个布尔值,而是一个很奇怪的数值,它是通过如下方式累加计算出来的:

000000 0 元素一致
000001 1 节点在不同的文档(或者一个在文档之外)
000010 2 节点 B 在节点 A 之前
000100 4 节点 A 在节点 B 之前
001000 8 节点 B 包含节点 A
010000 节点 A 包含节点 B
100000 32 浏览器的私有使用

-

阅读剩余部分...

转:深入浅出异步I/O模型

从上篇文章的介绍我们知道linux内核根据TCP/IP网络模型,给我们隐藏了传输层以下的网络传输细节,我们的网络应用程序只需要针对socket编程即可。这篇我们立足网络数据包的I/O。谈谈linux的一些I/O知识,以及Java的NIO.
 1.  基础知识

      我们知道Linux的内核将所有外部设备都可以看做一个文件来操作。那么我们对与外部设备的操作都可以看做对文件进行操作。我们对一个文件的读写,都通过调用内核提供的系统调用;内核给我们返回一个file descriptor(简称:fd,文件描述符);我们通过 ls -l  /proc/${pid}/fd/ 可以看到进程${pid}占用的所有描述符,或者lsof -p ${pid}; 而对一个socket的读写也会有相应的描述符,称为socketfd(socket描述符);描述符就是一个数字,指向内核中一个结构体(文件路径,数据区,等一些属性) ; 那么我们的应用程序对文件的读写就通过对描述符的读写完成。

     系统调用是如何完成一个I/O操作的呢? linux将内存分为内核区,用户区; linux内核给我们管理所有的硬件资源,应用程序通过调用系统调用和内核交互,达到使用硬件资源的目的; 应用程序通过系统调用read发起一个读操作;这时候内核创建一个文件描述符,并通过驱动程序向硬件发送读指令,并将读的的数据放在这个描述符对应结构体的缓存区。但这个结构体是在内核内存区的。需要将这个数据读到用户区。这样完成了一次读操作;

     但是大家都知道I/O设备相比cpu的速度是极慢的。linux提供的read系统调用,也是一个阻塞函数。这样我们的应用进程在发起read系统调用时,就必须阻塞,就进程被挂起而等待文件描述符的读就绪;

      这里,我们先了解一下,什么是文件描述符读就绪,什么是写就绪?

       读就绪:就是这个文件描述符的接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水位标记的当前大小;

       写就绪:该描述符发送缓冲区的可用空间字节数大于等于描述符发送缓冲区低水位标记的当前大小。(如果是socket fd,说明上一个数据已经发送完成)。

       接收低水位标记和发送低水位标记:由应用程序指定,比如应用程序指定接收低水位为64个字节。那么接收缓冲区有64个字节,才算fd读就绪;

2.各种I/O模型比较

    有没有办法能让我们在I/O时,不让我们的应用程序阻塞;从上边的分析我们知道向内核发起一个I/O操作,要经过等待fd就绪+内核数据到用户数据区复制,完成一次I/O;

    Linux POSIX是这样定义同步I/O 和 异步I/O的:

  •    同步I/O操作(synchronous I/O operation):导致请求进程阻塞,直到I/O操作完成。
  •    异步I/O操作(asynchronous I/O operation): 不导致请求进程阻塞。

     根据上述定义,我们的前四种模型------阻塞式I/O模型,非阻塞式I/O模型、I/O多路复用模型和信号驱动式I/O模型,因为其中真正的I/O操作将阻塞进程。只有异步I/O模型与POSIX定义的异步I/O相匹配;

 

 

                                  图: Linux 提供的所有I/O模型

    阻塞式:最普通的I/O模型;原生的read/write系统调用,默认是阻塞模式;导致进程阻塞;

    非阻塞:这种方式通过指定系统调用read/write的参数为非阻塞,告知内核fd没就绪时,不阻塞进程,而是返回一个错误码,应用进程死循环轮询,直到fd就绪;

    异步非阻塞(I/O复用):linux提供select/poll,进程通过将一个或多个fd传递给select或poll系统调用,阻塞在select;这样select/poll可以帮我们侦测许多fd是否就绪;但是select/poll是顺序扫描fd是否就绪,而且支持的fd数量有限。linux还提供了一个epoll系统调用,epoll是基于事件驱动方式,而不是顺序扫描,当有fd就绪时,立即回调函数rollback;

    异步非阻塞(信号驱动式I/O):内核在描述符就绪时发送SIGIO信号通知进程,进程通过信号处理函数接收数据;

    异步I/O(AIO):  告知内核某个操作,并让内核在整个操作(包括将数据复制到我们的进程缓冲区)完成后通知我们。这种模型和信号驱动式I/O模型区别在于:信号驱动式I/O由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是内核通知我们I/O操作何时完成。(此模型linux 2.6 内核推出)

 

3. java的NIO(new i/o)

    我们知道jdk 1.4版本里推出了java nio .此后java的很多网络应用都重写了底层I/O模块,大大提高并发性能;包括tomcat, jetty等;

     java nio api 里通过将许多fd扔给一个Selector去检测fd是否就绪;

     那么java 的nio使用的是那种I/O模型呢?

     通过查看jvm代码:(下载地址:http://download.java.net/jdk6/source/

 

    可见jvm的NIO使用的是linux 系统调用epoll模型;

 

4. java何时支持真正的AIO模型呢?

    JSR 203(http://jcp.org/en/jsr/detail?id=203) 在java SE 7.0中会完成JSR203.估计不久以后就可以普及了。asynchronous I/O对于java 绝对影响巨大,java写的网络服务器能够支持更大并发请求了。到时肯定大多网络服务器的I/O底层代码都会修改。就像当时Linux 2.6 支持AIO模型,很多数据库oracle,DB2都发布新版本。

http://www.iteye.com/topic/868702

Push or pull?

无论是消息系统,还是配置管理中心,甚至存储系统,你都要面临这样一个选择,push模型 or pull模型?是服务端主动给客户端推送数据,还是客户端去服务器拉数据,一张图表对比如下:
 
push模型 pull模型
描述 服务端主动发送数据给客户端 客户端主动从服务端拉取数据,通常客户端会定时拉取
实时性 较好,收到数据后可立即发送给客户端 一般,取决于pull的间隔时间
服务端状态 需要保存push状态,哪些客户端已经发送成功,哪些发送失败 服务端无状态
 客户端状态  无需额外保存状态 需保存当前拉取的信息的状态,以便在故障或者重启的时候恢复
状态保存 集中式,集中在服务端 分布式,分散在各个客户端
负载均衡 服务端统一处理和控制 客户端之间做分配,需要协调机制,如使用zookeeper
其他

服务端需要做流量控制,无法最大化客户端的处理能力。

其次,在客户端故障情况下,无效的push对服务端有一定负载。

客户端的请求可能很多无效或者没有数据可供传输,浪费带宽和服务器处理能力
缺点方案 服务器端的状态存储是个难点,可以将这些状态转移到DB或者key-value存储,来减轻server压力。

针对实时性的问题,可以将push加入进来,push小数据的通知信息,让客户端再来主动pull。

针对无效请求的问题,可以设置逐渐延长间隔时间的策略,以及合理设计协议尽量缩小请求数据包来节省带宽。



在面对大量甚至海量客户端的时候,使用push模型,保存大量的状态信息是个沉重的负担,加上复制N份数据分发的压力,也会使得实时性这唯一的优点也被放小。使用pull模型,通过将客户端状态保存在客户端,大大减轻了服务器端压力,通过客户端自身做流量控制也更容易,更能发挥客户端的处理能力,但是需要面对如何在这些客户端之间做协调的难题。

web安全普及(1)-XSS与CSRF攻击的原理和演示

      数据安全是任何一款软件设计都中都需要考虑的问题。从技术层面讲,数据安全就是保护你存储和使用的数据不被窃听、盗取和破坏,这可能是有外部因素造成的,比如由于过滤不严格造成的SQL注入漏洞,提升脚本执行权限等,也有可能是有代码内部的设计造成的,如死循环,低效率的语句造成的服务器性能下降以致影响访问。社会学意义上的数据安全则更广泛。比如,在一个在线购物商城的设计中,由于设计者错误地使用了自增ID做为商品的单据流水号,竞争对手或有心人很容易地就能分析出这个商城的每日销售量,进而估算出销售额,利润等商业机密数据。纵然,在这个层次上的安全没有很明显的破坏力,但是仍然值得注意。
    在程序中要保证数据的安全,除了要保证代码内部的可靠外,最主要的就是严格处理外部数据,即“一切输入输出都是不可靠的”,这就要求我们做好数据过滤和验证。
    在php的编程中,最简单的过滤机制就是转义,即对用户的输入和输出进行转义和过滤。为什么需要进行这步处理呢。很多人都这样告诉我们,一定要注意过滤数据,注意转义,否则网站是不安全的。可是他们却没有告诉我们为什么要这么做,到底怎么个不安全,能不能让我直观地看到不安全在什么地方?现在我们就来看两个例子。下面是一个简单的留言本的例子。
2011-06-24_024326.png
在程序中,没有任何的输入输出过滤,当我如图输入上面的数据时,得到了如下的页面:
   2011-06-24_024819.png
很显然,由于我们的输入含有CSS代码和JAVASCIPRT代码,但是没有对其进行处理,所以原样输出,导致页面被篡改。这就是我们常听到的“XSS”攻击。
    不过滤数据,还会造成SQL语句报错,数据库被注入等,所以数据的过滤是必须要重点考虑的。
    我们再来看看,不过滤数据还会带来什么后果。下面是一个超市管理系统的一个功能模块,管理员登陆后,点击可以删除商品。
2011-06-28_001307.png
当然,我们在后台有权限控制,只有管理员登陆才能删除商品,但是这样真的就足够安全吗?我们试一下,我们以一个普通会员的身份提交此删除链接。
    2011-06-28_003829.png
    嗯,被权限管理系统拦截,可是这是难不住攻击者的,攻击者在前端页面输入下面的恶意代码
    2011-06-28_004017.png
    用户在订货页面输入了一段html代码,其图片地址指向的就是删除链接。攻击者想干什么?现在我们还不清楚。管理员登陆后台,查看会员的订货单。
    2011-06-28_005412.png
    备注栏成了图片了,然而图片地址不是真的地址,而是一段删除商品的链接,其在数据库中的真实数据是“<img src=cg.php?ac=menu&do=delcp&todel=149&pages=13 >”。管理员玩玩没有想到,下面的一幕在偷偷上演:
    2011-06-28_005727.png
    是的,这个“图片”地址被请求了,这就意味着这个删除链接得到了执行。此时管理员仍然是不知不觉,只是当他打开清单管理时,傻眼了,
    2011-06-28_010350.png
       M01号商品,真的被不知不觉地删除了!这是为什么呢?原来用户本来是没有删除商品权限的,但是用户通过“借刀杀人”,盗用了管理员的权限,执行了这一操作。管理员权限是无限大的,如果用户输入的一个后台清空所有数据的链接呢?后果不堪设想!这就是传说中的跨站cookies攻击,曾经不少网站遭其毒手。怎么防止这种攻击,第一当然是不让用户轻易猜到后台管理链接,第二就是转义,过滤用户数据。
    现在我们应该能清楚地了解到不进行数据过滤的恶果了吧。
    针对上面的情况,我们只需在接收数据时,使用htmlspecialchars函数,把代码中的特殊字符转为HTML实体,这样在输出的时候,就不会使页面受影响。这些特殊字符主要是“"”,“'”,“&”,“<”,“>”。比如把“<script>alert(1);</script>”转为“&lt;script&gt;alert(1);&lt;/script&gt;”,这样就可以阻止这类攻击了。
     无需多言,知道了攻,才能更好的受。第一篇到此结束。更多内容,请关注白菜版《php指南》.

一个很有趣的东西,人物图像过滤,模拟绿坝

Java版人物图像过滤。直接上图,太有喜感了。
2011-06-26_023914.png2011-06-26_023836.png
过滤的不是很好,把老毛的脸都给过滤了,然后黑妹由于太黑,即使裸着上半身,都判断不出来。。。无法过滤。
看来针对黑人还得是另外一套算法,囧。
部分代码:
public class FleshDetector {

    /**
     * 截获脸部(肉色)特征区域
     *
     * @param src
     * @return
     */
    public static PallDetection detectFaces(final BufferedImage src,
            final int type) {
        // 转为黑白两色图
        BufferedImage bin = getFleshBinaryImage(src);
        // 扩大白色区域
        BufferedImage dil = Alteration.dilate(bin, type);
        // 取得肌肤颜色
        PallDetection bd = detectWhite(dil);
        return bd;
    }

    /**
     * 截获白色特征区域
     *
     * @param src
     * @return
     */
    public static PallDetection detectWhite(final BufferedImage src) {
        // 设定默认矫正值
        Pall.MAX_NBLINE = 4000;
        PallDetection.MAX_NUMBER = 1000;
        PallDetection pall = new PallDetection(src.getWidth(), src.getHeight());
        pall.setPosDiscrimination(false);
        pall.setThreshold(0.38f);
        int[] pixels = src.getRGB(0, 0, src.getWidth(), src.getHeight(), null,
                0, src.getWidth());
        // 利用象素点获得适当反色遮挡位置
        pall.computePalls(pixels);
        return pall;
    }

    /**
     * 返回黑白二色图
     *
     * @param src
     * @return
     */
    public static BufferedImage getFleshBinaryImage(final BufferedImage src) {
        BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(),
                BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < src.getWidth(); x++) {
            for (int y = 0; y < src.getHeight(); y++) {
                Color c = new Color(src.getRGB(x, y));
                if (isFlesh(c)) {
                    dst.setRGB(x, y, Color.WHITE.getRGB());
                } else {
                    dst.setRGB(x, y, Color.BLACK.getRGB());
                }
            }
        }
        return dst;
    }

    /**
     * 检查是否为肉色
     *
     * @param c
     * @return
     */
    private static boolean isFlesh(final Color c) {
        if ((c.getRed() > 230) && (c.getGreen() > 170)&&(c.getBlue() > 190)) {
            return false;
        }
        LDialyzer yuv = LDialyzer.getYuv(c.getRed(), c.getGreen(), c.getBlue());
        return ((c.getRed() > 40) && (c.getGreen() > 40) && (yuv.y + 16 > 145)
                && (yuv.v + 128 < 173) && (yuv.v + 128 > 133)
                && (yuv.u + 128 < 127) && (yuv.u + 128 > 77));
    }
}

通过代码来实现网页截图

    有时候,我们需要用代码来网页截图,其实要实现这个功能,无非就是要么实现一个仿真浏览器,要么调用系统浏览器,唯有此而发而已。这里还是用的最常见的第二种,第一种难度很大。在网上找了一个库,很好用,记录下来,仅供有需要的同学参考。
下载地址:http://code.google.com/p/greenvm/downloads/detail?name=Screenshot.7z&can=2&q=
    Screenshot就是这样的一个程序,这是一个以DJNativeSwing于系统后台调用浏览器,产生指定网页地址截图的示例。
部分核心代码如下:
public Main(final String url, final int maxWidth, final int maxHeight) {
        super(new BorderLayout());
        JPanel webBrowserPanel = new JPanel(new BorderLayout());
        final String fileName = System.currentTimeMillis() + ".jpg";
        final JWebBrowser webBrowser = new JWebBrowser(null);
        webBrowser.setBarsVisible(false);
        webBrowser.navigate(url);
        webBrowserPanel.add(webBrowser, BorderLayout.CENTER);
        add(webBrowserPanel, BorderLayout.CENTER);

        JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 4, 4));

        webBrowser.addWebBrowserListener(new WebBrowserAdapter() {

            // 监听加载进度
            public void loadingProgressChanged(WebBrowserEvent e) {
                // 当加载完毕时
                if (e.getWebBrowser().getLoadingProgress() == 100) {
                    String result = (String) webBrowser
                            .executeJavascriptWithResult(jsDimension.toString());
                    int index = result == null ? -1 : result.indexOf(":");
                    NativeComponent nativeComponent = webBrowser
                            .getNativeComponent();
                    Dimension originalSize = nativeComponent.getSize();
                    Dimension imageSize = new Dimension(Integer.parseInt(result
                            .substring(0, index)), Integer.parseInt(result
                            .substring(index + 1)));
                    imageSize.width = Math.max(originalSize.width,
                            imageSize.width + 50);
                    imageSize.height = Math.max(originalSize.height,
                            imageSize.height + 50);
                    nativeComponent.setSize(imageSize);
                    BufferedImage image = new BufferedImage(imageSize.width,
                            imageSize.height, BufferedImage.TYPE_INT_RGB);
                    nativeComponent.paintComponent(image);
                    nativeComponent.setSize(originalSize);
                    // 当网页超出目标大小时
                    if (imageSize.width > maxWidth
                            || imageSize.height > maxHeight) {
                        //截图部分图形
                        image = image.getSubimage(0, 0, maxWidth, maxHeight);
                        /*此部分为使用缩略图
                        int width = image.getWidth(), height = image
                            .getHeight();
                         AffineTransform tx = new AffineTransform();
                        tx.scale((double) maxWidth / width, (double) maxHeight
                                / height);
                        AffineTransformOp op = new AffineTransformOp(tx,
                                AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
                        //缩小
                        image = op.filter(image, null);*/
                    }
                    try {
                        // 输出图像
                        ImageIO.write(image, "jpg", new File(fileName));
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                    // 退出操作
                    System.exit(0);
                }
            }
        }

        );
        add(panel, BorderLayout.SOUTH);

    }
下面是我截的优酷的图,效果还是比较满意的。
1309025157547.jpg
    Page :
  1. 1
  2. ...
  3. 7
  4. 8
  5. 9
  6. 10
  7. 11
  8. 12
  9. 13
  10. ...
  11. 38