当前位置: 首页 > news >正文

做彩票网站代理沈阳百度推广排名优化

做彩票网站代理,沈阳百度推广排名优化,wordpress 的分享插件下载地址,杭州专业程序开发公司一.常见的锁策略 这里所讲的锁,不是一把具体的锁,而是锁的特性 1.乐观锁和悲观锁 悲观乐观是对锁冲突大小的预测 若预测锁冲突概率不大,就可能会少一些工作,那就是乐观锁;反之就是悲观锁 总是假设最坏的情况&…

一.常见的锁策略

这里所讲的锁,不是一把具体的锁,而是锁的特性

1.乐观锁和悲观锁

悲观乐观是对锁冲突大小的预测

若预测锁冲突概率不大,就可能会少一些工作,那就是乐观锁;反之就是悲观锁

总是假设最坏的情况,每次读取数据时都认为别人要改数据,所以在每次读取数据时都要上锁。这就是悲观锁

总是假设一般情况下不会产生冲突,所以在对数据进行提交更新时才会正式对数据是否产生并发冲突进行检查,如果发现冲突了,就返回错误的信息,让用户决定接下来怎么做

2.重量级锁和轻量级锁

锁的开销大就是重量级锁,对应悲观锁;锁的开销小就是轻量级锁,对应乐观锁

3.自旋锁和挂起等待锁

自旋锁是一种轻量级锁的典型实现;挂起等待锁是一种重量级锁的典型实现

自旋锁往往是一种纯用户态的实现。在之前,如果一个线程没有拿到锁,就会进入阻塞等待,也就是放弃cpu,需要过很久才能再次被调度。但是要是用了自旋锁,就不是这样的了。自旋锁相当于有一个while循环,如果获取锁失败,就会立即进行下一次获取锁的尝试,无限循环,直到第一次获得到锁。(这就是我们所说的忙等,虽然很消耗cpu,但是换来的却是更快的响应速度,因为他没有放弃cpu,当得到锁时,不用再次等待cpu调度的过程,而是直接就可以进行程序)

挂起等待锁:当线程没有申请到锁时,就会被挂起等待,即加入到等待队列阻塞等待;当锁被释放时,再重新竞争。(所以自旋锁也可以说为:当线程没有获得锁时,不会被观其等待,而是隔一段时间再去检查一下锁是否被释放)

4.读写锁

这里的读写锁和数据库的读写锁不同(数据库中,写加锁就是再写时不可以读,读加锁就是在读时不可以写。)

在这里,读加锁就是在读时能读但不能写,而写加锁就是不能读也不能写。

读写锁把枷锁操作分成了读锁和写锁。

在俩线程加锁过程中:读锁和读锁之间不会产生竞争(因为多线程下的读操作无线程安全问题);而读锁和写锁之间有竞争;写锁和写锁之间也有竞争。

5.可重入锁和不可重入锁

一个线程针对同一把锁先后枷锁俩次,没有出现死锁现象,就是可重入锁

6.公平锁和非公平锁

当很多线程尝试加同一把锁时,只有一个线程能够拿到锁,剩下的线程就得阻塞等待。一旦第一个线程释放了锁,接下来那个线程可以拿到锁?这时有俩种机制:

对于公平锁:会根据先来后到的顺序进行锁的分配

对于非公平锁:剩下的线程会以均等的机会来竞争锁。

操作系统提供的api一般是非公平锁,要想实现公平锁,就得引入额外的队列,来维护这些线程的加锁顺序。

那么synchronized属于哪些呢?

1.对于悲观乐观:是自适应的

2.重量轻量:自适应的

3.自旋锁/挂起等待锁:自适应的

初始时,synchronized会预测当前的锁冲突概率不大,此时以乐观锁的方式运行(也就是轻量级锁,基于自旋锁的方式实现);后来使用过程中,发现锁冲突较多,就升级成悲观锁(也就是重量级锁,基于挂起等待锁的方式实现)。所以说synchronized会自动记录冲突情况

4.它不是读写锁

5.是可重入锁

6.是非公平锁

二.CAS

CAS就是Compare And Swap,即比较并交换。它是一个cpu指令,一个指令,表示这是原子的

1.CAS基本思路

什么是CAS,我们来解释一下:

