一起Talk Android吧(第二十四回:Java多线程编程二)
2017-05-31 07:11
176 查看
各位看官们,大家好,上一回中咱们说的是Java多线程编程的例子,这一回咱们继续说该例子。闲话休提, 言归正转。让我们一起Talk Android吧!
看官们,我们在上一回中介绍了如何创建和使用线程。这一回中主要介绍线程同步相关的知识。关于线程同步的概念,我们在C栗子中介绍过,我们假定大家明白这个概念,这里我们主要介绍如何使用Java来实现线程同步。
Java提供了
方法一:
方法二:
方法一中的内容叫做同步块,方法二中的内容叫做同步方法。这两种方法都可以实现线程同步的效果,使用哪一种都可以,没有特别的推荐,不过同步块中的
这时有两位看官提问了:那么同步方法如何使用锁变量呢?为什么要使用同步呢?
看官莫急,我们先来回答第一位看官的问题,同步方法的语法中没有指定锁变量,不过它默认使用方法当前的对象做为锁变量,也就是
我们还是使用前一章回的代码,因为该代码有潜在的错误,所以我们可以通过这一回的知识来分析和修改其中的错误。
这是
在上面的代码中,我们使用了
下面是程序的运行结果:
从上面的程序运行结果中可以看到程序运行时发生了错误:
Thread-0的数据不是从1到5递增的,而是1-3-4-5-5;
Thread-1的数据是从1到5递增的,1-2-3-4-5。
这两个线程运行同样的代码,但是是却得到了不同的结果,这显然存在错误。错误的原因我们已经分析过了,接下来我们看看如何修改这种错误,这时候
在代码中我们使用同步块,同步块中使用的锁变量是
从上面的程序运行结果中可以看到程序运行结果是正确的。
TThread-0的数据从1到5递增:1-2-3-4-5;
Thread-1的数据也是从1到5递增的:1-2-3-4-5。
这两个线程得到了相同的运行结果。这就验证了我们的猜想:使用同步块可以解决访问临界资源的问题。
看官们,完整的代码我就不再列出了,大家只需要使用同步块的代码替换掉原来的旧代码就可以。至于程序的运行结果也是不同的,这取决于线程的调度,我们在前面章回中分析过其中的原因,不过有一点是可以保证的,那就是每个线程都会把数据从1递增到5,如果不是这样的运行结果,那么赶快检查一下是不是同步块使用不当呢。
看官们,使用同步块会损失一些性能,不过它保证了程序结果的正确。为此Java除了对虚拟机进行优化外,还提供了其它的锁变量,此外,Java还提供了其它的同步方法,比如信号量。这些内容和同步块的原理类似,如果大家感兴趣的话,可以自己去学习,相信大家可以举一反三,很快地掌握这些线程同步方法。
各位看官,关于Java多线程编程的例子咱们就介绍到这里,欲知后面还有什么例子,且听下回分解!
看官们,我们在上一回中介绍了如何创建和使用线程。这一回中主要介绍线程同步相关的知识。关于线程同步的概念,我们在C栗子中介绍过,我们假定大家明白这个概念,这里我们主要介绍如何使用Java来实现线程同步。
Java提供了
synchronized关键字来实现线程同步,接下来,我们通过伪代码来演示一下:
方法一:
synchronized(object){ // do some thing }
方法二:
synchronized void fun(){ //do some thing }
方法一中的内容叫做同步块,方法二中的内容叫做同步方法。这两种方法都可以实现线程同步的效果,使用哪一种都可以,没有特别的推荐,不过同步块中的
object我们推荐使用
this对象。因为该关键字使用了锁变量的方法来实现线程同步,
object可以看作是一种锁变量,使用this后就可以把当前的对象当作锁变量,这样就能避免把锁加在不同对象上。
这时有两位看官提问了:那么同步方法如何使用锁变量呢?为什么要使用同步呢?
看官莫急,我们先来回答第一位看官的问题,同步方法的语法中没有指定锁变量,不过它默认使用方法当前的对象做为锁变量,也就是
this。关于第二位看官的问题,使用同步是为了解决访问共享资源的问题,或者说解决访问临界资源的问题。这么说可能让大家觉得有点抽象,接下来我们通过具体的例子来进行说明。
我们还是使用前一章回的代码,因为该代码有潜在的错误,所以我们可以通过这一回的知识来分析和修改其中的错误。
public void run() { for(int i=0; i<5; ++i){ setData(i+1); System.out.println(Thread.currentThread().getName()+ " Data :"+ getData()); } }
这是
run方法中的内容,我们在该方法中修改了
data变量的值,然后把它修改后的值打印出来。每个线程都会执行该内容,试想一下如果线程A执行完
setData这行语句后还没有没执行下一行语句,这时线程B也执行了
setData这行语句,那么线程A使用
getData读取数据时读取到的是线程B修改后的数据,而不是线程A修改后的数据。这种情况是完全有可能发生的,我们在上一个章回中之所以没有发现,就是程序运行比较快,我们添加点代码让程序运行慢一些,方便我们发现其中的错误。
public void run() { for(int i=0; i<5; ++i){ setData(i+1); try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ " Data :"+ getData()); } }
在上面的代码中,我们使用了
sleep函数,让程序在
setData完成后等待1秒钟,然后再通过
getData方法读取数据。
下面是程序的运行结果:
Thread-1 Data :1 Thread-0 Data :1 Thread-1 Data :2 Thread-0 Data :3 Thread-1 Data :3 Thread-0 Data :4 Thread-1 Date :4 Thread-0 Data :5 Thread-1 Data :5 Thread-0 Data :5
从上面的程序运行结果中可以看到程序运行时发生了错误:
Thread-0的数据不是从1到5递增的,而是1-3-4-5-5;
Thread-1的数据是从1到5递增的,1-2-3-4-5。
这两个线程运行同样的代码,但是是却得到了不同的结果,这显然存在错误。错误的原因我们已经分析过了,接下来我们看看如何修改这种错误,这时候
synchronized关键字就派上用场了。在上面的程序中数据
data可以看作是共享资源或者叫临界资源,我们可以把访问临界资源的操作封装成一个临界区,然后使用同步块来控制该临界区,这样就可以放心地访问临界资源了。这是整体的思路,我们通过代码来演示一下具体的操作。
public void run() { for(int i=0; i<5; ++i){ synchronized (this) { setData(i+1); try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ " Data :"+ getData()); } } }
在代码中我们使用同步块,同步块中使用的锁变量是
this对象。它能不能解决程序中潜在的错误呢?那么我们来验证一下,运行程序后,得到以下结果:
Thread-0 Data :1 Thread-1 Data :1 Thread-1 Data :2 Thread-1 Data :3 Thread-0 Data :2 Thread-0 Data :3 Thread-0 Data :4 Thread-0 Data :5 Thread-1 Data :4 Thread-1 Data :5
从上面的程序运行结果中可以看到程序运行结果是正确的。
TThread-0的数据从1到5递增:1-2-3-4-5;
Thread-1的数据也是从1到5递增的:1-2-3-4-5。
这两个线程得到了相同的运行结果。这就验证了我们的猜想:使用同步块可以解决访问临界资源的问题。
看官们,完整的代码我就不再列出了,大家只需要使用同步块的代码替换掉原来的旧代码就可以。至于程序的运行结果也是不同的,这取决于线程的调度,我们在前面章回中分析过其中的原因,不过有一点是可以保证的,那就是每个线程都会把数据从1递增到5,如果不是这样的运行结果,那么赶快检查一下是不是同步块使用不当呢。
看官们,使用同步块会损失一些性能,不过它保证了程序结果的正确。为此Java除了对虚拟机进行优化外,还提供了其它的锁变量,此外,Java还提供了其它的同步方法,比如信号量。这些内容和同步块的原理类似,如果大家感兴趣的话,可以自己去学习,相信大家可以举一反三,很快地掌握这些线程同步方法。
各位看官,关于Java多线程编程的例子咱们就介绍到这里,欲知后面还有什么例子,且听下回分解!
相关文章推荐
- 一起Talk Android吧(第二回:Java版Hello World)
- 一起Talk Android吧(第四回:Java中的运算符)
- 一起Talk Android吧(第二十回:Java常用类之Date续)
- 一起Talk Android吧(第二十六回:Java包装类)
- 一起Talk Android吧(第二十五回:Java多线程编程三)
- 一起Talk Android吧(第十八回:Java常用类String VS StringBuffer)
- 一起Talk Android吧(第十七回:Java常用类之StringBuffer)
- 一起Talk Android吧(第五回:Java中的程序结构)
- 一起Talk Android吧(第十回:Java中的封装:二)
- 一起Talk Android吧(第九回:Java中的封装:一)
- 一起Talk Android吧(第十九回:Java常用类之Date)
- 一起Talk Android吧(第十四回:Java中的异常)
- 一起Talk Android吧(第三回:Java中的变量类型)
- 一起Talk Android吧(第八回:Java中的面向对象)
- 一起Talk Android吧(第二十二回:Java常用类之文件操作二)
- 一起Talk Android吧(第二十三回:Java多线程编程一)
- 一起Talk Android吧(第十五回:Java常用类之Arrays)
- 一起Talk Android吧(第十一回:Java中的继承)
- 一起Talk Android吧(第六回:Java中的函数)
- 一起Talk Android吧(第十三回:Java面向对象综合练习)