精华内容
下载资源
问答
  • shared_ptr线程安全性分析

    万次阅读 多人点赞 2012-12-13 22:34:40
    shared_ptr线程安全性分析 正如《STL源码剖析》所讲,“源码之前,了无秘密”。本文基于shared_ptr的源代码,提取了shared_ptr的类图和对象图,然后分析了shared_ptr如何保证文档所宣称的线程安全性。本文的分析...

    shared_ptr线程安全性分析

    正如《STL源码剖析》所讲,“源码之前,了无秘密”。本文基于shared_ptr的源代码,提取了shared_ptr的类图和对象图,然后分析了shared_ptr如何保证文档所宣称的线程安全性。本文的分析基于boost 1.52版本,编译器是VC 2010。

    shared_ptr的线程安全性

    boost官方文档对shared_ptr线程安全性的正式表述是:shared_ptr对象提供与内置类型相同级别的线程安全性。shared_ptrobjects offer the same level of thread safety as built-in types.】具体是以下三点。

    1. 同一个shared_ptr对象可以被多线程同时读取。【A shared_ptrinstance can be "read" (accessed using only const operations)simultaneously by multiple threads.】

    2. 不同的shared_ptr对象可以被多线程同时修改(即使这些shared_ptr对象管理着同一个对象的指针)。【Different shared_ptr instances can be "written to"(accessed using mutable operations such as operator= or reset) simultaneouslyby multiple threads (even when these instances are copies, and share the samereference count underneath.) 】

    3. 任何其他并发访问的结果都是无定义的。【Any other simultaneous accesses result in undefined behavior.】

    第一种情况是对对象的并发读,自然是线程安全的。

    第二种情况下,如果两个shared_ptr对象A和B管理的是不同对象的指针,则这两个对象完全不相关,支持并发写也容易理解。但如果A和B管理的是同一个对象P的指针,则A和B需要维护一块共享的内存区域,该区域记录P指针当前的引用计数。对A和B的并发写必然涉及对该引用计数内存区的并发修改,这需要boost做额外的工作,也是本文分析的重点。

    另外weak_ptr和shared_ptr紧密相关,用户可以从weak_ptr构造出shared_ptr,也可以从shared_ptr构造weak_ptr,但是weak_ptr不涉及到对象的生命周期。由于shared_ptr的线程安全性是和weak_ptr耦合在一起的,本文的分析也涉及到weak_ptr。

    下面先从总体上看一下shared_ptr和weak_ptr的实现。

    shared_ptr的结构图

    以下是从boost源码提取出的shared_ptr和weak_ptr的类图。


    我们首先忽略虚线框内的weak_ptr部分。最高层的shared_ptr就是用户直接使用的类,它提供shared_ptr的构造、复制、重置(reset函数)、解引用、比较、隐式转换为bool等功能。它包含一个指向被管理对象的指针,用来实现解引用操作,并且组合了一个shared_count对象,用来操作引用计数。

    但shared_count类还不是引用计数类,它只是包含了一个指向引用计数类sp_counted_base的指针,功能上是对sp_counted_base操作的封装。shared_count对象的创建、复制和删除等操作,包含着对sp_counted_base的增加和减小引用计数的操作。

    最后sp_counted_base类才保存了引用计数,并且对引用计数字段提供无锁保护。它也包含了一个指向被管理对象的指针,是用来删除被管理的对象的。sp_counted_base有三个派生类,分别处理用户指定Deleter和Allocator的情况:

    1. sp_counted_impl_p:用户没有指定Deleter和Allocator

    2. sp_counted_impl_pd:用户指定了Deleter,没有指定Allocator

    3. sp_counted_impl_pda:用户指定了Deleter和 Allocator

    创建指针P的第一个shared_ptr对象的时候,子对象shared_count同时被建立, shared_count根据用户提供的参数选择创建一个特定的sp_counted_base派生类对象X。之后创建的所有管理P的shared_ptr对象都指向了这个独一无二的X。

    然后再看虚线框内的weak_ptr就清楚了。weak_ptr和shared_ptr基本上类似,只不过weak_ptr包含的是weak_count子对象,但weak_count和shared_count也都指向了sp_counted_base。

    如果上面的文字还不够清楚,下面的代码就能说明问题。

    shared_ptr<SomeObject> SP1(new SomeObject());

    shared_ptr<SomeObject> SP2=SP1;

    weak_ptr<SomeObject> WP1=SP1;

    执行完以上代码后,内存中会创建以下对象实例,其中红色箭头表示指向引用计数对象的指针,黑色箭头表示指向被管理对象的指针。


    从上面可以清楚的看出,SP1、SP2和WP1指向了同一个sp_counted_impl_p对象,这个sp_counted_impl_p对象保存引用计数,是SP1、SP2和WP1等三个对象共同操作的内存区。多线程并发修改SP1、SP2和WP1,有且只有sp_counted_impl_p对象会被并发修改,因此sp_counted_impl_p的线程安全性是shared_ptr以及weak_ptr线程安全性的关键问题。而sp_counted_impl_p的线程安全性是在其基类sp_counted_base中实现的。下面将着重分析sp_counted_base的代码。

    引用计数类sp_counted_base

    幸运的是,sp_counted_base的代码量很小,下面全文列出来,并添加有注释。

    class sp_counted_base

    {

    private:

         // 禁止复制

        sp_counted_base( sp_counted_base const & );

        sp_counted_base & operator= ( sp_counted_baseconst & );

     

         // shared_ptr的数量

        long use_count_;  

         // weak_ptr的数量+1

        long weak_count_;      

     

    public:

         // 唯一的一个构造函数,注意这里把两个计数都置为1

        sp_counted_base(): use_count_( 1 ), weak_count_( 1 ){    }

     

         // 虚基类,因此可以作为基类

        virtual ~sp_counted_base(){    }

     

         // 子类需要重载,用operator delete或者Deleter删除被管理的对象

        virtual void dispose() = 0;

     

         // 子类可以重载,用Allocator等删除当前对象

        virtual void destroy(){

            delete this;

        }

     

        virtual void * get_deleter( sp_typeinfo const & ti ) = 0;

     

         // 这个函数在根据shared_count复制shared_count的时候用到

         // 既然存在一个shared_count作为源,记为A,则只要A不释放,

         // use_count_就不会被另一个线程release()1

         // 另外,如果一个线程把A作为复制源,另一个线程释放A,执行结果是未定义的。

         void add_ref_copy(){

            _InterlockedIncrement( &use_count_ );

        }

     

         // 这个函数在根据weak_count构造shared_count的时候用到

         // 这是为了避免通过weak_count增加引用计数的时候,

         // 另外的线程却调用了release函数,清零use_count_并释放了指向的对象

        bool add_ref_lock(){

            for( ;; )

            {

                long tmp = static_cast< long const volatile& >( use_count_ );

                if( tmp == 0 ) return false;

     

                if( _InterlockedCompareExchange( &use_count_, tmp + 1, tmp ) == tmp )return true;

            }

        }

     

        void release(){

            if( _InterlockedDecrement( &use_count_ ) == 0 )

            {

                  // use_count_1变成0的时候,

                  // 1. 释放对象

                  // 2. weak_count_执行一次递减操作。这是因为在初始化的时候(use_count_01时),weak_count初始值为1

                dispose();

                weak_release();

            }

        }

     

        void weak_add_ref(){

            _InterlockedIncrement( &weak_count_ );

        }

     

         // 递减weak_count_;且在weak_count0的时候,把自己删除

        void weak_release(){

            if( _InterlockedDecrement( &weak_count_ ) == 0 )

            {

                destroy();

            }

        }

     

         // 返回引用计数。注意如果用户没有额外加锁,引用计数完全可能同时被另外的线程修改掉。

        long use_count() const{

            return static_cast<long const volatile &>( use_count_ );

        }

    };

    代码中的注释已经说明了一些问题,这里再重复一点:use_count_字段等于当前shared_ptr对象的数量,weak_count_字段等于当前weak_ptr对象的数量加1。

    首先不考虑weak_ptr的情况。根据对shared_ptr类的代码分析(代码没有列出来,但很容易找到),shared_ptr之间的复制都是调用add_ref_copy和release函数进行的。假设两个线程分别对SP1和SP2进行操作,操作的过程无非是以下三种情况:

    1. SP1和SP2都递增引用计数,即add_ref_copy被并发调用,也就是两个_InterlockedIncrement(&use_count_)并发执行,这是线程安全的。

    2. SP1和SP2都递减引用计数,即release被并发调用,也就是_InterlockedDecrement(&use_count_ )并发执行,这也是线程安全的。只不过后执行的线程负责删除对象。

    3.  SP1递增引用计数,调用add_ref_copy;SP2递减引用计数,调用release。由于SP1的存在,SP2的release操作无论如何都不会导致use_count_变为零,也就是说release中if语句的body永远不会被执行。因此,这种情况就化简为_InterlockedIncrement(&use_count_)和_InterlockedDecrement( &use_count_ )的并发执行,仍然是线程安全的。

    然后考虑weak_ptr。如果是weak_ptr之间的操作,或者从shared_ptr构造weak_ptr,都不涉及到use_count_的操作,只需要调用weak_add_ref和weak_release来操作weak_count_。与上面的分析相同,_InterlockedIncrement和_InterlockedDecrement保证了weak_add_ref和weak_release并发操作的线程安全性。但如果存在从weak_ptr构造shared_ptr的操作,则需要考虑在构造weak_ptr的过程中,被管理的对象已经被其他线程被释放的情况。如果从weak_ptr构造shared_ptr仍然是通过add_ref_copy函数完成的,则可能发生以下错误情况:

     

    线程1,从weak_ptr创建shared_ptr

    线程2,释放目前唯一存在的shared_ptr

    1

    判断use_count_大于0,等待执行add_ref_copy

     

    2

     

    调用release,use_count--。发现use_count为0,删除被管理的对象

    3

    开始执行add_ref_copy,导致 use_count递增。

    发生错误,use_count==1,但是对象已经被删除了

     

    我们自然会想,线程1在第三行结束后,再判断一次use_count是否为1,如果是1,认为对象已经删除,判断失败不就可以了吗。其实是行不通的,下面是一个反例。

     

    线程1,从weak_ptr创建shared_ptr

    线程2,释放目前唯一存在的shared_ptr

    线程3,从weak_ptr创建shared_ptr

    1

    判断use_count_大于0,等待执行add_ref_copy

     

     

    2

     

     

    判断use_count_大于0,等待执行add_ref_copy

    3

     

    调用release,use_count--。发现use_count为0,删除被管理的对象

     

    4

    开始执行add_ref_copy,导致 use_count递增。

     

     

    5

     

     

    执行add_ref_copy,导致 use_count递增。

    6

    发现use_count_ != 1,判断执行成功。

    发生错误,use_count==2,但是对象已经被删除了

     

    发现use_count_ != 1,判断执行成功。

    发生错误,use_count==2,但是对象已经被删除了

    实际上,boost从weak_ptr构造shared_ptr不是调用add_ref_copy,而是调用add_ref_lock函数。add_ref_lock是典型的无锁修改共享变量的代码,下面再把它的代码复制一遍,并添加证明注释。

        bool add_ref_lock(){

            for( ;; )

            {

                // 第一步,记录下use_count_

                long tmp = static_cast< long const volatile& >( use_count_ );

                // 第二步,如果已经被别的线程抢先清0了,则被管理的对象已经或者将要被释放,返回false

                if( tmp == 0 ) return false;

                // 第三步,如果if条件执行成功,

             // 说明在修改use_count_之前,use_count仍然是tmp,大于0

                // 也就是说use_count_在第一步和第三步之间,从来没有变为0过。

                // 这是因为use_count一旦变为0,就不可能再次累加为大于0

                // 因此,第一步和第三步之间,被管理的对象不可能被释放,返回true

                if( _InterlockedCompareExchange( &use_count_, tmp + 1, tmp ) == tmp )return true;

            }

        }

    在上面的注释中,用到了一个没有被证明的结论,“use_count一旦变为0,就不可能再次累加为大于0”。下面四条可以证明它。

    1.   use_count_是sp_counted_base类的private对象,sp_counted_base也没有友元函数,因此use_count_不会被对象外的代码修改。

    2.   成员函数add_ref_copy可以递增use_count_,但是所有对add_ref_copy函数的调用都是通过一个shared_ptr对象执行的。既然存在shared_ptr对象,use_count在递增之前一定不是0。

    3.   成员函数add_ref_lock可以递增use_count_,但正如add_ref_lock代码所示,执行第三步的时候,tmp都是大于0的,因此add_ref_lock不会使use_count_从0递增到1

    4.   其它成员函数从来不会递增use_count_

    至此,我们可以放下心来,只要add_ref_lock返回true,递增引用计数的行为就是成功的。因此从weak_ptr构造shared_ptr的行为也是完全确定的,要么add_ref_lock返回true,构造成功,要么add_ref_lock返回false,构造失败。

    综上所述,多线程通过不同的shared_ptr或者weak_ptr对象并发修改同一个引用计数对象sp_counted_base是线程安全的。而sp_counted_base对象是这些智能指针唯一操作的共享内存区,因此最终的结果就是线程安全的。

    其它操作

    前面我们分析了,不同的shared_ptr对象可以被多线程同时修改。那其它的问题呢,同一个shared_ptr对象可以对多线程同时修改吗?我们必须要注意到,前面所有的同步都是针对引用计数类sp_counted_base进行的,shared_ptr本身并没有任何同步保护。我们看下面boost文档举出来的非线程安全的例子

    // thread A

    p3.reset(new int(1));

     

    // thread B

    p3.reset(new int(2)); // undefined, multiple writes

    下面是shared_ptr类相关的代码

    template<class Y>

    void reset(Y * p)

    {

         this_type(p).swap(*this);

    }

     

    void swap(shared_ptr<T> & other)

    {

         std::swap(px, other.px);

         pn.swap(other.pn);

    }

    可以看到,reset执行了两个修改成员变量的操作,thread A和thread B的执行结果可能是非法的。。

    但是仿照内置对象的语义,boost提供了若干个原子函数,支持通过这些函数并发修改同一个shared_ptr对象。这包括atomic_store、atomic_exchange、atomic_compare_exchange等。以下是实现的代码,不再详细分析。

    template<class T>

    void atomic_store( shared_ptr<T> * p, shared_ptr<T> r ){

        boost::detail::spinlock_pool<2>::scoped_lock lock( p );

        p->swap( r );

    }

     

    template<class T>

    shared_ptr<T> atomic_exchange( shared_ptr<T> * p, shared_ptr<T> r ){

        boost::detail::spinlock & sp = boost::detail::spinlock_pool<2>::spinlock_for( p );

     

        sp.lock();

        p->swap( r );

        sp.unlock();

     

        return r;

    }

     

    template<class T>

    bool atomic_compare_exchange( shared_ptr<T> * p, shared_ptr<T> * v, shared_ptr<T> w ){

     

        boost::detail::spinlock & sp = boost::detail::spinlock_pool<2>::spinlock_for( p );

        sp.lock();

        if( p->_internal_equiv( *v ) ){

            p->swap( w );

            sp.unlock();

            return true;

        }

        else{

            shared_ptr<T> tmp( *p );

            sp.unlock();

            tmp.swap( *v );

            return false;

        }

    }

    总结

    正如boost文档所宣称的,boost为shared_ptr提供了与内置类型同级别的线程安全性。这包括:

    1. 同一个shared_ptr对象可以被多线程同时读取。

    2. 不同的shared_ptr对象可以被多线程同时修改。

    3. 同一个shared_ptr对象不能被多线程直接修改,但可以通过原子函数完成。

    如果把上面的表述中的"shared_ptr"替换为“内置类型”也完全成立。

    最后,整理这个东西的时候我也发现有些关键点很难表述清楚,这也是由于线程安全性本身比较难严格证明。如果想要完全理解,还是建议阅读shared_ptr完整的代码。shared_ptr在windows下的源代码我已经单独从boost中提取了出来,整理成了单独的文件,且去掉了不相关的条件编译指令。如果有需要的请邮件vigourjiang@gmail.com。如果上面的分析有任何错误,也请指出。


    展开全文
  • Gevent的socket协程安全性分析

    千次阅读 2015-05-24 22:18:22
    一般讨论socket的并发安全性,都是指线程的安全性。。。而且绝大多数的情况下socket都不是线程安全的。。 当然一些框架可能会对socket进行一层封装,让其成为线程安全的。。。例如java的netty框架就是如此,将socket...

    一般讨论socket的并发安全性,都是指线程的安全性。。。而且绝大多数的情况下socket都不是线程安全的。。

    当然一些框架可能会对socket进行一层封装,让其成为线程安全的。。。例如java的netty框架就是如此,将socket封装成channel,然后让channel封闭到一个线程中,那么这个channel的所有的读写都在它所在的线程中串行的进行,那么自然也就是线程安全的了。。。。。


    其实很早看Gevent的源码的时候,就已经看过这部分的东西了,当时就已经知道gevent的socket不是协程安全的,也就是说gevnet的socket不能在不同的协程中同时读取或者写。。。。

    例如我们不能同时在两个协程中调用socket.recv方法。。。。

    不过好像自己现在已经忘了,那就再看看,顺便写篇博客记录下来,以防以后又忘记了,还找不到资料


    那么为什么呢。。?我们来分析一下源代码吧,这里就拿send方法来分析,先来看看gevent的send方法的定义:

        #这里发送数据不会保证发送的数据都发送完,而是能发送多少发送多少
        #然后返回发送的数据大小
        def send(self, data, flags=0, timeout=timeout_default):
            sock = self._sock
            if timeout is timeout_default:
                timeout = self.timeout
            try:
                return sock.send(data, flags)
            except error:
                #EWOULDBLOCK 当前操作可能会阻塞 ,对于非阻塞的socket,也就是说明缓冲区已经写满了
                #那么这里要做的事情就是等待当前的write_event事件,然后再写数据
                ex = sys.exc_info()[1]
                if ex.args[0] != EWOULDBLOCK or timeout == 0.0:
                    raise
                sys.exc_clear()
                self._wait(self._write_event)
                try:
                    return sock.send(data, flags)
                except error:
                    ex2 = sys.exc_info()[1]
                    if ex2.args[0] == EWOULDBLOCK:
                        return 0
                    raise

    也就是说,如果当前没办反发送,那么就会调用_wait方法来等待_write_event事件,

        #等待某一个watcher,可以是read或者write事件,这里可以带有超时
        def _wait(self, watcher, timeout_exc=timeout('timed out')):
            """Block the current greenlet until *watcher* has pending events.
    
            If *timeout* is non-negative, then *timeout_exc* is raised after *timeout* second has passed.
            By default *timeout_exc* is ``socket.timeout('timed out')``.
    
            If :func:`cancel_wait` is called, raise ``socket.error(EBADF, 'File descriptor was closed in another greenlet')``.
            """
            assert watcher.callback is None, 'This socket is already used by another greenlet: %r' % (watcher.callback, )
            if self.timeout is not None: #在等待之前,先挂起timeout,如果超市了,将会执行timeout
                #如果超时先结束,那么会返回到当前协程抛出异常
                timeout = Timeout.start_new(self.timeout, timeout_exc, ref=False)
            else:
                timeout = None
            try:
                self.hub.wait(watcher)  #在hub上面等待这个watcher,在这个里面会切换到hub的运行,然后等到watcher有反应了再切换回来
            finally:
                if timeout is not None:
                    timeout.cancel()

    其实这里很简单,就是在hub上面等待当前socket的写事件的watcher

        #当在调用gevent.sleep的时候如果传入了大于零的时间,将会用这里来处理
        #watcher是一个在loop上面注册的事件,可能是读,写或者定时
        #用于在loop上面注册watcher,然后将当前协程切换出去
        def wait(self, watcher):
            waiter = Waiter() #首先创建一个waiter对象
            unique = object() 
            watcher.start(waiter.switch, unique) #当watcher超时的时候将会调用waiter的switch方法这样就可以切换回来当前的协程
            try:
                result = waiter.get() #调用waiter的get方法,主要是让将当前调用sleep的greenlet切换出去,然后切换到hub的运行
                assert result is unique, 'Invalid switch into %s: %r (expected %r)' % (getcurrent(), result, unique)
            finally:
                watcher.stop()
    

    这个应该很简单吧。。创建一个waiter事件,然后调用watcher对象的start方法,回调方法就设置成当前waiter对象的switch方法,然后调用get方法,将当前的协程切换出去。。。。


    那么来来IO类型的watcher的start方法吧:

    #I/Owatcher的定义
    cdef public class io(watcher) [object PyGeventIOObject, type PyGeventIO_Type]:
    
        WATCHER_BASE(io)  #通过这个宏定义了一些基本的属性,例如libev的 watcher 引用等
    
        #这个其实其实就是启动watcher
        #这里的callback一般情况下都是waiter对象的switch方法,这样,当有IO事件之后就可以回到之前的协程了
        def start(self, object callback, *args, pass_events=False):
            CHECK_LOOP2(self.loop)
            if callback is None:
                raise TypeError('callback must be callable, not None')
            self.callback = callback
            if pass_events:
                self.args = (GEVENT_CORE_EVENTS, ) + args
            else:
                self.args = args
            LIBEV_UNREF
            libev.ev_io_start(self.loop._ptr, &self._watcher)  #在libev的loop上面启动这个io watcher
            PYTHON_INCREF
    
        ACTIVE
    

    嗯,这个是cython的代码,所以稍微别扭一些。。。,那么接下来再来看看libev的ev_io_start函数的实现吧:

    void noinline
    ev_io_start (EV_P_ ev_io *w) EV_THROW
    {
      int fd = w->fd;
    
      if (expect_false (ev_is_active (w)))
        return;
    
      assert (("libev: ev_io_start called with negative fd", fd >= 0));
      assert (("libev: ev_io_start called with illegal event mask", !(w->events & ~(EV__IOFDSET | EV_READ | EV_WRITE))));
    
      EV_FREQUENT_CHECK;
    
      ev_start (EV_A_ (W)w, 1);   //将监视器设置为active
      //判断当前的anfds的大小是否足够放入新的fd,如果不够的话,那么需要重新分配
      array_needsize (ANFD, anfds, anfdmax, fd + 1, array_init_zero);
      //将这个watcher放到当前fd的wather队列的头部
      wlist_add (&anfds[fd].head, (WL)w);
    
      /* common bug, apparently */
      assert (("libev: ev_io_start called with corrupted watcher", ((WL)w)->next != (WL)w));
    
      fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY);   //将该fd放到需要改变的数组,在合适的时候将会在loop上修改
      w->events &= ~EV__IOFDSET;
    
      EV_FREQUENT_CHECK;
    }

    这里主要就是激活当前的watcher对象,然后将这个watcher对象放到当前文件描述符的watcher链表的头部。。。嗯。。也就是wlist_add方法要做的事情。。。其实看到这里就知道socket肯定不是协程安全的了。。。


    嗯,相信大家也一定懂了。。。。不懂的话。。就再看看代码就知道了。。。。。

    展开全文
  • 前言本文将介绍在Spring MVC开发的web系统中,获取request对象的几种方法,并讨论其线程安全性。目录概述如何测试线程安全性方法1:Controller中加参数方法2:自动注入方法3:基类中自动注入方法4:手动调用方法5:@...

    前言

    本文将介绍在Spring MVC开发的web系统中,获取request对象的几种方法,并讨论其线程安全性。

    目录

    概述

    如何测试线程安全性

    方法1:Controller中加参数

    方法2:自动注入

    方法3:基类中自动注入

    方法4:手动调用

    方法5:@ModelAttribute方法

    总结

    概述

    在使用Spring MVC开发Web系统时,经常需要在处理请求时使用request对象,比如获取客户端ip地址、请求的url、header中的属性(如cookie、授权信息)、body中的数据等。由于在Spring MVC中,处理请求的Controller、Service等对象都是单例的,因此获取request对象时最需要注意的问题,便是request对象是否是线程安全的:当有大量并发请求时,能否保证不同请求/线程中使用不同的request对象。

    这里还有一个问题需要注意:前面所说的“在处理请求时”使用request对象,究竟是在哪里使用呢?考虑到获取request对象的方法有微小的不同,大体可以分为两类:

    1)      在Spring的Bean中使用request对象:既包括Controller、Service、Repository等MVC的Bean,也包括了Component等普通的Spring Bean。为了方便说明,后文中Spring中的Bean一律简称为Bean。

    2)      在非Bean中使用request对象:如普通的Java对象的方法中使用,或在类的静态方法中使用。

    此外,本文讨论是围绕代表请求的request对象展开的,但所用方法同样适用于response对象、InputStream/Reader、OutputStream/ Writer等;其中InputStream/Reader可以读取请求中的数据,OutputStream/ Writer可以向响应写入数据。

    最后,获取request对象的方法与Spring及MVC的版本也有关系;本文基于Spring4进行讨论,且所做的实验都是使用4.1.1版本。

    如何测试线程安全性

    既然request对象的线程安全问题需要特别关注,为了便于后面的讨论,下面先说明如何测试request对象是否是线程安全的。

    测试的基本思路,是模拟客户端大量并发请求,然后在服务器判断这些请求是否使用了相同的request对象。

    判断request对象是否相同,最直观的方式是打印出request对象的地址,如果相同则说明使用了相同的对象。然而,在几乎所有web服务器的实现中,都使用了线程池,这样就导致先后到达的两个请求,可能由同一个线程处理:在前一个请求处理完成后,线程池收回该线程,并将该线程重新分配给了后面的请求。而在同一线程中,使用的request对象很可能是同一个(地址相同,属性不同)。因此即便是对于线程安全的方法,不同的请求使用的request对象地址也可能相同。

    为了避免这个问题,一种方法是在请求处理过程中使线程休眠几秒,这样可以让每个线程工作的时间足够长,从而避免同一个线程分配给不同的请求;另一种方法,是使用request的其他属性(如参数、header、body等)作为request是否线程安全的依据,因为即便不同的请求先后使用了同一个线程(request对象地址也相同),只要使用不同的属性分别构造了两次request对象,那么request对象的使用就是线程安全的。本文使用第二种方法进行测试。

    客户端测试代码如下(创建1000个线程分别发送请求):

    public class Test {
    	public static void main(String[] args) throws Exception {
    		String prefix = UUID.randomUUID().toString().replaceAll("-", "") + "::";
    		for (int i = 0; i < 1000; i++) {
    			final String value = prefix + i;
    			new Thread() {
    				@Override
    				public void run() {
    					try {
    						CloseableHttpClient httpClient = HttpClients.createDefault();
    						HttpGet httpGet = new HttpGet("http://localhost:8080/test?key=" + value);
    						httpClient.execute(httpGet);
    						httpClient.close();
    					} catch (IOException e) {
    						e.printStackTrace();
    					}
    				}
    			}.start();
    		}
    	}
    }

    服务器中Controller代码如下(暂时省略了获取request对象的代码):

    @Controller
    public class TestController {
    
    	// 存储已有参数,用于判断参数是否重复,从而判断线程是否安全
    	public static Set<String> set = new HashSet<>();
    
    	@RequestMapping("/test")
    	public void test() throws InterruptedException {
    		
    		// …………………………通过某种方式获得了request对象………………………………
    
    		// 判断线程安全
    		String value = request.getParameter("key");
    		if (set.contains(value)) {
    			System.out.println(value + "\t重复出现,request并发不安全!");
    		} else {
    			System.out.println(value);
    			set.add(value);
    		}
    		
    		// 模拟程序执行了一段时间
    		Thread.sleep(1000);
    	}
    }
    

    如果request对象线程安全,服务器中打印结果如下所示:


    如果存在线程安全问题,服务器中打印结果可能如下所示:


    如无特殊说明,本文后面的代码中将省略掉测试代码。

    方法1:Controller中加参数

    代码示例

    这种方法实现最简单,直接上Controller代码:

    @Controller
    public class TestController {
    	@RequestMapping("/test")
    	public void test(HttpServletRequest request) throws InterruptedException {
    		// 模拟程序执行了一段时间
    		Thread.sleep(1000);
    	}
    }
    

    该方法实现的原理是,在Controller方法开始处理请求时,Spring会将request对象赋值到方法参数中。除了request对象,可以通过这种方法获取的参数还有很多,具体可以参见:https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-methods

    Controller中获取request对象后,如果要在其他方法中(如service方法、工具类方法等)使用request对象,需要在调用这些方法时将request对象作为参数传入。

    线程安全性

    测试结果:线程安全

    分析:此时request对象是方法参数,相当于局部变量,毫无疑问是线程安全的。

    优缺点

    这种方法的主要缺点是request对象写起来冗余太多,主要体现在两点:

    1)      如果多个controller方法中都需要request对象,那么在每个方法中都需要添加一遍request参数

    2)      request对象的获取只能从controller开始,如果使用request对象的地方在函数调用层级比较深的地方,那么整个调用链上的所有方法都需要添加request参数

    实际上,在整个请求处理的过程中,request对象是贯穿始终的;也就是说,除了定时器等特殊情况,request对象相当于线程内部的一个全局变量。而该方法,相当于将这个全局变量,传来传去。

    方法2:自动注入

    代码示例

    先上代码:

    @Controller
    public class TestController{
    	
    	@Autowired
    	private HttpServletRequest request; //自动注入request
    	
    	@RequestMapping("/test")
    	public void test() throws InterruptedException{
    		//模拟程序执行了一段时间
    		Thread.sleep(1000);
    	}
    }

    线程安全性

    测试结果:线程安全

    分析:在Spring中,Controller的scope是singleton(单例),也就是说在整个web系统中,只有一个TestController;但是其中注入的request却是线程安全的,原因在于:

    使用这种方式,当Bean(本例的TestController)初始化时,Spring并没有注入一个request对象,而是注入了一个代理(proxy);当Bean中需要使用request对象时,通过该代理获取request对象。

     

    下面通过具体的代码对这一实现进行说明。

    在上述代码中加入断点,查看request对象的属性,如下图所示:


    在图中可以看出,request实际上是一个代理:代理的实现参见AutowireUtils的内部类ObjectFactoryDelegatingInvocationHandler:

    	/**
    	 * Reflective InvocationHandler for lazy access to the current target object.
    	 */
    	@SuppressWarnings("serial")
    	private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {
    		private final ObjectFactory<?> objectFactory;
    		public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {
    			this.objectFactory = objectFactory;
    		}
    		@Override
    		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    			// ……省略无关代码
    			try {
    				return method.invoke(this.objectFactory.getObject(), args); // 代理实现核心代码
    			}
    			catch (InvocationTargetException ex) {
    				throw ex.getTargetException();
    			}
    		}
    	}

    也就是说,当我们调用request的方法method时,实际上是调用了由objectFactory.getObject()生成的对象的method方法;objectFactory.getObject()生成的对象才是真正的request对象。

    继续观察上图,发现objectFactory的类型为WebApplicationContextUtils的内部类RequestObjectFactory;而RequestObjectFactory代码如下:

    	/**
    	 * Factory that exposes the current request object on demand.
    	 */
    	@SuppressWarnings("serial")
    	private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
    		@Override
    		public ServletRequest getObject() {
    			return currentRequestAttributes().getRequest();
    		}
    		@Override
    		public String toString() {
    			return "Current HttpServletRequest";
    		}
    	}

    其中,要获得request对象需要先调用currentRequestAttributes()方法获得RequestAttributes对象,该方法的实现如下:

    	/**
    	 * Return the current RequestAttributes instance as ServletRequestAttributes.
    	 */
    	private static ServletRequestAttributes currentRequestAttributes() {
    		RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
    		if (!(requestAttr instanceof ServletRequestAttributes)) {
    			throw new IllegalStateException("Current request is not a servlet request");
    		}
    		return (ServletRequestAttributes) requestAttr;
    	}

    生成RequestAttributes对象的核心代码在类RequestContextHolder中,其中相关代码如下(省略了该类中的无关代码):

    public abstract class RequestContextHolder {
    	public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
    		RequestAttributes attributes = getRequestAttributes();
    		// 此处省略不相关逻辑…………
    		return attributes;
    	}
    	public static RequestAttributes getRequestAttributes() {
    		RequestAttributes attributes = requestAttributesHolder.get();
    		if (attributes == null) {
    			attributes = inheritableRequestAttributesHolder.get();
    		}
    		return attributes;
    	}
    	private static final ThreadLocal<RequestAttributes> requestAttributesHolder = 
    			new NamedThreadLocal<RequestAttributes>("Request attributes");
    	private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = 
    			new NamedInheritableThreadLocal<RequestAttributes>("Request context");
    }
    

    通过这段代码可以看出,生成的RequestAttributes对象是线程局部变量(ThreadLocal),因此request对象也是线程局部变量;这就保证了request对象的线程安全性。

    优缺点

    该方法的主要优点:

    1)      注入不局限于Controller中:在方法1中,只能在Controller中加入request参数。而对于方法2,不仅可以在Controller中注入,还可以在任何Bean中注入,包括Service、Repository及普通的Bean。

    2)      注入的对象不限于request:除了注入request对象,该方法还可以注入其他scope为request或session的对象,如response对象、session对象等;并保证线程安全。

    3)      减少代码冗余:只需要在需要request对象的Bean中注入request对象,便可以在该Bean的各个方法中使用,与方法1相比大大减少了代码冗余。

    但是,该方法也会存在代码冗余。考虑这样的场景:web系统中有很多controller,每个controller中都会使用request对象(这种场景实际上非常频繁),这时就需要写很多次注入request的代码;如果还需要注入response,代码就更繁琐了。下面说明自动注入方法的改进方法,并分析其线程安全性及优缺点。

    方法3:基类中自动注入

    代码示例

    与方法2相比,将注入部分代码放入到了基类中。

    基类代码:

    public class BaseController {
        @Autowired
        protected HttpServletRequest request;      
    }

    Controller代码如下;这里列举了BaseController的两个派生类,由于此时测试代码会有所不同,因此服务端测试代码没有省略;客户端也需要进行相应的修改(同时向2个url发送大量并发请求)。

    @Controller
    public class TestController extends BaseController {
    
    	// 存储已有参数,用于判断参数value是否重复,从而判断线程是否安全
    	public static Set<String> set = new HashSet<>();
    
    	@RequestMapping("/test")
    	public void test() throws InterruptedException {
    		String value = request.getParameter("key");
    		// 判断线程安全
    		if (set.contains(value)) {
    			System.out.println(value + "\t重复出现,request并发不安全!");
    		} else {
    			System.out.println(value);
    			set.add(value);
    		}
    		// 模拟程序执行了一段时间
    		Thread.sleep(1000);
    	}
    }
    
    @Controller
    public class Test2Controller extends BaseController {
    	@RequestMapping("/test2")
    	public void test2() throws InterruptedException {
    		String value = request.getParameter("key");
    		// 判断线程安全(与TestController使用一个set进行判断)
    		if (TestController.set.contains(value)) {
    			System.out.println(value + "\t重复出现,request并发不安全!");
    		} else {
    			System.out.println(value);
    			TestController.set.add(value);
    		}
    		// 模拟程序执行了一段时间
    		Thread.sleep(1000);
    	}
    }

    线程安全性

    测试结果:线程安全

    分析:在理解了方法2的线程安全性的基础上,很容易理解方法3是线程安全的:当创建不同的派生类对象时,基类中的域(这里是注入的request)在不同的派生类对象中会占据不同的内存空间,也就是说将注入request的代码放在基类中对线程安全性没有任何影响;测试结果也证明了这一点。

    优缺点

    与方法2相比,避免了在不同的Controller中重复注入request;但是考虑到java只允许继承一个基类,所以如果Controller需要继承其他类时,该方法便不再好用。

    无论是方法2和方法3,都只能在Bean中注入request;如果其他方法(如工具类中static方法)需要使用request对象,则需要在调用这些方法时将request参数传递进去。下面介绍的方法4,则可以直接在诸如工具类中的static方法中使用request对象(当然在各种Bean中也可以使用)。

    方法4:手动调用

    代码示例

    @Controller
    public class TestController {
    	@RequestMapping("/test")
    	public void test() throws InterruptedException {
    		HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
    		// 模拟程序执行了一段时间
    		Thread.sleep(1000);
    	}
    }

    线程安全性

    测试结果:线程安全

    分析:该方法与方法2(自动注入)类似,只不过方法2中通过自动注入实现,本方法通过手动方法调用实现。因此本方法也是线程安全的。

    优缺点

    优点:可以在非Bean中直接获取。缺点:如果使用的地方较多,代码非常繁琐;因此可以与其他方法配合使用。

    方法5:@ModelAttribute方法

    代码示例

    下面这种方法及其变种(变种:将request和bindRequest放在子类中)在网上经常见到:

    @Controller
    public class TestController {
    	private HttpServletRequest request;
    	@ModelAttribute
    	public void bindRequest(HttpServletRequest request) {
    		this.request = request;
    	}
    	@RequestMapping("/test")
    	public void test() throws InterruptedException {
    		// 模拟程序执行了一段时间
    		Thread.sleep(1000);
    	}
    }

    线程安全性

    测试结果:线程不安全

    分析:@ModelAttribute注解用在Controller中修饰方法时,其作用是Controller中的每个@RequestMapping方法执行前,该方法都会执行。因此在本例中,bindRequest()的作用是在test()执行前为request对象赋值。虽然bindRequest()中的参数request本身是线程安全的,但由于TestController是单例的,request作为TestController的一个域,无法保证线程安全。

    总结

    综上所述,Controller中加参数(方法1)、自动注入(方法2和方法3)、手动调用(方法4)都是线程安全的,都可以用来获取request对象。如果系统中request对象使用较少,则使用哪种方式均可;如果使用较多,建议使用自动注入(方法2 和方法3)来减少代码冗余。如果需要在非Bean中使用request对象,既可以在上层调用时通过参数传入,也可以直接在方法中通过手动调用(方法4)获得。


    原文链接:http://www.cnblogs.com/kismetv/p/8757260.html

    展开全文
  • 最近工作上涉及到对Android系统安全性的改造,在改造之前先分析整理下目前Android系统自身的安全性; 参考了一些文章及书籍,在这里大部分是对别人描述的提炼,我挑出一些对我有用的内容整理; 0 在前面的    ...

    声明

    1. 最近工作上涉及到对Android系统安全性的改造,在改造之前先分析整理下目前Android系统自身的安全性;
    2. 参考了一些文章及书籍,在这里大部分是对别人描述的提炼,我挑出一些对我有用的内容整理;

    0 写在前面的

        在刚接触Android时就知道他的底层内核是Linux,其实换一种理解角度的话Android系统其实类似于Linux在移动设备上的一个发行版,只是这个发行版在内核层加入了独有的驱动、在文件系统中加入了Dalvik/ART虚拟机以运行Java代码。所以,这样看来Android的安全性自然就是由虚拟机层和原生代码层这两个层面共同提供的了:

    • Android依靠底层的Linux基础设施来实现基本安全需求;
    • 针对大部分Android应用来说,它们还受到了由Dalvik/ART虚拟机执行的更高一层的安全保护;

    1 系统失守的原因

        移动设备在某些方面和个人电脑有些类似,移动设备面对的威胁方面主要是:

    • 移动设备的高度便携性, 比如它们可能会被无意间放错地方,或者不小心被偷;
    • 移动设备更加私人化,更有可能会记录用户的个人隐私信息,用户私人数据就成为黑客攻击目标;

    1.1 安装的APP是否可靠–本地提权

        坚固的防御常常从内部瓦解,移动设备的主要攻击向量来自它的内部不靠谱的APP。如果其中哪个应用中出现了错误的行为,或者是有意诱使你安装的恶意应用,它就能试图访问用户数据,甚至是使用于机通信的功能(比如发送诈骗短信)牟利。一般这类攻击会被归类为“本地提权”(local privilege escalation)攻击一一因为攻击发生时APP已经被安装并运行在移动设备中了,它只拥有一些受限制的权限,并想要提升自己的权限。

        为了防止出现本地提权。在默认情况下,应用将只被赋予最小权限集中的权限,除了这个最小权限集之外,所有的权限都是被禁止的。这个最小权限集中是不包括任何可能是敏感的权限的。比如,使用网络权限可能会被恶意地用来把设备中的信息传出去,因此它就不在最小权限集中。当应用需要任何一个不在最小权限集中的权限时,它就必须在Manifest文件里显式地声明,要求系统赋予它这个权限。

        从Android 4.4开始,通过引入SELinux, Android使用一个强制实施的访问控制框架,有效地把所有的进程置于沙箱里,将其束缚在可信区域内。到了Android Lollipop 版,这些框架又被进一步扩展,己经能支持对包(package)进行限制了。

        即使一个APP确实只拥有很少的一些权限,但它还是可以试图攻击它所在系统中具有安全漏洞的组件。利用这些安全漏洞,进而控制操作系统中拥有更多权限的组件特别是以root权限运行的组件。由于Android框架的实现是由极其大量的代码组成的,更底层的Linux内核中的代码更多,不可能保证每一行代码都没有问题。

    1.2 用户是否可靠

        因为手机作为移动设备很容易被偷,手机需要某种方式知道当前操作自己的是不是真正的主人:

    1. 屏幕密码解锁
    2. 指纹、人脸解锁
    3. Boot Loader加锁:试想如果小偷懂IT,偷了你的手机后,用技术手段给你刷机了,你之前的密码、指纹、人脸什么的都没用了,这也是Android设备默认都会对Boot Loader加锁原因之一,同时一旦Boot Loader被解锁,整个/data分区中的数据会马上被擦除掉;
    4. 数据加密:如果到了黑客级别的高手,他会直接从底层访问闪存中的二进制数据,这种方式听着有点恐怖了,估计除非手机里有绝密级别的东西才会被这样针对;值得注意的是加密密钥是由用户锁屏密码推算出来的,而绝不能直接存储在手机中;

    1.3 远程代码注入

        移动设备仍然还要面临和服务器及桌面电脑一样的远程代码注入的威胁。因为Android浏览器Webkit已被证明存在很多漏洞,虽然AOSP的代码里默认使用它作为浏览器,但是Google的Nexus和Pixel这些手机的默认factory包里的浏览器已经默认是Chrome了。

    2 系统的漏洞来源

        Android的复杂性决定了它受攻击的方向太多:

                

    2.1 源自Android

        这些漏洞是来自于AOSP本身的。可能位于框架的代码中,也可能位于更底层的系统守护进程中。Android中的大部分守护进程都已经惨遭被重写的命运,使它们无需拥有root权限就能正常工作,所以大部分守护进程只得到了一个系统AID。只有很少的几个守护进程(vold和lmkd)还在以root权限运行。不过从Android L 版开始,这些进程也受到了SELinux的约束,以增强Android的安全性。

    2.2 源自厂商

        这些漏洞都是出现在厂商(或运营商)在设备中新增的代码或服务(比如:我就经常在系统里加一些守护进程或内置一些APP,如果我的代码不严谨,很可能存在漏洞)。这些问题没出在Android AOSP自身代码中,而是出在那些额外增加进来的组件中,而且这些组件常常还是以超出自身所需的权限运行,又没有足够的安全防护的。

    2.3 源自第三方

        Android中使用了大量的其他开源项目,它们为Android提供了许多原生库(Native Library )以及系统中的守护进程(比如wpa_supplicant 、mdnsd 、racoon等),这些代码中很可能存在安全漏洞。

    2.4 源自Linux

        Android非常依赖于它的基础设施——Linux,但系统安全有一句行话——“信赖往往会变成负担” 。Android不光是(几乎一字不改地)使用了Linux的内核,在大部分关键组件上照抄了Linux 。因此,在Linux内核中发现的任何漏洞,几乎都可以用来黑掉Android设备。

    3 结论

        所以,Android整体的防御措施主要分布在Linux层、Dalvik层、用户层、存储四个方面,后面我再写4篇分别总结下这四个层次的安全防御措施。

    展开全文
  • 本文讲述了OpenCV中几种访问矩阵元素的方法,在指定平台上给出性能比较,分析每种矩阵元素访问方法的代码复杂度,易用
  • RSA公钥密码体制安全性分析

    千次阅读 2005-12-12 21:29:00
    摘要:随着通信的飞速发展,信息安全也越来越显得重要。...密码体制有对称密钥体制和...关键词:RSA公钥密码体制 优势 安全性引言RSA密码系统是较早提出的一种公开钥密码系统。1978年,美国麻省理工学院(M
  • 题记:若干年后,将来的新人类《新史记》的时候,the DAO事件应该会是一个重要的篇章。由于区块链的不可篡改的特性和存证的特长,the DAO事件的细节被完整地保存了下来,包括攻防双方的在以太坊区块链上的具体动作...
  • 两者都是C/C++里面的字符串拷贝函数,不同的是后者多了一个参数,此参数可以指定从源拷贝多长。char* strcpy(char* strDest, const char* ...下面开始介绍两个的安全性,strcpy函数: 如果参数 dest 所指的内存空间
  • 读写锁ReentrantReadWriteLock源码分析

    千次阅读 2019-10-09 20:46:19
    文章目录读写锁的介绍锁详解锁的获取锁的释放读锁详解读锁的获取读锁的释放锁降级 读写锁的介绍 在并发场景中用于解决线程安全的问题,我们几乎会高频率的使用到独占式锁,通常使用java提供的关键字...
  • 用户登录安全性的简单实例分析(Cookie、加密) 关键字:Cookie;DES加解密算法;安全性;权限;下面是以前的一篇文章,不一定会在目前的实际项目中应用,所以仅作为个人的业余读书、业余爱好。 从事hack的朋友...
  • APP安全性测试

    千次阅读 2017-11-21 21:34:44
     随着互联网发展,APP应用的盛行,最近了解到手机APP相关的安全性测试,以webview为主体的app,站在入侵或者攻击的角度来讲,安全隐患在于http抓包,逆向工程。  目前大部分app还是走的http或者https,所以防http...
  • 提高微服务安全性的11个方法

    千次阅读 多人点赞 2020-12-21 08:41:47
    原文发表于kubernetes中文社区,为作者原创翻译,原文地址 更多kubernetes文章,请多关注kubernetes中文社区 目录 为什么选择微服务?...6.通过交付流水线验证安全性 7.降低攻击者的速度 8.使用Docker Rootl..
  • SEAndroid安全机制框架分析

    万次阅读 多人点赞 2014-07-14 01:00:02
    针对传统Linux系统,NSA开发了一套安全机制SELinux,用来加强安全性。然而,由于Android系统有着独特的用户空间运行时,因此SELinux不能完全适用于Android系统。为此,NSA针对Android系统,在SELinux基础上开发了...
  • websocket安全分析

    万次阅读 2016-11-17 14:13:52
    本文的主要贡献是回顾和分析了与WS相关的安全问题,讨论了可能的解决方法以及部署WS的最佳实践。同样,这篇论文提出了在web浏览器中应该有一些安全的特性去余额宝用户的安全。浏览器供应商在提供安全特性方面充当着...
  • 软件安全性测试

    万次阅读 2017-07-08 23:18:44
    软件安全性是一个广泛而复杂的主题,每一个新的软件总可能有完全不符合所有已知模式的新型安全性缺陷出现。要避免因安全性缺陷问题受各种可能类型的攻击是不切实际的。在软件安全测试时,运用一组好的原则来避免不...
  • IIS配置 安全性配置

    千次阅读 2013-07-16 10:33:47
    最近,和朋友们在聊及ASP.NET程序的安全性没有JAVA高,IIS(Internet Infomartion Server)的存在很多漏洞(以及新型蠕虫,例如Code Red 和Nimda),安全得不到保障。针对IIS的安全性查了些资料,发现IIS的安全性曾被...
  • 实验三 数据库的安全性和完整性控制 实验教室 913 实验日期 2018年10月22日 学 号 2016214220 姓 名 ** 专.....
  • 文章目录可见定义导致不可见的原因可见 -synchronized (既保证原子又保证可见)可见 - volatile(但不保证操作的原子)volatile变量 操作volatile变量 读操作使用volatile尝试解决计数并发错误的问题 ...
  • hadoop安全性问题

    千次阅读 2014-05-28 16:55:16
    依据数据的敏感程度,我们可能要确保数据分析师能看到的数据是可以让他们分析的数据,并且必须明白发布这些数据及其分析结果可能产生的后果。仅Netflix数据泄漏一个案例就足以表明,即使已经试图对数据做了“匿名化...
  • 学习需求分析

    万次阅读 多人点赞 2017-04-17 21:34:51
    笔者本身是软件工程专业出身,但是对如何需求分析仍然是一知半解,拿到需求,仍然不知道如何下手,才能达到需求分析的目的。 今天看到一篇文章,让我受益良多,同时参考此文,笔者也尝试了一个需求分析,一个...
  • 游戏安全性测试总结

    千次阅读 2019-05-14 19:46:00
    手机游戏安全性日显重要,在上线之前已成为一个不可忽视的测试指标。 但是有关手机游戏安全性测试又任重道远…… 上线之后,依然出现各种问题,这里只单独讲下游戏安全这一块这些年的一些总结。 具体分析见后续单独...
  • 数据的安全性: 保护数据库以防止不合法的使用所造成的数据泄露、更改、或破坏。 ** 数据库的不安全因素: ** 非授权用户对数据库的恶意存取和破坏。 数据库中重要或敏感的数据被泄露。 安全环境的脆弱性。 数据...
  • 数据库-数据库安全性

    千次阅读 2019-09-02 23:46:10
    这篇博客内容有些琐碎繁杂,我整理的时候有很多上课时老师没有讲的,但我自己在看的时候看了看...数据库安全性 1、数据库安全性概述 1)、数据库的不完全因素 2)、安全标准简介 2、数据库安全性控制 1)、用户...
  • 【数据库系统设计】数据库安全性

    千次阅读 2020-04-04 22:46:24
    数据库安全性4.1 数据库安全性概述4.1.1 数据库的不安全因素4.2 数据库安全性控制4.2.1 用户身份鉴别4.2.2 存取控制4.2.3 自主存取控制方法4.2.4 授权:授予与回收4.2.5 数据库角色4.2.6 强制存取控制方法4.3 视图...
  • 讨论“get”和“post”安全性

    万次阅读 热门讨论 2014-01-19 19:50:52
    这或许是大家总结两者必须要分析的内容,因为这涉及到我们将内容从浏览器传送到服务器的安全性,选择不当将会带来巨大的不安全因素,从而可能带来巨大的损失。这篇博客,我将阐述一下,当然更多的还是希望各位大神...
  • 如何做好网站的安全性测试

    万次阅读 2013-08-29 09:11:26
    安全性测试并不最终证明应用程序是安全的,而是用于验证所设立策略的有效性,这些对策是基于威胁分析阶段所做的假设而选择的。 一个完整的WEB安全性测试可以从部署与基础结构、输入验证、身
  • 论安卓系统安全性

    千次阅读 2020-05-01 13:16:34
    但它们的安全性一致来说是非常危险的,当然不是说国产系统的不好,这也是由于安卓本身开源的问题,没有ios系统闭源,所有应用都由苹果把关。 现在由我去搞一个普通用户,我只需要拿到一些相应的权限即可。这些权限...
  • 安全U盘市场分析

    千次阅读 2019-07-01 20:13:03
    权限分级管理、单一文件加密、口令错误锁定、口令错误销毁、账号保险箱、写保护、安全区与普通区分离、安全区可见、防止截图等。 设计机制: 1.进不去:非法用户无法访问用户数据。 2.拿不走:非法用户无法...
  • SEAndroid安全机制中的文件安全上下文关联分析

    万次阅读 热门讨论 2014-07-21 00:59:43
    前面一篇文章提到,SEAndroid是一种基于安全策略的MAC安全机制。这种安全策略实施在主体和客体的安全上下文之上。...本文主要分析文件安全上下文的设置过程,接下来的一篇文章再分析进程安全上下文的设置过程。
  • android lint 安全分析

    千次阅读 2014-09-04 20:37:06
    Androidlint 安全性检查简介 1、 lint简介 lint是一个比较出名的C语言工具,用来静态分析代码。与大多数C语言编译器相比,lint可以对程序进行更加广泛的错误分析,是一种更加严密的编译工具。最初,lint这个工具...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 409,179
精华内容 163,671
关键字:

安全性分析怎么写