精华内容
下载资源
问答
  • 一个Oracle Applications DBA(Oracle应用程序数据库管理员)不仅需要和其他DBA一样去负责managing、 sizing、maintaining和 tuning database这些日常的数据库管理的工作,如果他的Apps database是OLTP系统的话,他...
  • Sam R.Alapati是世界顶尖的Oracle技术专家,一位经验丰富的数据库管理员,有20多年从业经历。他拥有Oracle OCP DBA证书和HP UNIX System Administrator证书,曾经担任Oracle公司的高级顾问,并在AT&T、雷曼兄弟、...
  • Key-Value存储作为NoSQL存储一种常见方式,提供了比SQL数据库好的可扩展性和读写性能。比如当前开源最热门Memcached和Redis;淘宝Tair、腾讯Cmem、AmazonDynamo等等,无论是缓存还是持久存储,均使用...
            Key-Value存储作为NoSQL存储的一种常见方式,提供了比SQL数据库更好的可扩展性和读写性能。比如当前开源最热门的Memcached和Redis;淘宝的Tair、腾讯的Cmem、Amazon的Dynamo等等,无论是做缓存还是持久存储,均使用内存作为主要存储介质,故内存管理策略就显得尤为重要了,是影响性能的重要因素。
            这里从源代码层面对Memcached、Redis和UDC(腾讯以前用的一套KV持久化存储系统)的内存管理策略进行分析,3者的内存管理策略各不相同,其他KV系统也和这3种方法大同小异了。最后对这3种策略进行了实际的性能测试分析,有出入的请使劲拍砖!

    --------------------------------------------------------------------------

    1 MemCached:Slab Allocation机制

    3个主要的概念:
    1 Chunk块:固定大小的数据块,数据存储的基本单位,1个Chunk存1个数据,剩余空间不做其他用途
    2 Slab页:固定大小的内存块(页),申请内存的基本单位,默认为1MB,每个SlabClass会把申请的Slab切分成相同大小Chunk来存数据
    3 SlabClass:由Chunk的大小确定,是大小相同的所有Chunk块集合

    如上图所示,Memcahed启动时,会根据传入的-n(最小数据尺寸,默认48B),-f(增长因子,默认为1.25)启动选项初始化SlabClass。默认情况下,首个SlabClass的Chunk大小为80B(32B元数据头+48B最小数据尺寸),然后以1.25为比值生成等比数列,直到1MB(1个Slab页大小)为止。生成的SlabClass如下所示(perslab值为每个Slab页能分割出的Chunk个数):
    $ memcached -vv
    slab class 1: chunk size 80 perslab 13107
    slab class 2: chunk size 104 perslab 10082
    slab class 3: chunk size 136 perslab 7710
    .....
    slab class 41: chunk size 717184 perslab 1
    slab class 42: chunk size 1048576 perslab 1

    当用户发来请求时,Memcahed会根key+value的值(能存放在1个Chunk内)来判断属于哪个SlabClass。确定这个SlabClass有无空闲的Chunk块,没有的话则先给这个SlabClass申请一个Slab页,将该Slab页按SlabClassChunk块大小进行切割,然后分配1个来存放用户数据。(这里还有LRU算法淘汰旧数据的逻辑,就不放在这里分析)。

    这种策略的特点:
    • 实现较复杂
    • 参数的选择(最小数据尺寸,增长因子)直接影响性能及内存利用率
    • 每个数据存放于1个Chunk块,读写简单
    相关源代码:主要由slab.h/c(SlabClassSlab相关)、item.h/c(Chunk块相关)这几个文件实现

    SlabClass数据结构:



    * 初始化SlabClass(通过增长因子计算各个SlabClass的Chunk块大小等)



    * 为SlabClass[i]申请一块新Slab内存页



    ---------------------------------------------------------------------------

    2 Redis:简单mallc/free封装

    相比Memcached,Redis做了一些优化。包括支持数据持久化(AOF和RDB两种持久化方式),支持主从复制读写分离,支持更丰富的数据结构(string、hash、set、list等)。
    然而在内存管理方面,相比于Memcached的Slab Allocation机制,Redis的实现就显得简单粗暴得多了,就只是mallc/free的简单封装(屏蔽底层差异,添加相关元数据等)。

    这种策略的特点:
    • 简单易实现,不易出错
    • 内存的利用率高
    • 大量系统调用开销大
    • 导致内存碎片,加重操作系统内存管理器负担
    相关源代码:zmalloc.h/c这两个文件实现,相对简单

    * 申请内存



    * 释放内存



    ---------------------------------------------------------------------------

    3 UDC:切分Shm成固定长度的块,组织空闲块链和用户数据块链

    UDC是腾讯以前用的一套基于Shm的、全Cache的键值存储系统。和MemCached或Redis使用进程堆内存不一样,UDC使用共享内存来存储数据,一方面进程Core的时候数据还在,一方面利于其他进程做数据落地、同步等操作。



    如上图所示,UDC启动时会申请一块大Shm(LinkTable),然后将这块Shm分割成固定长度的Chunk块(默认200B),通过下标组织成一条空闲块链。当收到添加数据的请求时,从空闲块链中不断取出空闲块存放用户数据,直到存放完成形成一条用户数据块链,链头下标存放于索引层中(基于Shm的多阶Hash)。当收到删除数据的请求时,直接将对应的用户数据块链清零,插入空闲块链中即可。

    这种策略的特点:
    • 实现较复杂
    • 代码不健壮有写乱Shm块链的风险
    • Shm数据块大小的选择直接影响性能及内存利用率
    • 用户数据块链组织成用户数据需多一次拷贝影响性能
    相关源代码:主要由hash_cache.h/c这2个文件实现

    初始化LinkTable



    * Set()操作,添加用户数据



    * Get()操作,获取用户数据



    ---------------------------------------------------------------------------

    性能测试:

    实验前提:
    * 机器:Intel(R) Xeon(R) CPU X3440 @2.53GHz (单线程,这里只用到1核),8G内存
    * key为uint32_t,值为 [1, 50w] 的随机数
    * value为string,串长度为 [50, 500] 之间的随机字节数

    1 不同块大小选择对UDC性能的影响:

    实验方案:
    * 对不同的块大小,进行1000w次读+写操作(即每次1个Set加1个Get),计算总耗时和内存利用率
    * 开足够大的Shm(400MB)保证能存下所有数据

    实验数据:
    * 块大小与总耗时(单位:s)的关系:

    * 块大小与内存利用率的关系:


    实验结论:
    * 内存块越大,系统性能越好,但内存利用率越低。需要在这2着间取个平衡点
    * 一般取值为"平均(key+value)长度/2",且很容易理解,用户数据长度基本一致即波动范围小时,内存碎片会减低,内存利用率会增加

    UDC、Memcache、Redis内存管理策略性能对比

    实验方案:
    * UDC策略使用默认块大小为200B,Memcached策略使用默认增长因子1.25
    * 分别进行10w、50w.... 5千万、1亿次压测操作,每次进行1个Set加1个Get,计算总耗时和内存利用率

    实验数据:
    * 3种内存管理策略在不同压测次数下的总耗时(单位:s)情况

    * 本实验3种内存管理策略的内存利用率(与压测次数无关)
       - UDC策略:72.83%
       - Memcached策略:88.19%
       - Redis策略:98.77%

    实验结论:
    * Memcached的Slab Allocation机制在性能上的表现是最好的,且内存利用率也接近90%
    * 当然,Memcached和Redis是基于进程堆的,主要用于缓存;UDC是基于Shm,用来做持久化存储,需考虑更多数据分布/同步/容灾等方面,3着没必要用来直接比较,这里只是单从内存管理策略上进行分析

    展开全文
  • 只要您具备一定计算机基础知识、熟悉本行业管理业务,完全可以利用该平台开发出功能强大的管理系统。单机、网络均可使用,不是用传统编程开发模式,而是给用户一种途径,就能灵活设计自己实用管理软件。 该...
  •  《循序渐进oracle:数据库管理、优化与备份恢复》适用于数据库管理人员、数据库开发人员、系统维护人员、数据库初学者及其他数据库从业人员,也可以作为各大中专院校相关专业参考用书和相关培训机构培训教材。...
  •  本书深入浅出地介绍了目前世界上最受欢迎的数据库管理系统之一——sql server。全书共分三个部分:第一部分阐释了数据库的基本概念,讲解了数据库建模语言;第二部分展示了从概念建模到在 sql server 2008上真正...
  •  本书深入浅出地介绍了目前世界上最受欢迎的数据库管理系统之一——sql server。全书共分三个部分:第一部分阐释了数据库的基本概念,讲解了数据库建模语言;第二部分展示了从概念建模到在 sql server 2008上真正...
  • 数据库原理(第5版)

    千次下载 热门讨论 2011-11-08 13:41:53
    事实上,在某些方面这些主题对于小型数据库更重要,因为它们没有专业的数据库管理员来确保关键任务的执行。第6章也讨论了分布式数据库和面向对象的数据库。 第7章介绍了使用基于Web的数据库处理,包括开放数据库连接...
  • Firebird数据库中文版

    热门讨论 2011-11-29 08:48:43
    Firebird可以工作最常见的硬件环境中,甚至非常差硬件中,也能很好的工作,当然,硬件要求依赖于你想什么,例如 ,你有多少个并发用户等等。 有效平台支持: Firebrid在常见的平台上都可运行,如Linux和...
  • 今天在文档管理相关系统时,需要将网页上文本输入框(textarea或input)中内容,上传到ORACLE数据库的CLOB字段中去。在网上找了长时间,总算有所收获,现将方法总结如下,其中部分代码为其它网友源码:一、...

    网上常见的例子总是将文本文件上传至数据库的方法。今天在做文档管理相关系统时,需要将网页上的文本输入框(textarea或input)中的内容,上传到ORACLE数据库的CLOB字段中去。在网上找了好长时间,总算有所收获,现将方法总结如下,其中部分代码为其它网友的源码:

    一、上传

    private void updateContent(Connection conn, Information info) throws Exception {

    PreparedStatement pstmt = conn.prepareStatement(

    "SELECT CONTENT FROM INFO_CONTENT WHERE ID=? FOR UPDATE");

    pstmt.setInt(1, info.getId());

    ResultSet rs = pstmt.executeQuery();

    /* 取出此CLOB对象 */

    if (rs.next()) {

    //Weblogic这样写

    OracleThinClob clob = (OracleThinClob) rs.getClob(1);

    //其它服务器这样写

    //oracle.sql.CLOB clob = (oracle.sql.CLOB) rs.getClob(1);

    /* 向CLOB对象中写入数据 */

    /*

    //保存文件

    //BufferedWriter out = new BufferedWriter(clob.getCharacterOutputStream());

    //BufferedReader in = new BufferedReader(new FileReader(filename));

    */

    //保存字符串

    Writer out = clob.getCharacterOutputStream();

    out.write(info.getContent());

    out.flush();

    out.close();

    rs.close();

    pstmt.close();

    }

    }

    二、在jsp中显示

    public Information getInformation(int id) throws Exception {

    Information base = new Information();

    Connection conn = null;

    try {

    conn = DBConnect.GainDBConnect();

    PreparedStatement ps = conn.prepareStatement(

    "SELECT * FROM INFO_CONTENT WHERE ID=?");

    ps.setInt(1, id);

    ResultSet rs = ps.executeQuery();

    while (rs.next()) {

    java.sql.Clob clob = (java.sql.Clob) rs.getClob("CONTENT");

    /* 以字符形式输出 */

    Reader out = new BufferedReader(clob.getCharacterStream());

    BufferedReader bfClob = new BufferedReader(out);

    String strClob = bfClob.readLine();

    StringBuffer sbResult = new StringBuffer();

    while (strClob != null) {

    sbResult.append(strClob);

    strClob = bfClob.readLine();

    }

    base.setContent(sbResult.toString());

    out.close();

    }

    rs.close();

    ps.close();

    }

    catch (Exception ex) {

    System.out.println(ex);

    throw ex;

    }finally {

    conn.close();

    }

    return base;

    }

    转载请注明来源网站:www.itxm.cn谢谢!

    分享到:

    展开全文
  • 订单管理系统

    2007-11-18 18:00:46
    (此版中提供了首页的显示补丁程序 defaultn.asp 由于时间太短,只是敷衍了事,能在首页上显示商品而已,用库里的TOP字段验证,和以前的数据库兼容, 但您需稍修改。如果您稍懂ASP,应该可以做出漂亮...
  • ASP.NET MVC Filters 4种默认过滤器使用【附示例】    过滤器(Filters)出现使得我们可以在ASP.NET MVC程序里更好的控制浏览器请求过来URL,不是每个请求...实现动态Action(权限管理系统的好东西)...

    ASP.NET MVC Filters 4种默认过滤器的使用【附示例】

     

     过滤器(Filters)的出现使得我们可以在ASP.NET MVC程序里更好的控制浏览器请求过来的URL,不是每个请求都会响应内容,只响应特定内容给那些有特定权限的用户,过滤器理论上有以下功能:

    1. 判断登录与否或用户权限
    2. 决策输出缓存
    3. 防盗链
    4. 防蜘蛛
    5. 本地化与国际化设置
    6. 实现动态Action(做权限管理系统的好东西)

    先来看一个简单的例子:新建一个AuthFiltersController,里面有两个Action

    复制代码
    public ActionResult Index()
    {
        return View();
    }
    
    [Authorize]
    public ActionResult Welcome()
    {
        return View();
    }
    复制代码

    很显然,第一个名为Index的Action是没有过滤的,任何身份的请求都可以通过,只要在浏览器的URL栏里键入:localhost:****/AuthFilters/Index 就能得到对应的视图响应;
    而第二个名为Welcome的Action上面标注了[Authorize],表示这是一个只处理那些通过身份验证的URL请求,如果没有通过身份验证就请求这个Action会被带到登录页面。看看配置文件:

    <authentication mode="Forms">
      <forms loginUrl="~/Account/LogOn" timeout="2880" />
    </authentication>

    根据配置文件的定义,登录页面就是AccountController下名为LogOn的Action,那么就新建一个AccountController,并新建两个Action:

    复制代码
    public ActionResult LogOn()
    {
        return View();
    }
    
    [HttpPost]
    public ActionResult LogOn(LogOnViewModel model)
    {
        if (model.UserName.Trim() == model.Password.Trim()) //伪代码,只要输入的用户名和密码一样就过
        {
            if (model.RememberMe)
                FormsAuthentication.SetAuthCookie(model.UserName, true);   //2880分钟有效期的cookie
            else
                FormsAuthentication.SetAuthCookie(model.UserName, false);  //会话cookie
            return RedirectToAction("Welcome", "AuthFilters");
        }
        else
            return View(model);
    }
    复制代码

    第一个是处理Get请求用于响应视图页面的,第二个是处理用户点击提交回发的登录表单。

    LogOnViewModel是用户登录实体类,看具体定义:

    复制代码
    /// <summary>
    /// 用户登录类
    /// </summary>
    public class LogOnViewModel
    {
        /// <summary>
        /// 用户名
        /// </summary>
        public string UserName { get; set; }
    
        /// <summary>
        /// 密码
        /// </summary>
        public string Password { get; set; }
    
        /// <summary>
        /// 记住我
        /// </summary>
        public bool RememberMe { get; set; }
    
    }
    复制代码

    ok,按F6编译下项目,再按Ctrl + F5运行下项目,在URL里输入:localhost:****/AuthFilters/Index 很轻松的得到了Index这个Action的响应

    再定位到:localhost:****/AuthFilters/Welcome

    可见,虽然定位到了Welcome这个Action,但是却并不像Index一样直接返回对应的视图,而是被带到了登录页面。就是因为Welcome这个Action上被标注了[Authorize],拒绝了所以未验证用户的访问。

    既然拒绝了未验证的用户,那就登录下通过验证,看看上面LogOn里写的伪代码就知道,输入相同的用户名和密码就能登录成功,用户名和密码都输入“wangjie”试试:

    已经通过验证并得到Welcome这个Action的响应了。相比之前就是多生成了一个名为“.ASPXAUTH”的Cookie,这是个默认名,配置文件里可以修改。同时,如果登录的时候勾选了“记住我”那么此Cookie的过期时间就是配置文件里定义的2880分钟。

    ok,现在提高下难度,只设置名为“a”、“bb”、“ccc”的用户可以访问欢迎页面:

    [Authorize(Users = "a,bb,ccc")]
    public ActionResult Welcome()
    {
        ViewBag.Message = "已登录";
        return View();
    }

    再用“wangjie”登录下发现跳不到欢迎页面了,因为指定了a、bb、ccc这三个用户才可以登录。
    先不管为何在Action上标注Users = "a,bb,ccc"就可以控制可以访问的用户,但从操作性上来说这样控制Action的访问权限还是很方便的。但是如果项目大,用户对应的角色和权限变化比较大,每次变化都来重新标注Action显然不合适。MVC框架提供的过滤器(Filters)就派上了用场:

    上图是Asp.Net MVC框架提供的几种默认Filter:授权筛选器、操作筛选器、结果筛选器、异常筛选器,下面来一一讲解,先看演示Demo结构图:

    一、授权筛选器

    授权筛选器用于实现IAuthorizationFilter接口和做出关于是否执行操作方法(如执行身份验证或验证请求的属性)的安全决策。 AuthorizeAttribute类和RequireHttpsAttribute类是授权筛选器的示例。授权筛选器在任何其他筛选器之前运行。
    新建一个继承AuthorizeAttribute类的UserAuthorize类,F12定位到AuthorizeAttribute类,看看内部申明:

    复制代码
    public AuthorizeAttribute();
    
    public string Roles { get; set; }
    public override object TypeId { get; }
    public string Users { get; set; }
    
    protected virtual bool AuthorizeCore(HttpContextBase httpContext);
    protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext);
    public virtual void OnAuthorization(AuthorizationContext filterContext);
    protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext);
    复制代码

    上面演示的指定用户才可以访问就是利用了Users属性,并由基类帮助我们验证,只放指定的Users用户通过。要实现自定义的验证只需重写下OnAuthorization和AuthorizeCore方法。为了演示效果,新建一个SampleData类用来初始化数据:

    复制代码
    /// <summary>
    /// 测试数据(实际项目中,这些数据应该从数据库拿)
    /// </summary>
    public class SampleData
    {
        public static List<User> users;
        public static List<Role> roles;
        public static List<RoleWithControllerAction> roleWithControllerAndAction;
    
        static SampleData()
        {
            // 初始化用户
            users = new List<User>(){
                new User(){ Id=1, UserName="wangjie", RoleId=1},
                new User(){ Id=2, UserName ="senior1", RoleId=2},
                new User(){ Id=3, UserName ="senior2", RoleId=2},
                new User(){ Id=5, UserName="junior1", RoleId=3},
                new User(){ Id=6, UserName="junior2", RoleId=3},
                new User(){ Id=6, UserName="junior3", RoleId=3}
            };
            // 初始化角色
            roles = new List<Role>()
            {
                new Role() { Id=1, RoleName="管理员", Description="管理员角色"},
                new Role() { Id=2, RoleName="高级会员", Description="高级会员角色"},
                new Role() { Id=3, RoleName="初级会员", Description="初级会员角色"}
            };
            // 初始化角色控制器和Action对应类
            roleWithControllerAndAction = new List<RoleWithControllerAction>()
            {
                new RoleWithControllerAction(){ Id=1, ControllerName="AuthFilters", ActionName="AdminUser", RoleIds="1"},
                new RoleWithControllerAction(){ Id=2, ControllerName="AuthFilters", ActionName="SeniorUser", 
    Ids="1,2"},
                new RoleWithControllerAction(){ Id=3, ControllerName="AuthFilters", ActionName="JuniorUser", 
    Ids="1,2,3"},
                new RoleWithControllerAction(){ Id=4, ControllerName="ActionFilters", ActionName="Index", RoleIds="2,3"}
            };
        }
    }
    复制代码

    简单明了,用户拥有角色,不同角色可以访问的Action也不同。这比较符合权限项目里的控制。再看看UserAuthorize类的具体定义:

    复制代码
    /// <summary>
    /// 自定义用户授权
    /// </summary>
    public class UserAuthorize : AuthorizeAttribute
    {
        /// <summary>
        /// 授权失败时呈现的视图
        /// </summary>
        public string AuthorizationFailView { get; set; }
    
        /// <summary>
        /// 请求授权时执行
        /// </summary>
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            //获得url请求里的controller和action:
            string controllerName = filterContext.RouteData.Values["controller"].ToString().ToLower();
            string actionName = filterContext.RouteData.Values["action"].ToString().ToLower();
            //string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
            //string actionName = filterContext.ActionDescriptor.ActionName;
    
            //根据请求过来的controller和action去查询可以被哪些角色操作:
            Models.RoleWithControllerAction roleWithControllerAction = 
    base.SampleData.roleWithControllerAndAction.Find(r => r.ControllerName.ToLower() == controllerName && 
    tionName.ToLower() == actionName);
            if (roleWithControllerAction != null)
            {
                this.Roles = roleWithControllerAction.RoleIds;     //有权限操作当前控制器和Action的角色id
            }
            base.OnAuthorization(filterContext);   //进入AuthorizeCore
        }
    
        /// <summary>
        /// 自定义授权检查(返回False则授权失败)
        /// </summary>
        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            if (httpContext.User.Identity.IsAuthenticated)
            {
                string userName = httpContext.User.Identity.Name;    //当前登录用户的用户名
                Models.User user = Database.SampleData.users.Find(u => u.UserName == userName);   //当前登录用户对象
    
                if (user != null)
                {
                    Models.Role role = Database.SampleData.roles.Find(r => r.Id == user.RoleId);  //当前登录用户的角色
                    foreach (string roleid in Roles.Split(','))
                    {
                        if (role.Id.ToString() == roleid)
                            return true;
                    }
                    return false;
                }
                else
                    return false;
            }
            else
                return false;     //进入HandleUnauthorizedRequest 
        }
    
        /// <summary>
        /// 处理授权失败的HTTP请求
        /// </summary>
        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            filterContext.Result = new ViewResult { ViewName = AuthorizationFailView };
        }
    }
    复制代码

    自定义好授权类就可以到控制器上使用了,看看AuthFiltersController类:

    复制代码
    public class AuthFiltersController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    
        //[Authorize(Users = "a,bb,ccc")]
        [Authorize]
        public ActionResult Welcome()
        {
            ViewBag.Message = "普通已授权页面";
            return View();
        }
    
        [UserAuthorize(AuthorizationFailView = "Error")]   //管理员页面
        public ActionResult AdminUser()
        {
            ViewBag.Message = "管理员页面";
            return View("Welcome");
        }
    
        [UserAuthorize(AuthorizationFailView = "Error")]   //会员页面(管理员、会员都可访问)
        public ActionResult SeniorUser()
        {
            ViewBag.Message = "高级会员页面";
            return View("Welcome");
        }
    
        [UserAuthorize(AuthorizationFailView = "Error")]   //游客页面(管理员、会员、游客都可访问)
        public ActionResult JuniorUser()
        {
            ViewBag.Message = "初级会员页面";
            return View("Welcome");
        }
    }
    复制代码

    Welcome这个Action使用了默认的授权验证,只要登陆成功就可以访问。其他几个Action上都标注了自定义的UserAuthorize,并没有标注Users="....",Roles=".....",因为这样在Action上写死用户或者角色控制权限显然是不可行的,用户和角色的对应以及不同的角色可以操作的Action应该是从数据库里取出来的。为了演示就在SampleData类里初始化了一些用户和角色信息,根据SampleData类的定义,很明显wangjie拥有1号管理员角色,可以访问AuthFilters这个控制器下的所有Action,senior1、senior2拥有2号高级会员的角色,可以访问AuthFilters这个控制器下除了AdminUser之外的Action等等
    再次登陆下,就发现拥有高级会员角色的用户senior1是不可以访问AdminUser这个Action,会被带到AuthorizationFailView属性指定的Error视图:

    二、操作筛选器、结果筛选器

    操作筛选器用于实现IActionFilter接口以及包装操作方法执行。IActionFilter接口声明两个方法:OnActionExecuting和OnActionExecuted。OnActionExecuting在操作方法之前运行。OnActionExecuted在操作方法之后运行,可以执行其他处理,如向操作方法提供额外数据、检查返回值或取消执行操作方法。

    结果筛选器用于实现IResultFilter接口以及包装ActionResult对象的执行。IResultFilter接口声明两个方法OnResultExecuting和OnResultExecuted。OnResultExecuting在执行ActionResult对象之前运行。OnResultExecuted在结果之后运行,可以对结果执行其他处理,如修改 HTTP 响应。OutputCacheAttribute 类是结果筛选器的一个示例。

    操作筛选器和结果筛选器都实现ActionFilterAttribute类,看看类里定义的方法:

    public virtual void OnActionExecuted(ActionExecutedContext filterContext);
    public virtual void OnActionExecuting(ActionExecutingContext filterContext);
    public virtual void OnResultExecuted(ResultExecutedContext filterContext);
    public virtual void OnResultExecuting(ResultExecutingContext filterContext);

    根据方法的名字就知道4个方法执行的顺序了:
    OnActionExecuting是Action执行前的操作、OnActionExecuted则是Action执行后的操作、OnResultExecuting是解析ActionResult前执行、OnResultExecuted是解析ActionResult后执行
    即:Action执行前:OnActionExecuting方法先执行→Action执行 →OnActionExecuted方法执行→OnResultExecuting方法执行→返回的ActionRsult中的 executeResult方法执行→OnResultExecuted执行

    完全可以重写OnActionExecuting方法实现上面授权筛选器一样的功能,因为OnActionExecuting方法是在Action方法执行前运行的,自定义一个实现ActionFilterAttribute类的ActionFilters类,OnActionExecuting方法这么写:

    复制代码
    /// <summary>
    /// 在执行操作方法之前由 MVC 框架调用
    /// </summary>
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        string userName = filterContext.HttpContext.User.Identity.Name;    //当前登录用户的用户名
        Models.User user = Database.SampleData.users.Find(u => u.UserName == userName);   //当前登录用户对象
    
        if (user != null)
        {
            Models.Role role = Database.SampleData.roles.Find(r => r.Id == user.RoleId);  //当前登录用户的角色
    
            //获得controller:
            string controllerName = filterContext.RouteData.Values["controller"].ToString().ToLower();
            //string actionName = filterContext.RouteData.Values["action"].ToString().ToLower();
            if (ActionName == null)
                ActionName = filterContext.RouteData.Values["action"].ToString();
    
            //查询角色id
            Models.RoleWithControllerAction roleWithControllerAction = 
    .SampleData.roleWithControllerAndAction.Find(r => r.ControllerName.ToLower() == controllerName && 
    Name.ToLower() == ActionName.ToLower());
            if (roleWithControllerAction != null)
            {
                this.Roles = roleWithControllerAction.RoleIds;     //有权限操作当前控制器和Action的角色id
            }
            if (!string.IsNullOrEmpty(Roles))
            {
                foreach (string roleid in Roles.Split(','))
                {
                    if (role.Id.ToString() == roleid)
                        return;   //return就说明有权限了,后面的代码就不跑了,直接返回视图给浏览器就好
                }
            }
            filterContext.Result = new EmptyResult();   //请求失败输出空结果
            HttpContext.Current.Response.Write("对不起,你没有权限!");   //打出提示文字
            //return;
        }
        else
        {
            //filterContext.Result = new ViewResult { ViewName = "Error" };
            filterContext.Result = new EmptyResult();
            HttpContext.Current.Response.Write("对不起,请先登录!");
            //return;
        }
        //base.OnActionExecuting(filterContext);
    }
    复制代码

    看看如何在ActionFiltersController控制器里使:

    复制代码
    public class ActionFiltersController : Controller
    {
        [ActionFilters]
        public ActionResult Index()
        {
            return View();
        }
    
        [ActionFilters(ActionName = "Index")]
        public ActionResult Details()
        {
            return View();
        }
    
        [ActionFilters]
        public ActionResult Test()
        {
            return View();
        }
    }
    复制代码

    很明显Index和Details这两个Action同用一个权限,看看初始化数据SampleData类的定义:

    new RoleWithControllerAction(){ Id=4, ControllerName="ActionFilters", ActionName="Index", RoleIds="2,3"}

    只有2和3号角色可以访问,那么1号角色的wangjie用户应该是访问不了的,登录试试:

    三、异常筛选器

    异常筛选器用于实现IExceptionFilter接口,并在ASP.NET MVC管道执行期间引发了未处理的异常时执行。异常筛选器可用于执行诸如日志记录或显示错误页之类的任务。HandleErrorAttribute类是异常筛选器的一个示例。

    有之前授权筛选器、操作和结果筛选器的使用经验,再看异常筛选器就简单许多了,来看看自定义的继承自HandleErrorAttribute类的异常筛选类ExceptionFilters:

    复制代码
    /// <summary>
    /// 异常筛选器
    /// </summary>
    public class ExceptionFilters : HandleErrorAttribute
    {
        /// <summary>
        /// 在发生异常时调用
        /// </summary>
        public override void OnException(ExceptionContext filterContext)
        {
            //if (!filterContext.ExceptionHandled && filterContext.Exception is NullReferenceException)
            if (!filterContext.ExceptionHandled)
            {
                //获取出现异常的controller名和action名,用于记录
                string controllerName = (string)filterContext.RouteData.Values["controller"];
                string actionName = (string)filterContext.RouteData.Values["action"];
                //定义一个HandErrorInfo,用于Error视图展示异常信息
                HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
    
                ViewResult result = new ViewResult
                {
                    ViewName = this.View,
                    ViewData = new ViewDataDictionary<HandleErrorInfo>(model)  //定义ViewData,泛型
                };
                filterContext.Result = result;
                filterContext.ExceptionHandled = true;
            }
            //base.OnException(filterContext);
        }
    }
    复制代码

    看看如何在视图中使用:

    [ExceptionFilters(View = "Exception")]
    public ActionResult Index()
    {
        throw new NullReferenceException("测试抛出异常!");
    }

    View是制定的捕获异常后显示给用户的视图:

    再看一个Action:

    [ExceptionFilters(View = "ExceptionDetails")]
    public ActionResult Details()
    {
        int i = int.Parse("hello,world!");
        return View();
    }

    把string类型的数据强转int,肯定得报FormatException异常,看看ExceptionDetails视图如何定义的:

    复制代码
    @model System.Web.Mvc.HandleErrorInfo
    @{
        Layout = null;
    }
    <!DOCTYPE html>
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>异常</title>
    </head>
    <body>
        <p>
            抛错控制器:<b>@Model.ControllerName</b> 抛错方法:<b>@Model.ActionName</b> 抛错类型:<b>@Model.Exception.GetType
    
    ().Name</b>
        </p>
        <p>
            异常信息:<b>@Model.Exception.Message</b>
        </p>
        <p>
            堆栈信息:</p>
        <pre>@Model.Exception.StackTrace</pre>
    </body>
    </html>
    复制代码

    浏览器显示结果:

     

    转载自:http://www.360doc.com/showweb/0/0/765540458.aspx

     

     

    数据库常见死锁原因及处理

     

    数据库是一个多用户使用的共享资源,当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。加锁是实现数据库并发控制的一个非常重要的技术。在实际应用中经常会遇到的与锁相关的异常情况,当两个事务需要一组有冲突的锁,而不能将事务继续下去的话,就会出现死锁,严重影响应用的正常执行。 
      在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(Share Locks,即S锁)。当数据对象被加上排它锁时,其他的事务不能对它读取和修改。加了共享锁的数据对象可以被其他事务读取,但不能修改。数据库利用这两种基本的锁类型来对数据库的事务进行并发控制。 
      下面总结下这两种锁造成的常见的死锁情况与解决方案:

    一. 事务之间对资源访问顺序的交替

    1. 出现原因: 
      一个用户A 访问表A(锁住了表A),然后又访问表B;另一个用户B 访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。
    2. 解决方法: 
      这种死锁比较常见,是由于程序的BUG产生的,除了调整的程序的逻辑没有其它的办法。仔细分析程序的逻辑,对于数据库的多表操作时,尽量按照相同的顺序进行处理,尽量避免同时锁定两个资源,如操作A和B两张表时,总是按先A后B的顺序处理, 必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源。

    二. 并发修改同一记录

    1. 出现原因: 
       用户A查询一条纪录,然后修改该条纪录;这时用户B修改该条纪录,这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁,而用户B里的独占锁由于A有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁。这种死锁由于比较隐蔽,但在稍大点的项目中经常发生。 
       一般更新模式由一个事务组成,此事务读取记录,获取资源(页或行)的共享 (S) 锁,然后修改行,此操作要求锁转换为排它 (X) 锁。如果两个事务获得了资源上的共享模式锁,然后试图同时更新数据,则一个事务尝试将锁转换为排它 (X) 锁。共享模式到排它锁的转换必须等待一段时间,因为一个事务的排它锁与其它事务的共享模式锁不兼容;发生锁等待。第二个事务试图获取排它 (X) 锁以进行更新。由于两个事务都要转换为排它 (X) 锁,并且每个事务都等待另一个事务释放共享模式锁,因此发生死锁。
    2. 解决方法: 
      a. 使用乐观锁进行控制。乐观锁大多是基于数据版本(Version)记录机制实现。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。乐观锁机制避免了长事务中的数据库加锁开销(用户A和用户B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。Hibernate 在其数据访问引擎中内置了乐观锁实现。需要注意的是,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。 
      b. 使用悲观锁进行控制。悲观锁大多数情况下依靠数据库的锁机制实现,如Oracle的Select … for update语句,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户账户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对成百上千个并发,这样的情况将导致灾难性的后果。所以,采用悲观锁进行控制时一定要考虑清楚。 
      c. SqlServer可支持更新锁 
      为解决死锁,SqlServer引入更新锁,它有如下特征: 
      (1) 加锁的条件:当一个事务执行update语句时,数据库系统会先为事务分配一把更新锁。 
      (2) 解锁的条件:当读取数据完毕,执行更新操作时,会把更新锁升级为独占锁。 
      (3) 与其他锁的兼容性:更新锁与共享锁是兼容的,也就是说,一个资源可以同时放置更新锁和共享锁,但是最多放置一把更新锁。这样,当多个事务更新相同的数据时,只有一个事务能获得更新锁,然后再把更新锁升级为独占锁,其他事务必须等到前一个事务结束后,才能获取得更新锁,这就避免了死锁。 
      (4) 并发性能:允许多个事务同时读锁定的资源,但不允许其他事务修改它。 
      例子如下:
       T1:
        begin tran
          select * from table(updlock) (加更新锁)
        update table set column1='hello'
        T2:
        begin tran
        select * from table(updlock)
        update table set column1='world'

    更新锁的意思是:“我现在只想读,你们别人也可以读,但我将来可能会做更新操作,我已经获取了从共享锁(用来读)到排他锁(用来更新)的资格”。一个事物只能有一个更新锁获此资格。 
    T1执行select,加更新锁。 
    T2运行,准备加更新锁,但发现已经有一个更新锁在那儿了,只好等。 
    当后来有user3、user4…需要查询table表中的数据时,并不会因为T1的select在执行就被阻塞,照样能查询,提高了效率。

    三. 索引不当导致全表扫描

    1. 出现原因: 
      如果在事务中执行了一条不满足条件的语句,执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行后,就很容易产生死锁和阻塞。类似的情况还有当表中的数据量非常庞大而索引建的过少或不合适的时候,使得经常发生全表扫描,最终应用系统会越来越慢,最终发生阻塞或死锁。
    2. 解决方法: 
      SQL语句中不要使用太复杂的关联多表的查询;使用“执行计划”对SQL语句进行分析,对于有全表扫描的SQL语句,建立相应的索引进行优化。

    四.事务封锁范围大且相互等待

     

    .NET源码中的链表

     

    .NET中自带的链表是LinkedList类,并且已经直接实现成了双向循环链表。

    其节点类LinkedListNode的数据结构如下,数据项包括指示到某个链表的引用,以及左,右节点和值。

     

    [html] view plain copy
     
    1. public sealed class LinkedListNode<T>  
    2. {  
    3.   internal LinkedList<T> list;  
    4.   internal LinkedListNode<T> next;  
    5.   internal LinkedListNode<T> prev;  
    6.   internal T item;  
    7. }  


    另外,获取前一个节点和后一个节点的实现如下:注意:里面的if-else结构的意义是当前一个(后一个)节点不为空且不是头节点时才不返回null,这样做的意义是当链表内只有1个节点时,其prev和next是指向自身的。

     

     

    [csharp] view plain copy
     
    1. [__DynamicallyInvokable]  
    2.    public LinkedListNode<T> Next  
    3.    {  
    4.      [__DynamicallyInvokable] get  
    5.      {  
    6.        if (this.next != null && this.next != this.list.head)  
    7.          return this.next;  
    8.        else  
    9.          return (LinkedListNode<T>) null;  
    10.      }  
    11.    }  
    12.   
    13.    [__DynamicallyInvokable]  
    14.    public LinkedListNode<T> Previous  
    15.    {  
    16.      [__DynamicallyInvokable] get  
    17.      {  
    18.        if (this.prev != null && this != this.list.head)  
    19.          return this.prev;  
    20.        else  
    21.          return (LinkedListNode<T>) null;  
    22.      }  
    23.    }  

     

     

    过有一个把链表置为无效的方法定义如下:

     

    [csharp] view plain copy
     
    1.     internal void Invalidate()  
    2.     {  
    3.       this.list = (LinkedList<T>) null;  
    4.       this.next = (LinkedListNode<T>) null;  
    5.       this.prev = (LinkedListNode<T>) null;  
    6.     }  

     

     

    而LinkedList的定义如下:主要的两个数据是头节点head以及长度count。

     

    [csharp] view plain copy
     
    1. public class LinkedList<T> : ICollection<T>, IEnumerable<T>, ICollection, IEnumerable, ISerializable, IDeserializationCallback  
    2.   {  
    3.     internal LinkedListNode<T> head;  
    4.     internal int count;  
    5.     internal int version;  
    6.     private object _syncRoot;  
    7.     private SerializationInfo siInfo;  
    8.     private const string VersionName = "Version";  
    9.     private const string CountName = "Count";  
    10.     private const string ValuesName = "Data";  

     

     

    而对此链表的主要操作,包括:

     

    • 插入节点到最后,Add(),也是AddLast()。
    • 在某个节点后插入,AddAfter(Node, T)。
    • 在某个节点前插入,AddBefore(Node, T)。
    • 插入到头节点之前,AddFirst(T)。
    • 清除所有节点,Clear()。
    • 是否包含某个值,Contains(T),也就是Find()。
    • 查找某个节点的引用,Find()和FindLast()。
    • 复制到数组,CopyTo(Array)
    • 删除某个节点,Remove(T)。
    另外有几个内部方法,用来支撑复用插入和删除操作:
    • 内部插入节点,InternalInsertNodeBefore()
    • 内部插入节点到空链表,InternalInsertNodeToEmptyList()
    • 内部删除节点,InternalRemoveNode()
    • 验证新节点是否有效,ValidateNewNode()
    • 验证节点是否有效,ValidateNode()
     
    插入新节点到链表最后的代码如下:
    [csharp] view plain copy
     
    1. public void AddLast(LinkedListNode<T> node)  
    2. {  
    3.   this.ValidateNewNode(node);  
    4.   if (this.head == null)  
    5.     this.InternalInsertNodeToEmptyList(node);  
    6.   else  
    7.     this.InternalInsertNodeBefore(this.head, node);  
    8.   node.list = this;  
    9. }  
    插入操作的第一步是验证节点是否有效,即节点不为null,且节点不属于其他链表。
    [csharp] view plain copy
     
    1. internal void ValidateNewNode(LinkedListNode<T> node)  
    2.    {  
    3.      if (node == null)  
    4.        throw new ArgumentNullException("node");  
    5.      if (node.list != null)  
    6.        throw new InvalidOperationException(SR.GetString("LinkedListNodeIsAttached"));  
    7.    }  

    如果头节点为空,则执行插入到空链表的操作:将节点的next和prev都指向为自己,并作为头节点。
    [csharp] view plain copy
     
    1. private void InternalInsertNodeToEmptyList(LinkedListNode<T> newNode)  
    2.    {  
    3.      newNode.next = newNode;  
    4.      newNode.prev = newNode;  
    5.      this.head = newNode;  
    6.      ++this.version;  
    7.      ++this.count;  
    8.    }  
    如果头节点不为空,则执行插入到头节点之前(注:因为是双向链表,所以插到头节点之前就相当于插到链表的最后了),具体的指针指向操作如下:
    [csharp] view plain copy
     
    1. private void InternalInsertNodeBefore(LinkedListNode<T> node, LinkedListNode<T> newNode)  
    2. {  
    3.   newNode.next = node;  
    4.   newNode.prev = node.prev;  
    5.   node.prev.next = newNode;  
    6.   node.prev = newNode;  
    7.   ++this.version;  
    8.   ++this.count;  
    9. }  

    而插入新节点到指定节点之后的操作如下:同样还是调用的内部函数,把新节点插入到指定节点的下一个节点的之前。有点绕,但确实让这个内部函数起到多个作用了。
    [csharp] view plain copy
     
    1.     public void AddAfter(LinkedListNode<T> node, LinkedListNode<T> newNode)  
    2.     {  
    3.       this.ValidateNode(node);  
    4.       this.ValidateNewNode(newNode);  
    5.       this.InternalInsertNodeBefore(node.next, newNode);  
    6.       newNode.list = this;  
    7.     }  

    而插入新节点到指定节点之前的操作如下:直接调用插入新节点的内部函数,另外还要判断指定的节点是否是头节点,如果是的话,要把头节点变成新的节点。
    [csharp] view plain copy
     
    1. public void AddBefore(LinkedListNode<T> node, LinkedListNode<T> newNode)  
    2.     {  
    3.       this.ValidateNode(node);  
    4.       this.ValidateNewNode(newNode);  
    5.       this.InternalInsertNodeBefore(node, newNode);  
    6.       newNode.list = this;  
    7.       if (node != this.head)  
    8.         return;  
    9.       this.head = newNode;  
    10.     }  

    把新链表插入到第一个节点(也就是变成头节点)的操作如下:如果链表为空就直接变成头节点,否则就插入到头节点之前,取代头节点。
    [csharp] view plain copy
     
    1. public void AddFirst(LinkedListNode<T> node)  
    2.     {  
    3.       this.ValidateNewNode(node);  
    4.       if (this.head == null)  
    5.       {  
    6.         this.InternalInsertNodeToEmptyList(node);  
    7.       }  
    8.       else  
    9.       {  
    10.         this.InternalInsertNodeBefore(this.head, node);  
    11.         this.head = node;  
    12.       }  
    13.       node.list = this;  
    14.     }  

    查找链表中某个值的操作如下:注意直接返回null的条件是头节点为空。然后就是遍历了,因为是双向链表,所以要避免死循环(遍历到头节点时跳出)。
    [csharp] view plain copy
     
    1. public LinkedListNode<T> Find(T value)  
    2.     {  
    3.       LinkedListNode<T> linkedListNode = this.head;  
    4.       EqualityComparer<T> @default = EqualityComparer<T>.Default;  
    5.       if (linkedListNode != null)  
    6.       {  
    7.         if ((object) value != null)  
    8.         {  
    9.           while (!@default.Equals(linkedListNode.item, value))  
    10.           {  
    11.             linkedListNode = linkedListNode.next;  
    12.             if (linkedListNode == this.head)  
    13.               goto label_8;  
    14.           }  
    15.           return linkedListNode;  
    16.         }  
    17.         else  
    18.         {  
    19.           while ((object) linkedListNode.item != null)  
    20.           {  
    21.             linkedListNode = linkedListNode.next;  
    22.             if (linkedListNode == this.head)  
    23.               goto label_8;  
    24.           }  
    25.           return linkedListNode;  
    26.         }  
    27.       }  
    28. label_8:  
    29.       return (LinkedListNode<T>) null;  
    30.     }  

    删除某个节点的操作如下:
    [csharp] view plain copy
     
    1. public void Remove(LinkedListNode<T> node)  
    2. {  
    3.   this.ValidateNode(node);  
    4.   this.InternalRemoveNode(node);  
    5. }  

    同样,内部删除节点的实现如下:如果节点指向自己,说明是头节点,所以直接把头节点置null。然后就是指针的指向操作了。
    [csharp] view plain copy
     
    1. internal void InternalRemoveNode(LinkedListNode<T> node)  
    2.     {  
    3.       if (node.next == node)  
    4.       {  
    5.         this.head = (LinkedListNode<T>) null;  
    6.       }  
    7.       else  
    8.       {  
    9.         node.next.prev = node.prev;  
    10.         node.prev.next = node.next;  
    11.         if (this.head == node)  
    12.           this.head = node.next;  
    13.       }  
    14.       node.Invalidate();  
    15.       --this.count;  
    16.       ++this.version;  
    17.     }  

    而清空链表的操作如下:遍历链表,逐个设置为无效,最后将内部的头节点也置为null。
    [csharp] view plain copy
     
    1. public void Clear()  
    2. {  
    3.   LinkedListNode<T> linkedListNode1 = this.head;  
    4.   while (linkedListNode1 != null)  
    5.   {  
    6.     LinkedListNode<T> linkedListNode2 = linkedListNode1;  
    7.     linkedListNode1 = linkedListNode1.Next;  
    8.     linkedListNode2.Invalidate();  
    9.   }  
    10.   this.head = (LinkedListNode<T>) null;  
    11.   this.count = 0;  
    12.   ++this.version;  
    13. }  
     
    链表转数组的实现如下:首先判断入参的有效性,然后从头节点开始遍历,依次复制到数组中,直到头结点(尽头)。
    [csharp] view plain copy
     
    1. public void CopyTo(T[] array, int index)  
    2.    {  
    3.      if (array == null)  
    4.        throw new ArgumentNullException("array");  
    5.      if (index < 0 || index > array.Length)  
    6.      {  
    7.        throw new ArgumentOutOfRangeException("index", SR.GetString("IndexOutOfRange", new object[1]  
    8.        {  
    9.          (object) index  
    10.        }));  
    11.      }  
    12.      else  
    13.      {  
    14.        if (array.Length - index < this.Count)  
    15.          throw new ArgumentException(SR.GetString("Arg_InsufficientSpace"));  
    16.        LinkedListNode<T> linkedListNode = this.head;  
    17.        if (linkedListNode == null)  
    18.          return;  
    19.        do  
    20.        {  
    21.          array[index++] = linkedListNode.item;  
    22.          linkedListNode = linkedListNode.next;  
    23.        }  
    24.        while (linkedListNode != this.head);  
    25.      }  
    26.    }  

    以上。
     

    多线程下C#如何保证线程安全?

     

    多线程编程相对于单线程会出现一个特有的问题,就是线程安全的问题。所谓的线程安全,就是如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的。 线程安全问题都是由全局变量及静态变量引起的。

      为了保证多线程情况下,访问静态变量的安全,可以用锁机制来保证,如下所示:

    复制代码
     1         //需要加锁的静态全局变量
     2         private static bool _isOK = false;
     3         //lock只能锁定一个引用类型变量
     4         private static object _lock = new object();
     5         static void MLock()
     6         {
     7             //多线程
     8             new System.Threading.Thread(Done).Start();
     9             new System.Threading.Thread(Done).Start();
    10             Console.ReadLine();
    11         }
    12 
    13         static void Done()
    14         {
    15             //lock只能锁定一个引用类型变量
    16             lock (_lock)
    17             {
    18                 if (!_isOK)
    19                 {
    20                     Console.WriteLine("OK");
    21                     _isOK = true;
    22                 }
    23             }
    24         } 
    复制代码

      需要注意的是,Lock只能锁住一个引用类型的对象。另外,除了锁机制外,高版本的C#中加入了async和await方法来保证线程安全,如下所示:

    复制代码
     1 public static class AsynAndAwait
     2  {
     3         //step 1 
     4         private static int count = 0;
     5         //用async和await保证多线程下静态变量count安全
     6         public async static void M1()
     7         {
     8             //async and await将多个线程进行串行处理
     9             //等到await之后的语句执行完成后
    10             //才执行本线程的其他语句
    11             //step 2
    12             await Task.Run(new Action(M2));
    13             Console.WriteLine("Current Thread ID is {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
    14             //step 6
    15             count++;
    16             //step 7
    17             Console.WriteLine("M1 Step is {0}", count);
    18         }
    19 
    20         public static void M2()
    21         {
    22             Console.WriteLine("Current Thread ID is {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
    23             //step 3
    24             System.Threading.Thread.Sleep(3000);
    25             //step 4
    26             count++;
    27             //step 5
    28             Console.WriteLine("M2 Step is {0}", count);
    29         }
    30 }
    复制代码

      在时序图中我们可以知道,共有两个线程进行交互,如下图所示:

      用async和await后,上述代码的执行顺序为下图所示:

     

      若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时对一个变量执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

     

    转载自:http://www.cnblogs.com/isaboy/p/C_async_await_lock_safe_thread_multi.html

     

    .net实现支付宝在线支付

     

     流程参考《实物商品交易服务集成技术文档2.0.pdf》
    网关地址http://paytest.rupeng.cn/AliPay/PayGate.ashx


    网关参数说明:
    partner:商户编号
    return_url:回调商户地址(通过商户网站的哪个页面来通知支付成功!)
    subject:商品名称
    body:商品描述
    out_trade_no:订单号!!!(由商户网站生成,支付宝不确保正确性,只负责转发。)
    total_fee:总金额
    seller_email:卖家邮箱
    sign:数字签名。
    为按顺序连接 (总金额、 商户编号、订单号、商品名称、商户密钥)的MD5值。


    重定向的url("http://paytest.rupeng.cn/AliPay/PayGate.ashx?partner="
                    + partner + "&return_url=" + Server.UrlEncode(return_url) 
    + "&subject=" 
    + Server.UrlEncode(subject) 
    + "&body=" + Server.UrlEncode(body) 
    + "&out_trade_no=" + out_trade_no 
    + "&total_fee=" + total_fee + "&seller_email=" 
    + Server.UrlEncode(seller_email) + "&sign=" + sign)




    回调商户接口地址参数说明:
    out_trade_no :订单号。给PayGate.ashx传过去的out_trade_no再传回来
    returncode :返回码,字符串。ok为支付成功,error为支付失败。
    total_fee :支付金额
    sign  :数字签名。为按顺序连接 (订单号、返回码、支付金额、商户密钥)为新字符串的MD5值。


    (每个商户的密钥是商户自己设置的,每个人的都不一样,只有支付宝和商户知道,所以无法猜测、假冒)




    MD5算法要用以下的,大小写都不能错:
            /// <summary>
            /// 得到字符串的MD5散列值
            /// </summary>
            /// <param name="input"></param>
            /// <returns></returns>
            public static String GetMD5(this string input)
            {
                System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider();
                byte[] bs = System.Text.Encoding.UTF8.GetBytes(input);
                bs = x.ComputeHash(bs);
                System.Text.StringBuilder s = new System.Text.StringBuilder();
                foreach (byte b in bs)
                {
                    s.Append(b.ToString("x2").ToLower());
                }
                return s.ToString();
            }

     

      /// <summary>

            /// 付款
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            protected void Unnamed1_Click(object sender, EventArgs e)
            {
                string partner = "2";//商户编号
                string return_url = "http://localhost:5059/ReturnPage.ashx";//回调商户地址(通过商户网站的哪个页面来通知支付成功!)
                string subject = "飞机"; //商品名称
                string body = "非常大的飞机";  //商品描述
                string out_trade_no = "aaabbb888";  //订单号!(由商户网站生成,支付宝不确保正确性,只负责转发。)
                string total_fee = "9"; //总金额
                string seller_email = "719862911@qq.com";//卖家邮箱  
                //商户密钥 abc123//不要写到url中
                //为按顺序连接 (总金额、 商户编号、订单号、商品名称、商户密钥)的MD5值。
                string sign = CommonHelper.getMD5Str(total_fee + partner + out_trade_no + subject + "abc123");//数字签名。


                Response.Redirect("http://paytest.rupeng.cn/AliPay/PayGate.ashx?partner="
                    + partner + "&return_url=" + Server.UrlEncode(return_url) + "&subject=" + Server.UrlEncode(subject) + "&body=" + Server.UrlEncode(body) + "&out_trade_no=" + out_trade_no + "&total_fee=" + total_fee + "&seller_email=" + Server.UrlEncode(seller_email) + "&sign=" + sign);

            }

     

      public void ProcessRequest(HttpContext context)
            {
                context.Response.ContentType = "text/html";
                context.Response.Write("支付宝消息返回到了我的商户网站的这个页面\r\n");


                string out_trade_no = context.Request["out_trade_no"];//订单号。给PayGate.ashx传过去的out_trade_no再传回来
                string returncode = context.Request["returncode"];//返回码,字符串。ok为支付成功,error为支付失败。
                string total_fee = context.Request["total_fee"];//支付金额
                string sign = context.Request["sign"];//支付宝端返回 的数字签名
                string MySign = CommonHelper.getMD5Str(out_trade_no + returncode + total_fee + "abc123");//为按顺序连接 (订单号、返回码、支付金额、商户密钥)为新字符串的MD5值。
                if (sign!=MySign)
                {
                    //提交的数据 =验证失败
                    context.Response.Write("提交的数据 =验证失败");
                    return;
                }
                if (returncode=="ok")
                {
                    context.Response.Write("支付成功");
                }
                else if (returncode == "error")
                {
                    context.Response.Write("支付失败");
                }
            }

     

    转载自:https://blog.csdn.net/u014297475/article/details/52419202

     

    彻头彻尾理解单例模式与多线程

     

    摘要: 
       
      本文首先概述了单例模式产生动机,揭示了单例模式的本质和应用场景。紧接着,我们给出了单例模式在单线程环境下的两种经典实现:饿汉式 和 懒汉式,但是饿汉式是线程安全的,而懒汉式是非线程安全的。在多线程环境下,我们特别介绍了五种方式来在多线程环境下创建线程安全的单例,使用 synchronized方法synchronized块静态内部类双重检查模式 和 ThreadLocal 实现懒汉式单例,并总结出实现效率高且线程安全的单例所需要注意的事项。


    版权声明:

    本文原创作者:书呆子Rico 
    作者博客地址:http://blog.csdn.net/justloveyou_/


    一. 单例模式概述

      单例模式(Singleton),也叫单子模式,是一种常用的设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候,整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,显然,这种方式简化了在复杂环境下的配置管理。

      特别地,在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。事实上,这些应用都或多或少具有资源管理器的功能。例如,每台计算机可以有若干个打印机,但只能有一个 Printer Spooler (单例) ,以避免两个打印作业同时输出到打印机中。再比如,每台计算机可以有若干通信端口,系统应当集中 (单例) 管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。

      综上所述,单例模式就是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种方法。


    二. 单例模式及其单线程环境下的经典实现

      单例模式应该是23种设计模式中最简单的一种模式了,下面我们从单例模式的定义、类型、结构和使用要素四个方面来介绍它。

    1、单例模式理论基础

    定义: 确保一个类只有一个实例,并为整个系统提供一个全局访问点 (向整个系统提供这个实例)。

    类型: 创建型模式

    结构:

                          技术分享

      特别地,为了更好地理解上面的类图,我们以此为契机,介绍一下类图的几个知识点:

    • 类图分为三部分,依次是类名、属性、方法;
    • 以<<开头和以>>结尾的为注释信息;
    • 修饰符+代表public,-代表private,#代表protected,什么都没有代表包可见;
    • 带下划线的属性或方法代表是静态的。

    三要素:

    • 私有的构造方法;

    • 指向自己实例的私有静态引用;

    • 以自己实例为返回值的静态的公有方法。


    2、单线程环境下的两种经典实现

      在介绍单线程环境中单例模式的两种经典实现之前,我们有必要先解释一下 立即加载 和 延迟加载 两个概念。

    • 立即加载 : 在类加载初始化的时候就主动创建实例;

    • 延迟加载 : 等到真正使用的时候才去创建实例,不用时不去主动创建。

        在单线程环境下,单例模式根据实例化对象时机的不同,有两种经典的实现:一种是 饿汉式单例(立即加载),一种是 懒汉式单例(延迟加载)饿汉式单例在单例类被加载时候,就实例化一个对象并交给自己的引用;而懒汉式单例只有在真正使用的时候才会实例化一个对象并交给自己的引用。代码示例分别如下:


    饿汉式单例:

    // 饿汉式单例
    public class Singleton1 {
    
        // 指向自己实例的私有静态引用,主动创建
        private static Singleton1 singleton1 = new Singleton1();
    
        // 私有的构造方法
        private Singleton1(){}
    
        // 以自己实例为返回值的静态的公有方法,静态工厂方法
        public static Singleton1 getSingleton1(){
            return singleton1;
        }
    }

      我们知道,类加载的方式是按需加载,且加载一次。。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。


    懒汉式单例:

    // 懒汉式单例
    public class Singleton2 {
    
        // 指向自己实例的私有静态引用
        private static Singleton2 singleton2;
    
        // 私有的构造方法
        private Singleton2(){}
    
        // 以自己实例为返回值的静态的公有方法,静态工厂方法
        public static synchronized Singleton2 getSingleton2(){
            // 被动创建,在真正需要使用时才去创建
            if (singleton2 == null) {
                singleton2 = new Singleton2();
            }
            return singleton2;
        }
    }

      我们从懒汉式单例可以看到,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给自己的引用。


      总之,从速度和反应时间角度来讲,饿汉式(又称立即加载)要好一些;从资源利用效率上说,懒汉式(又称延迟加载)要好一些。


    3、单例模式的优点

      我们从单例模式的定义和实现,可以知道单例模式具有以下几个优点:

    • 在内存中只有一个对象,节省内存空间;

    • 避免频繁的创建销毁对象,可以提高性能;

    • 避免对共享资源的多重占用,简化访问;

    • 为整个系统提供一个全局访问点。


    4、单例模式的使用场景

      由于单例模式具有以上优点,并且形式上比较简单,所以是日常开发中用的比较多的一种设计模式,其核心在于为整个系统提供一个唯一的实例,其应用场景包括但不仅限于以下几种:

    • 有状态的工具类对象;
    • 频繁访问数据库或文件的对象;

    5、单例模式的注意事项

      在使用单例模式时,我们必须使用单例类提供的公有工厂方法得到单例对象,而不应该使用反射来创建,否则将会实例化一个新对象。此外,在多线程环境下使用单例模式时,应特别注意线程安全问题,我在下文会重点讲到这一点。


    三. 多线程环境下单例模式的实现

      在单线程环境下,无论是饿汉式单例还是懒汉式单例,它们都能够正常工作。但是,在多线程环境下,情形就发生了变化:由于饿汉式单例天生就是线程安全的,可以直接用于多线程而不会出现问题;但懒汉式单例本身是非线程安全的,因此就会出现多个实例的情况,与单例模式的初衷是相背离的。下面我重点阐述以下几个问题:

    • 为什么说饿汉式单例天生就是线程安全的?

    • 传统的懒汉式单例为什么是非线程安全的?

    • 怎么修改传统的懒汉式单例,使其线程变得安全?

    • 线程安全的单例的实现还有哪些,怎么实现?

    • 双重检查模式、Volatile关键字 在单例模式中的应用

    • ThreadLocal 在单例模式中的应用


      特别地,为了能够更好的观察到单例模式的实现是否是线程安全的,我们提供了一个简单的测试程序来验证。该示例程序的判断原理是:

      开启多个线程来分别获取单例,然后打印它们所获取到的单例的hashCode值。若它们获取的单例是相同的(该单例模式的实现是线程安全的),那么它们的hashCode值一定完全一致;若它们的hashCode值不完全一致,那么获取的单例必定不是同一个,即该单例模式的实现不是线程安全的,是多例的。注意,相应输出结果附在每个单例模式实现示例后。 
       
      若看官对上述原理不够了解,请移步我的博客《Java 中的 ==, equals 与 hashCode 的区别与联系》

    public class Test {
        public static void main(String[] args) {
            Thread[] threads = new Thread[10];
            for (int i = 0; i < threads.length; i++) {
                threads[i] = new TestThread();
            }
    
            for (int i = 0; i < threads.length; i++) {
                threads[i].start();
            }
        }
    
    }
    
    class TestThread extends Thread {
        @Override
        public void run() {
            // 对于不同单例模式的实现,只需更改相应的单例类名及其公有静态工厂方法名即可
            int hash = Singleton5.getSingleton5().hashCode();  
            System.out.println(hash);
        }
    }

    1、为什么说饿汉式单例天生就是线程安全的?

    // 饿汉式单例
    public class Singleton1 {
    
        // 指向自己实例的私有静态引用,主动创建
        private static Singleton1 singleton1 = new Singleton1();
    
        // 私有的构造方法
        private Singleton1(){}
    
        // 以自己实例为返回值的静态的公有方法,静态工厂方法
        public static Singleton1 getSingleton1(){
            return singleton1;
        }
    }/* Output(完全一致): 
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
     *///:~

      我们已经在上面提到,类加载的方式是按需加载,且只加载一次。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用。换句话说,在线程访问单例对象之前就已经创建好了。再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例,也就是说,线程每次都只能也必定只可以拿到这个唯一的对象。因此就说,饿汉式单例天生就是线程安全的。


    2、传统的懒汉式单例为什么是非线程安全的?

    // 传统懒汉式单例
    public class Singleton2 {
    
        // 指向自己实例的私有静态引用
        private static Singleton2 singleton2;
    
        // 私有的构造方法
        private Singleton2(){}
    
        // 以自己实例为返回值的静态的公有方法,静态工厂方法
        public static synchronized Singleton2 getSingleton2(){
            // 被动创建,在真正需要使用时才去创建
            if (singleton2 == null) {
                singleton2 = new Singleton2();
            }
            return singleton2;
        }
    }/* Output(不完全一致): 
            1084284121
            2136955031
            2136955031
            1104499981
            298825033
            298825033
            2136955031
            482535999
            298825033
            2136955031
     *///:~

      上面发生非线程安全的一个显著原因是,会有多个线程同时进入 if (singleton2 == null) {…} 语句块的情形发生。当这种这种情形发生后,该单例类就会创建出多个实例,违背单例模式的初衷。因此,传统的懒汉式单例是非线程安全的。


    3、实现线程安全的懒汉式单例的几种正确姿势

    1)、同步延迟加载 — synchronized方法

    // 线程安全的懒汉式单例
    public class Singleton2 {
    
        private static Singleton2 singleton2;
    
        private Singleton2(){}
    
        // 使用 synchronized 修饰,临界资源的同步互斥访问
        public static synchronized Singleton2 getSingleton2(){
            if (singleton2 == null) {
                singleton2 = new Singleton2();
            }
            return singleton2;
        }
    }/* Output(完全一致): 
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
     *///:~

      该实现与上面传统懒汉式单例的实现唯一的差别就在于:是否使用 synchronized 修饰 getSingleton2()方法。若使用,就保证了对临界资源的同步互斥访问,也就保证了单例。

      从执行结果上来看,问题已经解决了,但是这种实现方式的运行效率会很低,因为同步块的作用域有点大,而且锁的粒度有点粗。同步方法效率低,那我们考虑使用同步代码块来实现。

      更多关于 synchronized 关键字 的介绍, 请移步我的博文《Java 并发:内置锁 Synchronized》


    2)、同步延迟加载 — synchronized块

    // 线程安全的懒汉式单例
    public class Singleton2 {
    
        private static Singleton2 singleton2;
    
        private Singleton2(){}
    
    
        public static Singleton2 getSingleton2(){
            synchronized(Singleton2.class){  // 使用 synchronized 块,临界资源的同步互斥访问
                if (singleton2 == null) { 
                    singleton2 = new Singleton2();
                }
            }
            return singleton2;
        }
    }/* Output(完全一致): 
            16993205
            16993205
            16993205
            16993205
            16993205
            16993205
            16993205
            16993205
            16993205
            16993205
     *///:~

      该实现与上面synchronized方法版本实现类似,此不赘述。从执行结果上来看,问题已经解决了,但是这种实现方式的运行效率仍然比较低,事实上,和使用synchronized方法的版本相比,基本没有任何效率上的提高。


    3)、同步延迟加载 — 使用内部类实现延迟加载

    // 线程安全的懒汉式单例
    public class Singleton5 {
    
        // 私有内部类,按需加载,用时加载,也就是延迟加载
        private static class Holder {
            private static Singleton5 singleton5 = new Singleton5();
        }
    
        private Singleton5() {
    
        }
    
        public static Singleton5 getSingleton5() {
            return Holder.singleton5;
        }
    }
    /* Output(完全一致): 
            482535999
            482535999
            482535999
            482535999
            482535999
            482535999
            482535999
            482535999
            482535999
            482535999
     *///:~

      如上述代码所示,我们可以使用内部类实现线程安全的懒汉式单例,这种方式也是一种效率比较高的做法。至于其为什么是线程安全的,其与问题 “为什么说饿汉式单例天生就是线程安全的?” 相类似,此不赘述。

      更多关于 内部类 的介绍, 请移步我的博文《 Java 内部类综述 》

      关于使用双重检查、ThreaLocal实现线程安全的懒汉式单例分别见第四节和第五节。


    四. 单例模式与双重检查(Double-Check idiom)

      使用双重检测同步延迟加载去创建单例的做法是一个非常优秀的做法,其不但保证了单例,而且切实提高了程序运行效率。对应的代码清单如下:

    // 线程安全的懒汉式单例
    public class Singleton3 {
    
        //使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例
        private static volatile Singleton3 singleton3;
    
        private Singleton3() {
        }
    
        public static Singleton3 getSingleton3() {
            // Double-Check idiom
            if (singleton3 == null) {
                synchronized (Singleton3.class) {       // 1
                    // 只需在第一次创建实例时才同步
                    if (singleton3 == null) {       // 2
                        singleton3 = new Singleton3();      // 3
                    }
                }
            }
            return singleton3;
        }
    }/* Output(完全一致): 
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
     *///:~

      如上述代码所示,为了在保证单例的前提下提高运行效率,我们需要对 singleton3 进行第二次检查,目的是避开过多的同步(因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同步获取锁了)。这种做法无疑是优秀的,但是我们必须注意一点: 
       
      必须使用volatile关键字修饰单例引用。


      那么,如果上述的实现没有使用 volatile 修饰 singleton3,会导致什么情形发生呢? 为解释该问题,我们分两步来阐述:

    (1)、当我们写了 new 操作,JVM 到底会发生什么?

      首先,我们要明白的是: new Singleton3() 是一个非原子操作。代码行 singleton3 = new Singleton3(); 的执行过程可以形象地用如下3行伪代码来表示:

    memory = allocate();        //1:分配对象的内存空间
    ctorInstance(memory);       //2:初始化对象
    singleton3 = memory;        //3:使singleton3指向刚分配的内存地址

      但实际上,这个过程可能发生无序写入(指令重排序),也就是说上面的3行指令可能会被重排序导致先执行第3行后执行第2行,也就是说其真实执行顺序可能是下面这种:

    memory = allocate();        //1:分配对象的内存空间
    singleton3 = memory;        //3:使singleton3指向刚分配的内存地址
    ctorInstance(memory);       //2:初始化对象

      这段伪代码演示的情况不仅是可能的,而且是一些 JIT 编译器上真实发生的现象。


    (2)、重排序情景再现 
       
      了解 new 操作是非原子的并且可能发生重排序这一事实后,我们回过头看使用 Double-Check idiom 的同步延迟加载的实现:

      我们需要重新考察上述清单中的 //3 行。此行代码创建了一个 Singleton 对象并初始化变量 singleton3 来引用此对象。这行代码存在的问题是,在 Singleton 构造函数体执行之前,变量 singleton3 可能提前成为非 null 的,即赋值语句在对象实例化之前调用,此时别的线程将得到的是一个不完整(未初始化)的对象,会导致系统崩溃。下面是程序可能的一组执行步骤:

      1、线程 1 进入 getSingleton3() 方法; 
      2、由于 singleton3 为 null,线程 1 在 //1 处进入 synchronized 块; 
      3、同样由于 singleton3 为 null,线程 1 直接前进到 //3 处,但在构造函数执行之前,使实例成为非 null,并且该实例是未初始化的; 
      4、线程 1 被线程 2 预占; 
      5、线程 2 检查实例是否为 null。因为实例不为 null,线程 2 得到一个不完整(未初始化)的 Singleton 对象; 
      6、线程 2 被线程 1 预占。 
      7、线程 1 通过运行 Singleton3 对象的构造函数来完成对该对象的初始化。

      显然,一旦我们的程序在执行过程中发生了上述情形,就会造成灾难性的后果,而这种安全隐患正是由于指令重排序的问题所导致的。让人兴奋地是,volatile 关键字正好可以完美解决了这个问题。也就是说,我们只需使用volatile关键字修饰单例引用就可以避免上述灾难。


      特别地,由于 volatile关键字的介绍 和 类加载及对象初始化顺序 两块内容已经在我之前的博文中介绍过,再此只给出相关链接,不再赘述。

      更多关于 volatile关键字 的介绍, 请移步我的博文《 Java 并发:volatile 关键字解析》

      更多关于 类加载及对象初始化顺序的介绍, 请移步我的博文《 Java 继承、多态与类的复用》


    五. 单例模式 与 ThreadLocal

      借助于 ThreadLocal,我们可以实现双重检查模式的变体。我们将临界资源线程局部化,具体到本例就是将双重检测的第一层检测条件 if (instance == null) 转换为 线程局部范围内的操作 。这里的 ThreadLocal 也只是用作标识而已,用来标识每个线程是否已访问过:如果访问过,则不再需要走同步块,这样就提高了一定的效率。对应的代码清单如下:

    // 线程安全的懒汉式单例
    public class Singleton4 {
    
        // ThreadLocal 线程局部变量
        private static ThreadLocal<Singleton4> threadLocal = new ThreadLocal<Singleton4>();
        private static Singleton4 singleton4 = null;    // 不需要是
    
        private Singleton4(){}
    
        public static Singleton4 getSingleton4(){
            if (threadLocal.get() == null) {        // 第一次检查:该线程是否第一次访问
                createSingleton4();
            }
            return singleton4;
        }
    
        public static void createSingleton4(){
            synchronized (Singleton4.class) {
                if (singleton4 == null) {          // 第二次检查:该单例是否被创建
                    singleton4 = new Singleton4();   // 只执行一次
                }
            }
            threadLocal.set(singleton4);      // 将单例放入当前线程的局部变量中 
        }
    }/* Output(完全一致): 
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
    *///:~

      借助于 ThreadLocal,我们也可以实现线程安全的懒汉式单例。但与直接双重检查模式使用,本实现在效率上还不如后者。

      更多关于 ThreadLocal 的介绍, 请移步我的博文《 Java 并发:深入理解 ThreadLocal》


    六. 小结

      本文首先介绍了单例模式的定义和结构,并给出了其在单线程和多线程环境下的几种经典实现。特别地,我们知道,传统的饿汉式单例无论在单线程还是多线程环境下都是线程安全的,但是传统的懒汉式单例在多线程环境下是非线程安全的。为此,我们特别介绍了五种方式来在多线程环境下创建线程安全的单例,包括:

    • 使用synchronized方法实现懒汉式单例;

    • 使用synchronized块实现懒汉式单例;

    • 使用静态内部类实现懒汉式单例;

    • 使用双重检查模式实现懒汉式单例;

    • 使用ThreadLocal实现懒汉式单例;


      当然,实现懒汉式单例还有其他方式。但是,这五种是比较经典的实现,也是我们应该掌握的几种实现方式。从这五种实现中,我们可以总结出,要想实现效率高的线程安全的单例,我们必须注意以下两点:

    • 尽量减少同步块的作用域;

    • 尽量使用细粒度的锁。


    七. 更多

      本文涉及内容比较广,涉及到 hashcode、synchronized 关键字、内部类、 类加载及对象初始化顺序、volatile关键字 和 ThreadLocal 等知识点,这些知识点在我之前的博文中均专门总结过,现附上相关链接,感兴趣的朋友可以移步到相关博文进行查看。

      更多关于 hashCode 与相等 的介绍,请移步我的博客《Java 中的 ==, equals 与 hashCode 的区别与联系》

      更多关于 synchronized 关键字 的介绍, 请移步我的博文《Java 并发:内置锁 Synchronized》

      更多关于 内部类 的介绍, 请移步我的博文《 Java 内部类综述 》

      更多关于 volatile关键字 的介绍, 请移步我的博文《 Java 并发:volatile 关键字解析》

      更多关于 类加载及对象初始化顺序的介绍, 请移步我的博文《 Java 继承、多态与类的复用》

      更多关于 ThreadLocal 的介绍, 请移步我的博文《 Java 并发:深入理解 ThreadLocal》


      此外,

      更多关于 Java SE 进阶 方面的内容,请关注我的专栏 《Java SE 进阶之路》。本专栏主要研究Java基础知识、Java源码和设计模式,从初级到高级不断总结、剖析各知识点的内在逻辑,贯穿、覆盖整个Java知识面,在一步步完善、提高把自己的同时,把对Java的所学所思分享给大家。万丈高楼平地起,基础决定你的上限,让我们携手一起勇攀Java之巅…

      更多关于 Java 并发编程 方面的内容,请关注我的专栏 《Java 并发编程学习笔记》。本专栏全面记录了Java并发编程的相关知识,并结合操作系统、Java内存模型和相关源码对并发编程的原理、技术、设计、底层实现进行深入分析和总结,并持续跟进并发相关技术。


    引用

    Java 中的双重检查(Double-Check) 
    单例模式与双重检测 
    用happen-before规则重新审视DCL 
    JAVA设计模式之单例模式 
    23种设计模式(1):单例模式

     

    转载自:http://www.mamicode.com/info-detail-1728587.html

     

    App.Config详解及读写操作

     
    App.Config详解
    应用程序配置文件是标准的 XML 文件,XML 标记和属性是区分大小写的。它是可以按需要更改的,开发人员可以使用配置文件来更改设置,而不必重编译应用程序。
    配置文件的根节点是configuration。我们经常访问的是appSettings,它是由.Net预定义配置节。我们经常使用的配置文件的架构是象下面的形式。
    先大概有个印象,通过后面的实例会有一个比较清楚的认识。下面的“配置节”可以理解为进行配置一个XML的节点。 
    1.  向项目添加 app.config 文件:
    右击项目名称,选择“添加”→“添加新建项”,在出现的“添加新项”对话框中,选择“添加应用程序配置文件”;如果项目以前没有配置文件,则默认的文件名称为“ app.config ”,单击“确定”。出现在设计器视图中的app.config 文件为:
    <? xml version = "1.0 "encoding = "utf-8 " ?>
    < configuration >
    </ configuration >
    在项目进行编译后,在 bin/Debuge 文件下,将出现两个配置文件 ( 以本项目为例 ) ,一个名为“JxcManagement.EXE.config ”,另一个名为“ JxcManagement.vshost.exe.config ”。第一个文件为项目实际使用的配置文件,在程序运行中所做的更改都将被保存于此;第二个文件为原代码“ app.config ”的同步文件,在程序运行中不会发生更改。
    2.  connectionStrings 配置节:
    请注意:如果您的 SQL 版本为 2005 Express 版,则默认安装时 SQL 服务器实例名为localhost/SQLExpress ,须更改以下实例中“ Data Source=localhost; ”一句为“ Data Source=localhost/SQLExpress; ”,在等于号的两边不要加上空格。
    <!-- 数据库连接串 -->
         < connectionStrings >
             < clear />
             < add name = "conJxcBook "
                  connectionString = "Data Source=localhost;Initial Catalog=jxcbook;User                                   ID=sa;password=******** "
                  providerName = "System.Data.SqlClient " />
         </ connectionStrings >
    3. appSettings 配置节:
    appSettings 配置节为整个程序的配置,如果是对当前用户的配置,请使用 userSettings 配置节,其格式与以下配置书写要求一样。
    <!-- 进销存管理系统初始化需要的参数 -->
         < appSettings >
             < clear />
             < add key = "userName "value = "" />
             < add key = "password "value = "" />
             < add key = "Department "value = "" />
             < add key = "returnValue "value = "" />
             < add key = "pwdPattern "value = "" />
             < add key = "userPattern "value = "" />
    </ appSettings >
    4. 读取与更新 app.config
    对于app.config 文件的读写,参照了网络文章:http://www.codeproject.com/csharp/  SystemConfiguration.asp标题为“Read/Write App.Config File with .NET 2.0”一文。
    请注意:要使用以下的代码访问app.config文件,除添加引用System.Configuration外,还必须在项目添加对System.Configuration.dll的引用。
    4.1 读取connectionStrings配置节
    /// <summary>
    /// 依据连接串名字connectionName返回数据连接字符串
    /// </summary>
    /// <param name="connectionName"></param>
    /// <returns></returns>
    private static string GetConnectionStringsConfig(string connectionName)
    {
    string connectionString =
            ConfigurationManager .ConnectionStrings[connectionName].ConnectionString.ToString();
        Console .WriteLine(connectionString);
        return connectionString;
    }
    4.2 更新connectionStrings配置节
    /// <summary>
    /// 更新连接字符串
    /// </summary>
    /// <param name="newName"> 连接字符串名称 </param>
    /// <param name="newConString"> 连接字符串内容 </param>
    /// <param name="newProviderName"> 数据提供程序名称 </param>
    private static void UpdateConnectionStringsConfig(string newName,
        string newConString,
        string newProviderName)
    {
        bool isModified = false ;    // 记录该连接串是否已经存在
        // 如果要更改的连接串已经存在
        if (ConfigurationManager .ConnectionStrings[newName] != null )
        {
            isModified = true ;
        }
        // 新建一个连接字符串实例
        ConnectionStringSettings mySettings =
            new ConnectionStringSettings (newName, newConString, newProviderName);
        // 打开可执行的配置文件*.exe.config
        Configuration config =
            ConfigurationManager .OpenExeConfiguration(ConfigurationUserLevel .None);
        // 如果连接串已存在,首先删除它
        if (isModified)
        {
            config.ConnectionStrings.ConnectionStrings.Remove(newName);
        }
        // 将新的连接串添加到配置文件中.
        config.ConnectionStrings.ConnectionStrings.Add(mySettings);
        // 保存对配置文件所作的更改
        config.Save(ConfigurationSaveMode .Modified);
        // 强制重新载入配置文件的ConnectionStrings配置节
        ConfigurationManager .RefreshSection("ConnectionStrings" );
    }
    4.3 读取appStrings配置节
    /// <summary>
    /// 返回*.exe.config文件中appSettings配置节的value项
    /// </summary>
    /// <param name="strKey"></param>
    /// <returns></returns>
    private static string GetAppConfig(string strKey)
    {
        foreach (string key in ConfigurationManager .AppSettings)
        {
            if (key == strKey)
            {
                return ConfigurationManager .AppSettings[strKey];
            }
        }
        return null ;
    }
    4.4 更新connectionStrings配置节
    /// <summary>
    /// 在*.exe.config文件中appSettings配置节增加一对键、值对
    /// </summary>
    /// <param name="newKey"></param>
    /// <param name="newValue"></param>
    private static void UpdateAppConfig(string newKey, string newValue)
    {
        bool isModified = false ;   
        foreach (string key in ConfigurationManager .AppSettings)
        {
           if (key==newKey)
            {   
                isModified = true ;
            }
        }
     
        // Open App.Config of executable
        Configuration config =
            ConfigurationManager .OpenExeConfiguration(ConfigurationUserLevel .None);
        // You need to remove the old settings object before you can replace it
        if (isModified)
        {
            config.AppSettings.Settings.Remove(newKey);
        }   
        // Add an Application Setting.
        config.AppSettings.Settings.Add(newKey,newValue);  
        // Save the changes in App.config file.
        config.Save(ConfigurationSaveMode .Modified);
        // Force a reload of a changed section.
        ConfigurationManager .RefreshSection("appSettings" );

    }

    C#读写app.config中的数据

    读语句:

    
    
    String str = ConfigurationManager.AppSettings["DemoKey"];

     写语句:

    
    
    Configuration cfa = 
      ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    cfa.AppSettings.Settings["DemoKey"].Value = "DemoValue";
    cfa.Save();

     配置文件内容格式:(app.config)

    
    
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <appSettings>
    <add key="DemoKey" value="*" />
    </appSettings>
    </configuration>

     红笔标明的几个关键节是必须的

    
    
    System.Configuration.ConfigurationSettings.AppSettings["Key"];

       但是现在FrameWork2.0已经明确表示此属性已经过时。并建议改为ConfigurationManager 

    或WebConfigurationManager。并且AppSettings属性是只读的,并不支持修改属性值.

      但是要想调用ConfigurationManager必须要先在工程里添加system.configuration.dll程序集的引用。

    (在解决方案管理器中右键点击工程名称,在右键菜单中选择添加引用,.net TablePage下即可找到)
    添加引用后可以用 String str = ConfigurationManager.AppSettings["Key"]来获取对应的值了。

      更新配置文件:

    
    
    Configuration cfa = ConfigurationManager.
        OpenExeConfiguration(ConfigurationUserLevel.None);
    cfa.AppSettings.Settings.Add("key", "Name") || 
      cfa.AppSettings.Settings["BrowseDir"].Value = "name";

      等等...
      最后调用
      cfa.Save(); 
      当前的配置文件更新成功。

    *****************************************************************************************************************

    读写配置文件app.config 
      在.Net中提供了配置文件,让我们可以很方面的处理配置信息,这个配置是XML格式的。而且.Net中已经提供了一些访问这个文件的功能。

    1、读取配置信息
      下面是一个配置文件的具体内容:

     

    
    
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
    <appSettings>
    <add key="ConnenctionString" value="*" />
    <add key="TmpPath" value="C:\Temp" />
    </appSettings>
    </configuration>

     .net提供了可以直接访问<appsettings>(注意大小写)元素的方法,在这元素中有很多的子元素,这些子元素名称都是 “add”,有两个属性分别是“key”和“value”。一般情况下我们可以将自己的配置信息写在这个区域中,通过下面的方式进行访问:

     

     

    
    
    string ConString=System.Configuration
        .ConfigurationSettings.AppSettings["ConnenctionString"];
    在appsettings后面的是子元素的key属性的值,例如appsettings["connenctionstring"],我们就是访 问<add key="ConnenctionString" value="*" />这个子元素,它的返回值就是“*”,即value属性的值。

    2、设置配置信息

      如果配置信息是静态的,我们可以手工配置,要注意格式。如果配置信息是动态的,就需要我们写程序来实现。在.Net中没有写配置文件的功能,我们可以使用操作XML文件的方式来操作配置文件。下面就是一个写配置文件的例子。

     

    
    
    private void SaveConfig(string ConnenctionString)
    {
      XmlDocument doc=new XmlDocument();
      //获得配置文件的全路径
      string strFileName=AppDomain.CurrentDomain.BaseDirectory.ToString()
                       +"Code.exe.config";
      doc.LOAd(strFileName);
      //找出名称为“add”的所有元素
      XmlNodeList nodes=doc.GetElementsByTagName("add");
      for(int i=0;i<nodes.Count;i++)
      {
        //获得将当前元素的key属性
        XmlAttribute att=nodes[i].Attributes["key"];
        //根据元素的第一个属性来判断当前的元素是不是目标元素
        if (att.Value=="ConnectionString") 
        {
          //对目标元素中的第二个属性赋值
          att=nodes[i].Attributes["value"];
          att.Value=ConnenctionString;
          break;
        }
      }
      //保存上面的修改
      doc.Save(strFileName);
    }
    读取并修改App.config文件
    1. 向项目添加app.config文件:
    右击项目名称,选择“添加”→“添加新建项”,在出现的“添加新项”对话框中,选择“添加应用程序配置文件”;如果项目以前没有配置文件,则默认的文件名称为“app.config”,单击“确定”。出现在设计器视图中的app.config文件为:
    <?xmlversion="1.0"encoding="utf-8" ?>
    <configuration>
    </configuration>
    在项目进行编译后,在bin\Debuge文件下,将出现两个配置文件(以本项目为例),一个名为“JxcManagement.EXE.config”,另一个名为“JxcManagement.vshost.exe.config”。第一个文件为项目实际使用的配置文件,在程序运行中所做的更改都将被保存于此;第二个文件为原代码“app.config”的同步文件,在程序运行中不会发生更改。
    2.  connectionStrings配置节:
    请注意:如果您的SQL版本为2005 Express版,则默认安装时SQL服务器实例名为localhost\SQLExpress,须更改以下实例中“Data Source=localhost;”一句为“Data Source=localhost\SQLExpress;”,在等于号的两边不要加上空格。
    <!--数据库连接串-->
         <connectionStrings>
             <clear />
             <addname="conJxcBook"
                  connectionString="Data Source=localhost;Initial Catalog=jxcbook;User                                   ID=sa;password=********"
                  providerName="System.Data.SqlClient" />
         </connectionStrings>
    3. appSettings配置节:
    appSettings配置节为整个程序的配置,如果是对当前用户的配置,请使用userSettings配置节,其格式与以下配置书写要求一样。
    <!--进销存管理系统初始化需要的参数-->
         <appSettings>
             <clear />
             <addkey="userName"value="" />
             <addkey="password"value="" />
             <addkey="Department"value="" />
             <addkey="returnValue"value="" />
             <addkey="pwdPattern"value="" />
             <addkey="userPattern"value="" />
    </appSettings>
    4.读取与更新app.config
    对于app.config文件的读写,参照了网络文章:http://www.codeproject.com/csharp/ SystemConfiguration.asp标题为“Read/Write App.Config File with .NET 2.0”一文。
    请注意:要使用以下的代码访问app.config文件,除添加引用System.Configuration外,还必须在项目添加对System.Configuration.dll的引用。
    4.1 读取connectionStrings配置节
    ///<summary>
    ///依据连接串名字connectionName返回数据连接字符串
    ///</summary>
    ///<param name="connectionName"></param>
    ///<returns></returns>
    private static string GetConnectionStringsConfig(string connectionName)
    {
    string connectionString =
            ConfigurationManager.ConnectionStrings[connectionName].ConnectionString.ToString();
        Console.WriteLine(connectionString);
        return connectionString;
    }
    4.2 更新connectionStrings配置节
    ///<summary>
    ///更新连接字符串
    ///</summary>
    ///<param name="newName">连接字符串名称</param>
    ///<param name="newConString">连接字符串内容</param>
    ///<param name="newProviderName">数据提供程序名称</param>
    private static void UpdateConnectionStringsConfig(string newName,
        string newConString,
        string newProviderName)
    {
        bool isModified = false;    //记录该连接串是否已经存在
        //如果要更改的连接串已经存在
        if (ConfigurationManager.ConnectionStrings[newName] != null)
        {
            isModified = true;
        }
        //新建一个连接字符串实例
        ConnectionStringSettings mySettings =
            new ConnectionStringSettings(newName, newConString, newProviderName);
        // 打开可执行的配置文件*.exe.config
        Configuration config =
            ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
        // 如果连接串已存在,首先删除它
        if (isModified)
        {
            config.ConnectionStrings.ConnectionStrings.Remove(newName);
        }
        // 将新的连接串添加到配置文件中.
        config.ConnectionStrings.ConnectionStrings.Add(mySettings);
        // 保存对配置文件所作的更改
        config.Save(ConfigurationSaveMode.Modified);
        // 强制重新载入配置文件的ConnectionStrings配置节
        ConfigurationManager.RefreshSection("ConnectionStrings");
    }
    4.3 读取appStrings配置节
    ///<summary>
    ///返回*.exe.config文件中appSettings配置节的value项
    ///</summary>
    ///<param name="strKey"></param>
    ///<returns></returns>
    private static string GetAppConfig(string strKey)
    {
        foreach (string key in ConfigurationManager.AppSettings)
        {
            if (key == strKey)
            {
                return ConfigurationManager.AppSettings[strKey];
            }
        }
        return null;
    }
    4.4 更新connectionStrings配置节
    ///<summary>
    ///在*.exe.config文件中appSettings配置节增加一对键、值对
    ///</summary>
    ///<param name="newKey"></param>
    ///<param name="newValue"></param>
    private static void UpdateAppConfig(string newKey, string newValue)
    {
        bool isModified = false;   
        foreach (string key in ConfigurationManager.AppSettings)
        {
           if(key==newKey)
            {   
                isModified = true;
            }
        }
     
        // Open App.Config of executable
        Configuration config =
            ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
        // You need to remove the old settings object before you can replace it
        if (isModified)
        {
            config.AppSettings.Settings.Remove(newKey);
        }   
        // Add an Application Setting.
        config.AppSettings.Settings.Add(newKey,newValue);  
        // Save the changes in App.config file.
        config.Save(ConfigurationSaveMode.Modified);
        // Force a reload of a changed section.
        ConfigurationManager.RefreshSection("appSettings");
    }
    url:http://greatverve.cnblogs.com/archive/2011/07/18/app-config.html
     
     

    判断客户端是iOS还是Android,判断是不是在微信浏览器打开

     
    复制代码
         bool flag = false;
                string agent = System.Web.HttpContext.Current.Request.UserAgent.ToLower();
                string[] keywords = { "iphone", "ipod", "ipad", "itouch" };
    
                foreach (string item in keywords)
                {
                    if (agent.Contains(item))
                    {
                        flag = true;
                        break;
                    }
                }
                if (flag)
                {
                    ViewBag.Phone = "iOS";
                }
                else {
                    ViewBag.Phone = "Android";
                }
    复制代码
     if (agent.ToLower().Contains("micromessenger")
    {
    微信浏览器
    }
    展开全文
  • 过去几年间,他工作集中在数据库管理、优化、数据架构规划、解决方案等方面.2007年国内首批OraclecACE,网络ID“Fenng”,长期关注Webc2.0网站架构技术.个人技术Blog:http://dbanotes.net.... 目录 封面 -19 封底 -...
  • 3.1.10基本ASE 数据库管理命令 70 3.1.11简单故障及排除方法 71 3.2 中级篇 71 3.2.1 IP地址改变后如何修改interfaces文件 71 3.2.2 如何解决数据库被挂起问题 72 3.2.3 如何终止数据库恢复过程 74 3.2.4 如何...
  • JQuery EasyUI+Java实现web管理系统

    万次阅读 2016-05-10 20:34:54
    当然了,我的web管理系统做的很简单也很粗糙了,前端使用常见网站模板样式+JQuery EasyUI组件,前后端使用JSON传送数据,后端语言使用Java,与MySQL数据库交互采用DbUtils。 关于自定义样式 这个主要包

    各行各业需要各种各样的web管理系统,优点肯定是显而易见的(比人工记录管理在在纸上、在excel和word表上方便多了),其基本功能无非就是增删改查。

    当然了,我的web管理系统就做的很简单也很粗糙了,该系统是管理计算机设备的。该系统主要管理关于设备自身固有属性(SN号、型号)与设备关联属性(所在位置、所处状态、拥有者)。

    系统前端使用模板样式+JQuery EasyUI组件,使用JSON交换数据,后端语言使用Java,服务器使用Tomcat,与MySQL数据库交互采用DbUtils

    自定义样式

    这个主要包括注册、登录、管理页面的整体格调、色彩搭配、页面样式、布局等等。要想真的前端搞好,要狠下功夫!然而我并不懂设计,也没有太多经验。为了敏捷开发(先把东西做出来  是最重要的,改进优化以r后再说!),直接搜各种网站素材模板(主要是图片资源和静态的html代码),借用它的皮囊。

    JQuery EasyUI 组件

     tabs(选项卡)组件在项目中主要用于 切换不同的tabs窗口,注意不同的tabs其实处在同一个页面上,切换tab其实只是将该显示的show出来,不该显示的hide掉。

    因此如果不做特殊选择,操作多个相同类型的tabs会操作所有该类型的tabs(因为选择器一样),如果有这样的需求,请使用iframe。

    dialog(对话框)组件主要负责呈现提交form组件,dialog中可包括各种类型的validatebox,对validatebox的验证可以使用自定义验证器。

    <p style="margin:10px">
       输入新密码:<input class="easyui-validatebox textbox"name="password"id="pwd"type="password"placeholder="******"data-options="required:true"validType="lengthTo[5,10]"style="margin-left:10px"/> 
    </p>
    <p style="margin:10px">
       确认输入:    <input class="easyui-validatebox textbox"type="password"placeholder="******"data-options="required:true"validType="equalTo['pwd']"style="margin-left:10px"/>
    </p>
    $.extend($.fn.validatebox.defaults.rules,{    
         lengthTo:{
            validator:function(value,param){
                 return value.length>=param[0] &&value.length<=param[1];
            },
            message:'密码应该为5到10位'
         },
         equalTo:{
            validator:function(value,param){
              if ($("#"+param[0]).val()!=""&&value!=""){  
                    return $("#" + param[0]).val() ===value;  
                  }else{  
                 return true;  
               } 
            },
            message:'两次输入的密码不一致'
         },    
       }); 

    对form的整体验证可以使用提供的方法,但注意jquery提供的serializeArray()封装的json数据为name:''value:''格式,可能与期望json格式不符,因此可以在前端对其进行转换,将其转换为{'属性名':'属性值'}类型json。

    var isValid=$('#update_form').form('enableValidation').form('validate'); 
    function formToJson(form) {  
        var result = {};  
        var fieldArray = $(form).serializeArray();  
        for (var i = 0; i < fieldArray.length; i++) {  
            var field = fieldArray[i];  
            if (field.name in result) {  
                result[field.name] += ',' + field.value;  
            } else {  
                result[field.name]=field.value;  
            }  
        }  
       return JSON.stringify(result);  
     } 
    

    messager(消息窗口)组件主要负责显示操作辅助信息,如在ajax请求前( 显示操作进度条)、响应接收后回调函数(显示操作成功或者操作失败提示框 )  

    combobox(组合框)组件主要负责构建下拉列表框,以方便于模糊匹配设备sn码等特征信息,可以为combobox绑定一个回车监听事件(实现继承自combo组件的keyHandler方法比较麻烦,可以不使用)

    $(input_combo).combobox('textbox').keydown(function(event){
    				if(event.keyCode=='13'){  //回车键
    	                ...
    				 }
    	 });   

    datagrid(数据表格)组件主要呈现设备信息,远程请求url设置为servlet,在servlet中调用service方法获取数据库中的数据,将其转换为json后,使用输出流写出来。

    treegrid(树形表格)组件以树形式呈现数据  它所请求的json也比较特殊,必须要传一个id,注意id必须保持其唯一性,两个id相同的根节点或者子节点或者一个根节点与一个子节点,其中一个都是不能够正常使用的。

    treegrid加载初始化完成之后,有子节点的根节点可以正常的展开与关闭,对于没有子节点的根节点来说,展开该根节点时,EasyUI会默认把此节点id的值作为http请求参数并命名为'id',发送到之前设定好的url服务器端。如果此url和之前treegrid初始化url相同时(事实上确实就是相同的),程序再次响应,会输出含有所有数据的json(与之前初始化的相同),进而导致treegrid卡死。为防止此类现象产生,两种做法,一种在后端进行判断,如果含有id参数,直接返回空,另一种是前端进行判断,为treegrid添加onBeforeExpand事件,如果该根节点没有子节点,该事件直接return false,禁止展开该根节点,从而禁止请求服务器。

    onBeforeExpand:function(row){  
        if(row.children.length==0){
          return false;
        }
    },

    JSON

    EasyUI 控件获取、提交都支持JSON格式,因此使用JSON传输数据将会变得非常方便。

    但是ajax提交之前需要注意控件方法获取到的值为JSON对象,应将其使用JSON.stringify()解析为JSON字符串提交,要不然获取到的只是生硬的[Object object],其原因在于jquery提供的ajax方法会将请求数据自动转换为String格式。

    在服务器端使用json-lib.jar包,将请求传来的json数组转换成对应的javaBean 对象并进行进一步的操作,将从数据库中获取的、需要响应的javaBean对象转换成json数组,实现了与前端的数据通信。

    //解析json数据 并封装为equipmentBean对象
      JSONObject jsonObject = JSONObject.fromObject(request.getParameter("formdata"));
      equipmentBean eb=(equipmentBean)JSONObject.toBean(jsonObject,equipmentBean.class);

    DbUtils

    当使用了jdbc写了那么多条sql冗长而且容易出错语句之后,你就会发现使用DbUtils会多么简化你的代码,它实现了对jdbc最简单的封装。

    具体使用方法就是  引入c3p0.jar包与commons-dbutils.jar包,从c3p0构建的数据库连接池里获取连接,写sql执行,就这么简单。

    public class c3p0Support {
    		private static ComboPooledDataSource cpsd=new ComboPooledDataSource();  //从c3p0池中获取连接
    		public static DataSource getDataSource(){
    			return cpsd;
    		}
    }
     public ArrayList<tb_user> queryUser(String username,String password){            //查询给定用户与密码的结果集
    		  ArrayList<tb_user> result=new ArrayList<tb_user>();
    		  QueryRunner runner= new QueryRunner(c3p0Support.getDataSource()); 
    		  String sql="select * from user where username=? and password=?";
    		  Object[]params=new Object[]{username,password};
    		  try {
    			  result=(ArrayList<tb_user>)runner.query(sql,new BeanListHandler<tb_user>(tb_user.class),params);
    			} catch (SQLException e){
    				System.out.println("出现错误"+e.getErrorCode()+e.getMessage());
    				e.printStackTrace();
    			}
    			return result;
    	  }
    
    
    

    MySQL数据库

    关于数据库的设计,只设计了两种类型的表,基础属性表与关联表,基础属性表就主要是 自增主键id+设备自身属性+操作信息(操作人+时间)。
    由于数据库的现有数据是从文档中整体导入,不全面也有些错误,为避免麻烦,关联表并没有设计成外键形式,而是以  自增主键id+关联事物主键id+被关联事物主键id+操作信息(操作人+时间)。

    通过联表查询 获取并修改数据。

    创建数据库表的时候一定要设置好表的字符集和核对校验规则,我采用的是utf8编码格式与utf8_general_ci校验规则,不同类型的核对校验规则会有一定的差别,一个数据库中的所有表一般都要保持一致,不然的话,联表查询会出现错误,只能重新建表。

    测试

    开发者永远是根据开发者的思维和逻辑在构建系统和编写代码,如果他自己测试,肯定测试的不全面。

    让别人测试 可以找到很多意想不到的BUG。

    我越来越意识到在软件发布之前进行完整的测试是多么重要!很关键!

    总结

    纸上得来终觉浅,绝知此事要躬行。虽然很简单,但是我通过自己的实践获取到了一定的知识,分享出来,欢迎指正,欢迎交流。

    其实客观的评价这个系统,只是能用而已,它存在着很多很多的问题。

    比如说 交互性问题(生硬到极致的人机交互)、可维护性(换个人能不能接着开发?)、安全性问题(用户行为真的很规矩吗?系统真的很安全吗?)、可用性(只能在内网Linux服务器上跑?能不能在全市跑?能不能在全国跑?)、容灾性(tomcat崩了怎么办?数据库崩了怎么办?) 

    一个真正成熟的上千万人用的大型网站架构系统,和一个人用的系统、二三十个人用的系统、上千个人用的系统是从需求分析、设计、编码、测试、部署是有着天壤之别的,我意识到了,但是现在并没有能力来改进,哎,路漫漫其修远兮,吾将上下而求索......

    顺便趁着这篇博文,感谢一下带我进入编程世界张龙、李彦辉老师,素未谋面,神交已久。他们的声音带给我智慧、思考与力量!非常感谢!

    展开全文
  • Key-Value存储作为NoSQL存储一种常见方式,提供了比SQL数据库好的可扩展性和读写性能。比方当前开源最热门Memcached和Redis;淘宝Tair、腾讯Cmem、AmazonDynamo等等,不管是缓存还是持久存储,均使用...
  • WMS: Warehouse Management System, 仓库管理系统 1.4. 参考资料 《新编软件工程实用教程》 --------周丽娟 王华 编著 清华大学出版社 《数据库系统概论》 --------萨师煊 王珊 主编 高等教育出版社 2. 概述 ...
  •  杨廷琨(网名Yangtingkun),现任海虹医药电子商务有限公司首席DBA, ITPUB论坛Oracle数据库管理版版主。2004年曾参与编写《Oracle数据库性能优化》一书,2007年被Oracle公司授予Oracle ACE称号,喜欢研究Oracle...
  • 对于大的数据库不要设置数据库自动增长,它会降低服务器的性能。 在T-sql的写法上有很大的讲究,下面列出常见的要点:首先,DBMS处理查询计划的过程是这样的:  1、 查询语句的词法、语法检查  2、 将语句提交...
  • 感谢您选择LaiErNet企业网站管理系统,为了让LaiErNet企业网站管理系统有更好的发展空间,如果您使用,请保留LaiErNet版权信息; 解压及上传 1. 将我们提供给你安装包解压至本地目录; 2. 将目录中所有文件传至...
  • 今天在文档管理相关系统时,需要将网页上文本输入框(textarea或input)中内容,上传到ORACLE数据库的CLOB字段中去。在网上找了长时间,总算有所收获,现将方法总结如下,其中部分代码为其它网友源码: ...
  • 开博送货单打印软件标准版是一套专为小型企业或个体户及个人用于送货单管理软件,本软件依据我司在各种行业内积累丰富信息化经验而研发,结合各行各 业特点而量身订做的管理系统。本软件源于我司大型ERP系统...
  • 千里马酒店前台管理系统V7使用手册

    热门讨论 2011-06-16 14:09:38
    软件平台:服务器端和客户端是WINDOWS XP/2000/2000 SERVER/Vista 操作系统,数据库管理系统为MS-SQL SERVER 2000/2005(网络版)。 硬件平台:建议服务器至少配1G内存(建议2G)、工作站至少配512M内存(建议1G)以...
  •  芭奇站群管理系统能根据设置关键词自动抓取各大搜索引擎相关搜索词以及相关长尾词,然后根据衍生出词来抓取大量最新数据,完全摒弃普通采集软件所需繁琐规则定制,实现一键采集一键发布。  芭奇站群...
  • 在Oracle公司,Kyte专门负责Oracle数据库,他任务是帮助使用Oracle数据库的客户,并与他们共同设计和构建系统,或者对系统进行重构和调优。在进入Oracle公司之前,Kyte是一名系统集成人员,主要为美国军方和政府...

空空如也

空空如也

1 2 3 4 5 ... 16
收藏数 313
精华内容 125
关键字:

常见好做的数据库管理系统