Java多线程开发

首先,我想先给大家区分下两个概念——线程和进程。

1.进程:拥有整台计算机的资源,多进程之间不共享内存,进程之间通过消息传递进行协作。

一般来说,进程==程序==应用,但一个应用中可能包含多个进程,OS支持的IPC机制(pipe/socket)支持进程间通信, 不仅是本机的多个进程之间, 也可以是不同机器的多个进程之间。

 JVM通常运行单一进程,但也可以创建新的进程。

2.线程:线程共享进程中的所有内存,就像一个进程代表一台虚拟计算机一样,线程抽象代表一个虚拟处理器,因此线程有时也称为lightweight process。  很难获得线程私有的内存空间, 通过创建消息队列在线程之间进行消息传递

简单总结:进程=虚拟机,线程=虚拟CPU, 程序共享、资源共享,都隶属于进程。

在我们开发的应用中,每个应用至少有一个线程,主线程,可以创建其他的线程,java中可使用如下方式创建其他的进程:

new Thread(new Runnable()

{

    public void run()

    {

        ......(implement)

    }

}).start();

掌握了基础的线程知识后,我们就可以进行多线程开发了,然而多线程程序与单线程程序相比,有着其独特性,刚刚学习多线程程序时,我们必须注意两个问题——交错和竞争。

接下来,我先介绍下Time slicing。虽然计算机系统可以有多线程, 但只有一个核,每个时刻只能执行一个线程,通过时间分片,在多个进程/线 程之间共享处理器,即使是多 核CPU,进程/线程的数目也往往大于核的数目。

当线程数多于处理器时,并发性通过时间分片来模拟,这意味着处理器在线程之间切换。

三个线程T1,T2和T3可能在具有两个实际处理器的机器上进行时间分割
首先一个处理器运行线程T1,另一个运行线程T2,然后第二个处理器切换到运行线程T3。线程T2只是暂停,直到下一个时间片在同一个处理器或另一个处理器上。

时间分片是由OS自动调度的。

在理解了时间分片后,我们来谈谈线程之间的内存共享问题。

例如,一家银行有存钱和取钱操作,我们定义一个类bank,里面提供两个简单的操作,存入1块钱和取出一块钱。代码如下:

这时我们创建2个进程,在主进程中新建一个bank类,在子进程中分别调用saveMoney()函数,最后调用showMonkey()查看账户余额,多次运行程序后,我们会发现一个神奇的事情,账户余额有时候剩余2,有时候却只剩余1!这是什么情况,我们可以来慢慢分析一下。

假设创建的两个进程分别为T1和T2,在两个进程中都调用了saveMoney()函数,即对bank类的money变量执行加一操作。我们可以将加一操作分为两个部分来理解,一个部分是对money的值进行加一,但还未写回,另一个部分是将加一后的值写回money中。

我们理想中的执行过程应该如下:

1.T1 进程先对money的值加一,然后立刻将money的值写回bank

2.T2 进程先对money的值加一,然后立刻将money的值写回bank

其中T1和T2的顺序可以交换,但是内部的执行顺序应该是固定的。

然而,这时候就出现了竞争问题。进程之间的切换是随机的,所以会以下情况:

1.T1进程对monkey的值加一,money的值还并未写回,此时bank中的money依然值为0。

2.此时,切换到T2进程,T2进程对money的值加一,此时T2获得的money值为0。

3.T2将加一后的money写回bank,bank中的money值为1。

4.切换到T1,T1将money值写回,写回的值为1,最终bank中money值为1。

在这里,我仅仅给出了一个例子,实际上这段代码运行时还会出现很多其他情况,其中一些情况也会出现这个竞争问题,这是由于各个线程之间共享内存,并且变量还是mutable,多个进程对mutable变量执行操作时就会出现竞争问题。这样的代码是线程不安全的,那如何保证threadsafe呢,我们可以有以下几个方法。

1. Confinement

 将可变数据限制在单一线程内部,避免竞争,不允许任何线程直接读写该数据。核心思想为线程之间不共享mutable数据类型。

2. Immutability

使用不可变数据类型和不可变引用,避免多线程之间的race condition。不可变数据通常是线程安全的。

3. Using Threadsafe Data Types

如果必须要 用mutable的数据类型在多线程之间共享数据,要使用线程安全的数据类型。例如,通过以下代码获得一个线程安全的HashMap()类型变量。

private static Map<Integer,Boolean> cache =   Collections.synchronizedMap(new HashMap<>());

4. Locks and Synchronization

使用锁机制,获得对数据的独家mutation权,其他线程被阻塞,不得访问。如下图代码使用了锁机制。
在使用锁机制后,锁住的代码严格按照串行顺序执行。

但是我们必须认识到一个问题,使用锁机制后,虽然代码实现了threadsafe的目标,但是这段代码变成了串行执行,如果对一个进程中的所有代码上锁,则我们所写的多线程程序则按照单线程程序的运行顺序进行,那则失去了我们编写多线程程序的初衷,如何在安全和效率之间获得平衡,这需要我们注意。因此我们要对所写代码进行分析,针对会产生线程安全问题的代码上锁,尽可能保障我们的代码的并行效率。


总结:多线程程序相比单线程有其独特的优势,但是在编写多线程程序时我们会遇到很多单线程程序不会出现的问题,如何避免这些问题并保证多线程程序的高效性需要我们程序员多多思考。最后希望大家都能开发出安全、高效的多线程程序。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值