传统线程机制

今天这篇文章,我们来谈谈传统线程技术,我们会从两点入手:传统线程机制以及线程的同步互斥和通信,在传统线程机制中我们会看一下创建线程的几种方式以及这几种方式的区别。在线程的同步互斥中我们将会看到synchronized关键字,我们会从JVM的角度解释一下synchronized关键字的实现,在线程的通信中,我们会谈到终极父类Object下的两个方法:wait()和notify().

传统线程机制:

学过OS的童鞋应该都知道进程和线程之间的区别,在java的世界上没有进程这一个概念,只有线程这个概念。那么什么是线程,线程其实就是进程的一个实体,是操作系统进行运算调度的最小单位。下面我们来谈谈在java中创建线程的几种方式:

1 继承Thread类

这是第一种方式,继承Thread类,然后override run()方法,最后调用start().这里有一个问题需要注意一下当foo.start()之后,是要执行run()方法的,但是执行run()的时机并不是紧接着start()方法的,因为执行run()方法是JVM中后台线程需要做的事情。

2 实现Runnable接口

第二种方式就是实现Runnable接口,override run()方法,然后new Thread(foo).start()启动线程,在很多书籍中都把Thread和Runnable混为一谈,这是一种错误的观念,在第二种方式中我们可以把Runnable接口想象成任务,这时的Thread类就是任务的容器,我们把任务扔进Thread中就行。

这两种方式的比较:在实际开发中我们大多数只会用第二种,因为我们大家都知道在java中的是单继承的,所以如果我们用了第一种做法,那么我们就不能做其他扩展,但是我们如果采用第二种做法,不仅可以实现Runnable接口,还可以继承其他类。

线程的同步互斥

相对于单线程编程,多线程编程有天然的优势可以充分的利用处理器资源,吞吐量比较高,但是多线程编程有很多棘手的地方, 首先也是最重要的就是线程安全性,那么什么是线程安全类呢,我们看一下定义:

A class is thread‐safe if it behaves correctly when accessed from multiple threads, regardless of the scheduling or interleaving of the execution of those threads by the runtime environment, and with no additional synchronization or other coordination on the part of the calling code.

当多个线程访问某个类的时候,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要额外的同步或协同,这个类都能表现出正确的行为,那么这个时候我们就称这个类是线程安全的。这里我用一个交替打印名字的例子来说明线程安全性:

这段代码在理想的情况下会交替打印: lixiangmao和shenjinsheng,但是看一下下面这种情况:2015-02-24 13:38:22 的屏幕截图

很明显是乱码了,所以这段代码不是线程安全的,那么我们该如何解决这个问题呢。首先我们分析一下为什么会出现上面的这个不正确的结果,假设打印lixiangmao字符串的线程为线程A,打印shenjinsheng字符串的线程为线程B,那么当线程A执行到lixia后,发生上下文切换,保存线程A的现场,然后开始执行线程B,线程B打印完shenjinsheng字符串后,线程A继续打印ngmao.上下文切换的原因有很多,这里应该是优先级问题导致线程的上下文切换。那么应该如何保证完整的打印完字符串并在中间过程不会发生上下文切换。这就需要互斥同步,什么是互斥同步,互斥同步是最常见的一种并发正确性保障手段,同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条线程使用。在java里面,最基本的同步互斥的手段就是synchronized关键字,那么synchronized关键字是在JVM内部如何实现的呢,synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令,这两个字节码都需要一个reference类型参数来指明要锁定的解锁的对象。

那么上面那段程序应该如何改动呢:看一下版本1:

上面这段程序好像看起来是同步了,但是结果仍然是不正确的,为什么呢,因为你上锁的name变量它不是共享资源,只有对共享资源加synchronized关键字才能互斥同步。看一下版本2:

这样就可以保证同步,至于为什么this关键字为什么可以保证同步,因为这里this指向调用该方法的那个对象,这个对象共享资源,所以可以保证同步。

线程通信

其实线程通信问题可以分为几个关键点:通过共享对象通信,忙等待,wait()&notify()&notifyAll(),丢失的信号,假唤醒。下面我们结合一个生活中的例子来看看这几个关键点到底是怎么样工作的。

1通过共享对象通信

上学的时候我们都会去图书馆,因为《java并发编程实战》这本书只有一本,所以大家都想去看,小A早上的时候把这本书给借走了,然后下午小B去图书馆去找这本书,这时候小A和小B是两个线程,《java并发编程实战》就是共享对象,小B发现这本书已经被借走了,所以就回去等了几天,几天后,小B又去图书馆发现这本书被还回来了,就把书借走了,这就是通过共享对象进行通信。

2忙等待

由于快要阿里巴巴实习生招聘了,所以小B非常想看这本书,所以小B就每隔一个小时(while循环)就去看看这本书有没有被还回来了,这样虽然比较耗费处理器资源,但是只要书一旦被还回来,小B就可以马上知道。

3wait() notify() notifyAll()

由于图书馆隔着宿舍比较近,所以小B发现每隔一个小时就去图书馆身体有点吃不消,不过很快,学校的图书馆系统增加了短信提醒功能(notify()),所以小B可以一边睡觉一边等短信。

4丢失的信号

图书馆系统是这么设计的,当有一本书被还回来的时候,就会给等待者发短信,但是短信只能发送一次,如果没有等待者,短信也会发出(只不过这个时候没有没有接受者),问题出现了,因为短信只会发一次,当书被还回来的时候,没有人等待借书,他会发一条空短信,但是之后有等待借此本书的同学永远也不会再收到短信,导致这些同学会无休止的等待。为了解决这个问题,我们要进入等待状态的时候先打电话问问图书馆阿姨是否需要继续等待。

5假唤醒

图书馆系统有一个bug,会是不是给用户发送错误短信,我们很听话,收到短信就会去图书馆借书,但是到达图书馆后发现书根本就没有被还回来,所以我们要学聪明点,每次收到短信的时候就打电话问问图书馆阿姨书能不能借。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值