假设M是内存,A,B是寄存器,CAS(M,A,B):如果M和A的值相同,就把M和B的值交换,并返回true;如果M和A的值不同,就无事发生,返回false。交换的本质其实是赋值,就是把B的值赋给M(寄存器B的值是啥,我们不关心,我们只关心M中的内容)

2.CAS优点:保证线程安全,避免阻塞影响效率

cas是一个cpu指令,也就是上述逻辑的完成不是分步进行的,而是原子的。

所以可以使用CAS哇变成一些操作,进一步替代加锁,给编写线程安全的代码,引入了新思路。

基于CAS实现线程安全,就是无锁编程!!!省去了加锁时的消耗

CAS本质上是cpu提供的指令,又被操作系统进行封装,提供成api,然后又被JVM进行封装,提供成新的api以供程序员使用(代码)

3.CAS缺点

1.代码更复杂,不好理解

2.只适用于一些特定场景,不如加锁具有普适性

4.CAS的应用

1.实现原子类

在Java标准库的java.util.concurrent.atomic包中,提供了许多原子类,就是基于CAS实现的

AtomicInteger

根据之前的学习,我们知道int数据进行加加操作不是原子的,而是有三步,这就会有线程安全问题,而AtomicInteger,基于CAS方式,对int进行了封装,此时的加加操作就是原子的了。使用如下:

创建对象时,先传入一个初始值,getAndIncrement相当于后置加加,incrementAndGet相当于前置加加

注意这个方法,调用了unsafe的方法!!!!为什么呢?

在Java中,有些操作是偏底层的,偏底层操作会有很多注意事项,稍有不慎就会写出问题,这些操作就会归为unsafe类

看unsafe里面的这个方法,里面有调用了compareAndSwapInt,这其实就是那个CAS操作,但是再点进去,就看不到具体实现了:

它是native、修饰的,就是本地方法,也就是在JVM源码中使用c++代码实现的

伪代码实现:

我们来用上面的伪代码来学习一下CAS的思路,实现原理:

在while循环中:如果this.val=oldval(this.val是刚刚从内存上读的,而oldval是之前读到的,已经存放在寄存器A中了),那么说明没有其他线程对val进行修改,那么就把oldval+1的值赋值给this.val,也就是实现了一次加加操作,同时返回true,由于前面有一个!,所以会出了while,结束加加操作。

但要是this.val!=oldval,就说明有其他线程对内存中的val值进行了修改,所以就返回了false,加上前面的!,就会进入while语句块,对oldval的值进行更新,但因为这次的++操作没有进行下去,所以会在进行CAS指令。

具体我们来举一个俩个线程的例子,首先把getAndIncrement方法分成俩步:

这是第一步

这是第二步

然后后面是俩个线程的执行顺序:

t1                    t2

第一步      

                 第一步

                 第二步

第二步

在t1执行1和2之间,t2已经进行一一次完整的加加操作。假设t1第一步读到的oldval值为1,然后t2进行加加,那么内存里的val值就是2了,那么这是时1进行第二步时,this.val和oldval的值就不相同,就不会进行赋值操作,并且返回false,然后就可以进入到while内部,更新oldval的值为2

总结:

CAS是让这里的自增不要穿插进行,它是通过重试的方式避免穿插的,一旦发现oldval的值与内存中的值不相同,就说明有人穿插进来了。

而要是加锁,就是通过阻塞来避免穿插执行。

2.实现自旋锁(需要靠代码来进行纯用户态的实现)

伪代码实现:

owner是记录当前的自旋锁被哪个线程获取

如果owner==null(说明当前的锁是空闲的,没有现成使用该把锁)那么就把当前的线程赋值给owner。如果这个锁已经被别的线程持有即owner!=null,那么就自旋等待

这个锁的劣势就是一直消耗cpu资源,忙等,所以该锁要看场景使用

5.CAS的ABA问题

由上面的讲述可知,CAS是根据值不变来作为“没有其他线程穿插执行的”判定依据,但这不够严谨,比如可能遇到ABA问题,就是其他线程将数据从A改成了B,又从B改回了A。

一般情况下,ABA问题不会出现bug,因为在逻辑上没啥问题,但是也会有极端情况:

比如ATM取钱:账户中有1000元,我要取500元,比如出现了bug,我按了一下没有反应,所以就又按了一下,这时候就有俩个线程:

t1                                           t2

int oldval=val(1000);

                                                   int oldval=val(1000);

                                                   CAS(this.val,oldval,oldval-500);

CAS(this.val,oldval,oldval-500);

t1线程在CAS之前,t2线程已经进行了CAS,对于t2来说,this.val==oldval==1000,所以t2已经完成了扣款操作。到了t1进行CAS时,由于this.val(500)!=oldval(1000),所以就不进行扣款了。这里的CAS原子操作保证了在出现bug按了俩次时没有多扣款

但是如果现在在t2的CAS和t1的CAS操作之间又来了一个t3线程,比如我妈给我转账500元,那么这时this.val就又是1000元了,然后t1再CAS时就会成功扣款了,所以就一共扣了俩次款,但我只取到了500元。

如何解决ABA问题

只要让判定的数值按一个方向增长即可(有增有减就有可能出现ABA问题,但只是增或减就没问题)

但针对像账户余额这样的概念,本身就得有增邮件,那么我们就可以引入一个版本号,每次修改都让他进行自增,此时CAS进行判定时,就不仅仅直接判定余额,而是还要判定版本号。

三.synchronized几个重要的机制

1.锁升级

在代码执行过程中,synchronized会根据当前情况适当进行锁升级过程,升级顺序为:无锁->偏向锁->自旋锁->重量级锁

什么是偏向锁?

类比于线程池,线程池是优化了找“下一任”的效率,而偏向锁则是优化了“分手”的效率。比如我看上了一个小哥哥,立马就和他确立了情侣关系,但到了后期我不耐烦时,想要和他分手,就得考虑很多,还得找理由,这样的分手就低效;但要是我看上了一个小哥哥,但从不提交往,只是培养感情,感情越来越好,但没有确认关系,当我对他不耐烦时,只要不理会他就行,因为我俩也没啥关系,是自由的。这就是偏向锁的状态。

偏向锁不是真正的加锁,只是做了一个标记。

如果在我和小哥哥培养感情的时候又来了一个妹子接近小哥哥,针对这种情况,我就立马想小哥哥进行表白确立关系。就是一旦出现锁竞争威胁,一旦出现锁竞争的可能,偏向锁就可以立即升级为真正加锁

所以偏向锁的核心思想就是“懒汉模式”,能不加锁就不加锁,因为只要加锁就会有开销。

总结:

锁升级过程就是在性能和线程安全之间尽量进行权衡

2.锁粗化

首先了解一下锁的粒度:synchronized代码块中,代码越多,锁的粒度越粗。

而锁的粒度细时:能够并发执行的逻辑就越多,更利于充分利用多核cpu资源,但太细的锁被反复加锁解锁,实际效果或者效率可能还不如粒度粗的锁

此时编译器的优化机制:一段逻辑中要是多次出现加锁解锁,编译器+JVM就会自动进行锁的粗化

3.锁消除

这也是编译器的优化机制:编译器和JVM会针对当前写到加锁代码进行盘底你个,若觉得不需要加锁,就会把synchronized优化掉。

例如:StringBuilder不带synchronized,StringBuffer带有synchronized。但若是在单个线程中使用buffer,编译器就会自动把锁优化掉(但编译器只在自己非常有把握的情况下进行锁消除,所以触发的概率并不高)

四.JUC(java.util.concurrent)常见的类

1.Callable接口

Callable接口也是一个创建线程的方式。适用于想让某个线程执行一个逻辑并返回一个结果的。

这就是和Runnable的区别,runnable不关注结果,而Callable关注结果

例如:创建一个线程,返回1到100的加和结果

不用Callable接口时:

如果没有Callable接口,代码就得如下这样写:

最终的计算结果得用一个自定义的类来封装。但我这样写对吗?

执行后,发现结果是0!!这是为什么?因为t线程还没有执行完,而主线程就已经执行完了!!。所以我们应该怎么写呢?

可以如上加一个t.join()等待线程执行完毕。但还有一个问题,如果又有另一个线程对rusult的值进行修改怎么办?为了不被修改,我们应该对有些地方进行加锁:

使用Callable接口时:

Callable接口是一个泛型接口,他只有一个抽象方法call,我们重写他即可

然后将这个callable封装到futuretask中,然后创建线程传入futuretask,然后启动线程,让线程执行call方法完成计算,最终的计算结果就放到了futuretask当中。最后注意:拿结果时用的是futuretask,而不是t线程。同时futuretask.get方法能够触发阻塞,等待t线程把结果计算完毕

理解Callable:

Callable和Runnable是同一级别的,只不过一个可以返回结果,另一个无法返回结果

Callable需要搭配一个FutureTask来使用,它用来保存callable的结果。因为callable自己没有等待功能,所以futuretask就可以负责等待这一个工作

理解FutureTask:

想象一下我们去喝奶茶,服务员会给我们一个小票,上面写着取奶茶的号,我们要凭小票取奶茶。这里,点单就相当于是告诉了call,应该干什么;小票就相当于是futuretask,只有叫了上面的号,我们才结束等待;后厨就相当于是一个线程,开始做奶茶;最后我们就凭小票取奶茶,也就是get方法

面试题小结:

创建线程的几个方法:

1.继承Thread重写run(创建单独的类或使用匿名内部类)

2.实现Runnable重写run(创建单独的类或使用匿名内部类)

3.实现Callable重写call(创建单独的类或使用匿名内部类)

4.使用lambda表达式

5.使用线程工厂ThreadFactory

6.使用线程池

2.ReentrantLock

也是叫做可重入锁,不过,它不是一个简单的关键字,而是一个类,它比synchronized使用起来更麻烦

使用方式:

lock方法进行加锁,unlock方法进行解锁

所以一定不能忘记unlock!!!如何能不忘记unlock呢?用try finally

ReentrantLock的优势:

1.它的加锁有俩种方式:lock和trylock

对于lock来说,当加锁失败时,就会进行阻塞等待;而trylock没有加上锁就会直接放弃

阻塞等待有时候并不是一个好的选择,有时候死等就不如放弃,trylock就给了我们更多操作空间

2.reentranlock提供了公平锁的实现。默认情况下,它是非公平锁,但我们可以在他的构造方法中传入参数,表示需要公平锁

3.reentrantlock提供了更强大的等待通知机制。它搭配了Condition类,来实现等待通知(例如:可规定想要唤醒哪个线程)

虽然有以上优势,但是,加锁首选synchronized,因为reentrantlock使用起来更复杂,尤其是容易忘记解锁。

3.信号量(Semaphore)

什么是信号量:

想象一下停车场门口有一个显示牌,显示剩余几个车位,这就好比与信号量。

信号量就是一个计数器,描述了可用资源的个数,每次申请(P操作)释放(V操作)一个可用资源时,信号量就会自动加一或减一(这里的加减操作是原子的)。

例如初始有100个可用资源(即信号量初始为100)当进行了100次P操作后,再进行一次P操作就会进入阻塞等待,有一种上锁的感觉。

锁其实就是一个特殊的信号量,可用资源为1的信号量。加锁:1->0;解锁:0->1,这样的信号量也叫二元信号量。

操作系统提供了信号量的实现,并封装成了api,JVM有进一步封装了此api,就可以再java代码中使用了。

代码实例:

这时就一直等待下去

4.CountDownLatch

理解CountDownLatch:

适用于多个线程来完成一系列任务时,用来衡量任务进度是否完成。比如:将一个大任务分成多个小任务,让这些小任务都并发执行。这时就可以用countdownlatch来判定小任务是否都完成了,如果没有完成,就会进入等待。

主要方法:

await方法:调用await时会触发阻塞,只有当所有小人物都完成了,await才会返回,才能继续进行下去

countDown方法:在每个分任务执行完毕后,都要调用countDown方法,来告诉CountDownLatch我这个任务执行完了。

使用示例:

创建一个实例,然后放置10个任务,每个任务完成后都要调用countDown,结果如下:

但要是执行到任务不够10个,那就会阻塞等待

五.线程安全的集合类

1.多线程环境使用ArrayList

自己使用synchronized或ReentrantLock去调节

使用Colletions.synchronizedList(new ArrayList());

在Collections类中,这是一个类方法,直接用类名调用,返回一个新对象synchronizedList,它实现了List接口,该类相当于是标准库中提供的一个基于synchronized进行线程同步的List,也就是在List中的关键方法上加上synchronized,简单来看一下:

使用CopyOnWriteArrayList

copyonwrite即写时拷贝,例如:俩个线程同时使用ArrayList。若俩个线程同时读,就直接读;若某个线程在改,那么就把ArrayList复制一份副本,改的时候就改那个副本,而读的那个线程就继续读原List。改完后,就会使用修改好的这份数据替代元数据(往往是引用赋值,就是将原List的引用指向新的引用)

但注意:

1.ArrayList不能太大,否则copy的开销就大

2.更适合于一个线程改,其他线程读,而而不适用于多个线程改!!!

2.多线程环境下使用哈希表

HashTable:

这个类是在方法前面加上了synchronized,相当于是对this上锁,也就是对整个哈希表上锁。这就导致了只要有俩个线程操作同一个哈希表时就会引起冲突

ConCurrentHashMap:

但是我们知道,hash表的结构是一个数组,每个元素又是一个链表的表头(这是用来解决hash冲突的)可是当操作不同链表时,不会有线程安全,只有操作同一个链表时才会有线程安全问题。所以根本没必要对整个哈希表都上锁,而仅仅以链表为单位进行上锁即可。所以concurrenthashmap就对哈希表进行了如下改进:

1.最核心的改进:把一个全局的大锁改成了每个链表独立的小锁,大幅度降低了锁冲突的频率,等同于提高了效率

2.充分利用了CAS特性,把一些没必要加锁的环节省略加锁了(比如:记录hash表中元素个数时,可用原子操作对size进行加减操作)

3.激进操作:针对读操作没有加锁。这样,读与读和读与写之间都没有竞争。

那么是否会出现读到了修改了一半的数值的情况呢?ConCurrentHashMap在底层编码时,谨慎处理了一些操作,比如修改时避免使用++--这样的非原子的操作,而是用=进行修改,所以不会出现以上情况。

4.concurrenthashmap针对扩容操作进行了优化:本身hashmap/hanshset在扩容时会将表全都拷贝一遍,担当元素多时就容易卡顿,拷贝也耗时。

所以concurrenthashmap就化整为零,一旦需要扩容,确实是要搬运,这不过不是一次性搬运完,第一次先创建出足够大小的数组并把新数写上去,第二次添加元素时,就会把部分旧元素拷贝过去,第三次以后也是这样。

http://www.ds6.com.cn/news/77545.html

相关文章:

  • 微信小程序游戏充值破解宁波优化网站排名软件
  • 钢筋网片生产厂家seo外包优化网站
  • 做3d动画网站免费下载百度一下
  • 为什么做电影网站没有流量网络营销的常用方法有哪些
  • cad精品课网站建设哪个网站是免费的
  • 海外网站cdn加速下载带佣金的旅游推广平台有哪些
  • 上海市卫生健康委员会网站优化课程
  • 动态网站商品浏览怎么做深圳推广公司
  • 网站swf怎么做培训网站设计
  • 建设网站建设网络推广赚钱项目
  • 深圳市住房和建设局工程交易中心泉州seo网站排名
  • wordpress https转换上海seo外包
  • 做装饬在哪家网站挂百度网盘客服中心电话
  • 网站开发对显卡的要求seowhy官网
  • 有什么网站可以做批发明年2024年有疫情吗
  • 华为云云速建站怎样搜外网
  • 网站建设的原因查看浏览过的历史记录百度
  • 闵行虹桥网站建设百度收录情况
  • 网站整合建设方案今晚比赛预测比分
  • 宁波妇科专家排名seo博客
  • 恒彩装饰公司口碑杭州seo技术培训
  • 建设网站的基本步骤网店营销与推广策划方案
  • 怎么做点播网站上海百度研发中心
  • 可以做视频的一个网站目前最火的自媒体平台
  • 怎么做简单的企业网站seo优化推广专员招聘
  • 建材网站素材网络游戏推广
  • 连云港规划建设网站网站推广的基本方法有
  • 做ptt有什么好的模板网站360关键词排名推广
  • 十大素材网站网站推广是什么
  • 如何做网站效果图网站优化方法