Java 并发和多线程向导(1)

以前的电脑只有一个CPU,并且一次只能够执行一个程序。接着出现了多任务意味着电脑可以同时执行多个程序。操作系统会再运行中的程序挑选,并每个执行一会儿。

接着多任务对于研发成了一个挑战。程序不能够再假定可以拥有全部的CPU资源,内存资源或者其他资源。一个好的公民必须在他使用完资源后马上释放,以便于其他程序的使用。

然后多线程的出现意味着你可以执行多个线程在同一个程序里面。一次多线程执行被视为程序里一次CPU的执行。如果你有多个线程在执行,就像一个程序里面执行了多次CPU指令。

多线程带来的挑战甚至比多任务的还要多。线程在同一个程序里执行共享并同时执行同个内存块的读写。这个会导致在单线程中不可能出现的问题。现代电脑大多都是多核CPU。分离的线程可以同时的分离给不同的CPU核心进行执行。


如果一个线程对一个内存地址进行读操作的时候另外一个线程在写。哪个值是第一个线程应该读的?旧值还是新值?或者两个线程同时对一个内存地址进行写,应该以哪个为准?如果没有合适的防范措施,这些是都有可能发生的,无法预测,结果会不断发生。


Java是第一个让多线程编程变得简单的语言。多线程的好处(1)更高的资源利用率(2)简化某些场景下的开发难度(3)更高的程序相应。

比如多个文件的读写,线性的多线程的方式分别是14S和12S。

多线程虽然有好处,但是也有成本。并不要因为有能力就开启程序的多线程。你应该要对多线程带来的好处大于成本有明确的认识,如果有怀疑,尝试用性能和响应指标来进行测试而不该去猜。

缺点主要如下(1)更复杂的设计(2)上下文却换的消耗。(3)增加资源的消耗。

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

竞争条件和关键区域

在一个程序里面允许多个线程运行本身没问题。问题是多个线程访问了同样的资源。比如:同一内存(变量、数组、对象),系统(数据库、webservice)、文件等。事实上,问题只会出现多个线程写同一个资源,只要数据是不变的,读是安全的。

想象一下两个线程,A和B执行同一个类中的加法函数。我们无法知道操作系统什么时候切换两个线程。我们假设虚拟机是这么执行的

get this.count from memory into register
   add value to register
   write register to memory
观察一下混合执行A和B线程发生了什么

       this.count = 0;
   A:  reads this.count into a register (0)
   B:  reads this.count into a register (0)
   B:  adds value 2 to register
   B:  writes register value (2) back to memory. this.count now equals 2
   A:  adds value 3 to register
   A:  writes register value (3) back to memory. this.count now equals 3
两个线程加2加3结果应该是5,然而两个线程是隔离的,都读到了0,所以结果可能是2也可能是3.如果没有很好的线程同步机制,是无法得知多线程执行是否互相隔离的。

两个线程竞争同一个资源的场合,资源的访问顺序是很重要的。一个代码片段导致竞争条件被称之为关键片段。前面的例子add就是一个关键片段,会导致条件竞争。这可以通过合适的线程同步机制来避免。

线程安全和共享资源

被多线程访问安全的代码称之为线程安全的,它必然是不存在条件竞争的。条件竞争只会发生在多线程更新共享资源。因此,了解什么资源会被多线程共享是很重要的。

(1)本地变量

本地变量储存在自己的堆里面,这意味着本地变量永远不会被多线程共享,这也意味着本地原始变量是线程安全的。

(2)本地对象引用

本地变量引用有点不同,引用本身是不共享的。然而对象的引用不存储在在每个线程的本地桟。所有的对象储存在共享的堆。如果一个对象创建一个本地变量没有脱离所创建它的方法,是线程安全的。但是如果是作为参数传递进来的

someMethod 是安全的,每个线程在执行这个方法的时候都会创建一个本地变量并指向本地的引用。甚至你将这个变量传给其他的方法,也是安全的。唯一的问题是,如果其中一个方法调用了这个对象作为入参,并存储本地变量允许多线程访问。


public void someMethod(){
  
  LocalObject localObject = new LocalObject();

  localObject.callMethod();
  method2(localObject);
}

public void method2(LocalObject localObject){
  localObject.setValue("value");
}


(3)对象成员。

对象的成员储存在堆里面。因此如果两个线程访问一个方法修改了同一个成员变量。这个方法就不是线程安全的。

(4)线程控制的逃逸规则

当你尝试决定是否代码的访问某些资源的时候是否线程安全的,你可以使用线程控制的逃逸规则。

If a resource is created, used and disposed within
the control of the same thread,
and never escapes the control of this thread,
the use of that resource is thread safe.
可被共享的资源像对象、数组、数据库连接、套接字。在Java中并没有严格的显性销毁。所以,销毁意味着对引用空置化。

尽管一个对象是线程安全的,如果这个对象指向一个共享的资源像是文件或者数据库。你的应用整个来说是线程不安全的。因此区分对象是控制资源还是引用的是资源很重要。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值