您的位置:首页 > 移动开发 > Android开发

一起Talk Android吧(第二十四回:Java多线程编程二)

2017-05-31 07:11 176 查看
各位看官们,大家好,上一回中咱们说的是Java多线程编程的例子,这一回咱们继续说该例子。闲话休提, 言归正转。让我们一起Talk Android吧!

看官们,我们在上一回中介绍了如何创建和使用线程。这一回中主要介绍线程同步相关的知识。关于线程同步的概念,我们在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多线程编程的例子咱们就介绍到这里,欲知后面还有什么例子,且听下回分解!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: