对于java中线程的一些思考

       本文就是谈谈本人对java并发编程应用的一些理解,可能比较随性,但是尽可能的将自己平时所感表达的尽可能详细,文章有点长,如果你想快速获取某些知识,这篇文章可能不适合你,这篇文章适合你在公交上,无聊时观看,不过,我还是希望你看完后,能有一些感悟,无论是觉得我说的对的好的,或者我说的错的,可以跟我交流

        最近圈子里风风火火的996icu事件正愈演愈烈,我不知道这次事件会收场了无声,还是真的能够产生一些比较重大的影响,不过,我认为这样事件的发生确实是说明IT加班文化在国内盛行对程序员造成的伤害有多大以及很多程序员心中都还有对自己向往生活的一种追求。不过,这次事件,也着实引起了我自己的一些思考,我编程到底是为了什么,for fun?for money?我的编程生涯到底会以一种什么样的结局收场。我曾经会为自己感到幸运,因为我能够从事我自己感兴趣的方向的相关工作,并能够获得比较丰厚的回报用于支撑我的生活。但我也会想到自己平时工作什么样的内容居多--CURD,这真是我想要的编程生活吗?甚至有那么一两次我想干脆转行得了,把编程真正的变成个人兴趣,工作就是工作,我不愿意把编程变成现在工作如此乏味无聊的行为。说实话,我害怕有一天这样的工作会让我失去对编程的兴趣,尽管我现在每天仍在不断给自己充电,不断扩大视野,希望将来自己有能力进入更好的企业能够让我从事那些有挑战并真正让自己沉浸进去的工作,但是一想到即使自己进入那些国内很知名的企业之后,等待自己的并不是自己想象中那样,而是无休止的996,自己对的工作和生活幻想又显得越来越虚幻,自己也会对自己发问:这真是你想要的生活吗?把你的编程爱好作为工作真的好吗?如果有机会,可能我真的会选择成为一名自由职业人,去用自己所学去做一些自己喜欢的事,至少我能充分的掌握和安排自己的时间去做一些自己想做的事。

        进入正题吧,我觉得作为一个正经的编程人,掌握一门熟悉的语言,熟悉他的api固然重要,我们在宏观上对某一领域的理解我觉得更加重要,有了理解,知道这门语言的特性或者说能力吧,再去找自己觉得适合的api我觉得是一个比较理想的编程方式,这样也有利于加深自己对计算机领域的普遍认识,有利于横向扩展自己的能力。

        为什么使用线程?不仅仅当你的电脑cpu是多核的时候有用,当你的cpu是单核的时候,同样也能起到很大作用。我们用多个线程能充分的将计算机资源利用最大化,因为在你做一些事时,你的单核cpu在运转的时候,很可能你的io会比较空闲,在计算机里或者说在java里,不同进程可能会有不同的内存区域对io操作或者说被cpu操作,那么我们就可以利用这点,将这些操作同时进行以求相同的时间能干更多的事。进程对资源调度和切换的代价太大,于是线程的概念就应用而生。可是我们应该也有这样的概念,线程也是一种有代价行为,太多线程会导致频繁的线程切换,java中也会有虚拟机栈容量的限制,线程上去之后会不会导致一些资源的竞争而引发错误,即使你使用锁的技术,当线程量上去之后,会不会因为放大某些竞争而产生死锁问题或者由于锁的获取等待队列太长时而导致某些线程迟迟无法获得资源而导致整个系统的运行效率变低,,,等等等等,所以,线程也不要滥用。

        我自己使用线程比较多的地方是自己在写一些工具脚本时,自己比较清楚并发量的级别,纯粹为减少某一任务的时间时,才会使用多线程编程。而在我平时工作中,在spring boot的包装下,自己很少用到多线程编程。从比较原始的servlet时,servlet框架和服务器便将一个请求对应一个线程,也还记得当时配置服务器接受请求线程池希望能够在一些请求超时或者关闭请求后,系统能够复用现有线程而不用重新创建线程而引起新的花销。在spring中当然也是如此。spring项目开发时,和以前我觉得最大的不同就是我们无需每次使用对象时就新建一个对象,尤其是那些我们很常用的也用的很顺手的,比如对数据库层及事务层的封装直接注入,这些注入的是受spring管理的单例(默认单例),单例?那不是会发生资源竞争吗?其实spring这里就是用ThreadLocal解决这个问题的,使每个线程都有一个自己的副本(存着从连接池获取到的Connection),从而避免竞争底层Connection对象。既然说到这里,就把我对spring事务操作数据库的理解说一下吧。spring事务本质是使用数据库自带的事务,而数据库事务实现靠的是数据库锁(看吧,并发都是离不开锁的),spring事务很多人有的概念是:隔离性,原子性(要么都成功,要么都失败)。那spring事务到底能不能实现像java中对象锁的那样的效果,能够保证某些情况下,某一些数据只有一处能操作呢?答案是:不确定,这个要看底层数据库的设置的隔离级别。在很良好的编码习惯及对数据库锁有比较好的认知下,你是可以完全依赖数据库及spring事务来实现某一资源的有效线程隔离和操作的。举一个很简单的例子:现在有一个订单想减少库存,我们且认为这条语句是nums =select num from availiable_product where product_id=1;update available_product set num=nums-1 where product_id=1;(product_id假如是主键,我这里只是想举例子让你有这个感觉,其他的可以举一反三,读者自己去思考吧)在spring事务环境中运行这两句话,这两句话其实是无法实现我们所想象的那种扣减库存效果的。原因是在mysql默认的事务隔离模式下(repeaded read)两个同时运行的事务第一句查询语句的执行是不会对数据库加锁的他们都会读取他们的事务启动时的相应数据数据库快照(mysql的MVCC多版本并发控制,感兴趣去了解一下)然后会获取相同的数据100,然后某一事务获取执行锁,并对这条数据加上互斥锁,然后更新了这条数据得到99,这个事务释放锁后,另一个事务加锁并开始更新数据,由于事务开始时获取到的nums是100所以更新后也是99,这就导致了并发问题。那么这个问题也很好解决,你可以在第一个查询语句后面加上for update语句,这时这条查询语句就会加上更新锁,就不会允许其他事务对这条数据加锁,那么其他事务读取这条数据也就会获取到的是数据库的当前数据而不是快照数据,上面我们说的并发问题就迎刃而解了。

         上面说的在spring自身运行特点和事务本身加持下,其实是可以实现我们平时对业务的并发要求的,反而在我们服务器上我们自己利用java并发api去实现多线程操作,并不能实现真正的并发,因为java api并发也都是针对单个虚拟机而言,对于多个服务器操作是无法同步多个(进程的)线程的,在现实中,对于分布式系统,会利用自带分布式特性的框架技术来实现分布式锁比如redis,zookeeper等。

          既然谈java并发编程,那就谈一些俗气的,我也把自己对java中并发编程的编程style讲一下个人理解。首先,java线程有NEW,RUNNING,BLOCKED,WAITING/TIME_WAITING,TERNINATED这几种有意义的状态。对线程来说能不能中断或者如何中断是一个比较有意义的研究点,java中已不在推荐使用stop函数来直接中断线程(因为会导致资源占用释放关系错乱,同时也会导致一些用户不能处理的错误),java中使用thread.interrupt()设置线程中断位标志但是并未真正停止线程,然后,你可以在程序中通过thread.isInterrupted()判断其是否有被中断需求,然后依次释放资源并自己可控的退出程序。

         对于java中的线程池,有RUNNING,SHUTDOWN,STOP,TERMINATED几种状态。对于ThreadPoolExcutor有几个比较重要的参数,核心池数量,池最大数量,线程空闲时存活时间,等待队列(LinkedBlockingQueue,ArrayBlockingQueue),线程工厂类,拒绝任务策略,其中,池最大数量对与有限制长度(ArrayBlockingQueue)的等待队列有意义,对于无限长度(LinkedBlockingQueue)其实没有意义,当阻塞队列塞满之后,如果线程池里活动线程数小于最大线程数,则新建线程用来运行塞到线程池的任务,当达到池内最大线程数后,拒绝任务的handler就发挥作用了,有几种类型你可以自由选择(直接拒绝并抛出异常,直接拒绝不抛出异常,不拒绝但是把队列头的任务删掉把这个新加进去,让添加这个任务到线程池的线程去执行这个任务)。通过excutorservice.submit()(返回Future对象可以获取运行结果)或者excutorservice.excute()来提交任务到线程池对于线程池的终止,当调用excutorservice.shutdown()时,线程池会处于SHUTDOWN状态并且会拒绝接受新来的任务但是会将阻塞队列的任务运行完,当调用excutorservice.shutdownNow()后,线程池会处于STOP状态拒绝接受新任务并且会尝试终止正在运行的任务(正在运行的)并且清除阻塞队列的任务。

       当然,上面只是我个人粗浅理解,并发编程中还有很多比较细节和需要你注意的东西,接下来一段时间我会专门写几篇相关文章进行研究。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值