-------<a href="http://www.itheima.com/"">android培训</a>、<a href="http://www.itheima.com/" ">java培训</a>期待与您交流!----------
2014 7月份 我对使用多线程的心得
本博客的题目就有些怪——2014 7月份 我对使用多线程的心得。加上日期,是因为我觉得,在学习编程这种纯思想上的技能。(有些技能是训练肌肉记忆,我觉得编程是与之相对的技能)。对编程语言的理解与使用,如果不断地认真学习,理解也会在不断地变化,提高。不同时期会有不同的认识,并且这种改变会很快。经常有时候看也前自己的学习笔记会发现自己很浅薄。但这些浅薄也正是自己变的深刻的台阶。所以我的这篇多线程使用心得客观上也许是很浅薄的,但并不妨碍我要将他写出来。
在使用多线程时,主要是围绕两个方面——线程的安全问题和线程间的通信问题。
线程的安全问题产生的原因:
多线程程序在运行时,不是每个线程同时被运行,而是CPU在每个线程间快速切换的运行着。它在这个线程上运行一会,又切到另一个线程间运行会。
正是由于多线程的这种运行机制导致了多线程的安全问题。我们知道一个线程中,有些代码是要求必须完整一次性被CPU执行完的。不然就会出现问题。这由其表现在线程在操作共享数据的代码时。
举个卖票的例子。两个线程在卖票。车票号是共享的数据。用一个变量N来存储当前的票号,每个线程取出当前票号作为售出车票的车号,然后再将N减去一,作为下次售出车票号。在程序运行时,假设线程一取出N,然后将该号的车票卖出,这时N减去一的代码还没执行,CPU就有又切到第二个线程,取出N,因为线程一并没有执行N减一的代码,所以它就取出了与线程一取出的相同号码的票号。结果就是,车站卖出了两张相同车票号的车票。
总结一下,多线程出现安全问题是因为线程中有要求被CPU完整一次性执行的代码。而这种代码主要是操作共享数据的代码。
线程安全问题的解决就是同步。将需要被CPU完整一次性执行的代码放到同步代码块里,或是将其封装为同步方法方法。这个如何做到很简单就不说了。
既然知道了多线程安全问题的原因了,接下来就是研究如何写代码了。
先讲下多线程的启动。
许多视屏,书上都讲开启一个线程有两个方式。但是我觉得从本质上讲,开启线程就只有一个方式。那就是调用Thread类对象的start()方法。
Start()方法内部是掉用了run方法。之所以说有两种方式就在于这个run方法。Start()实际运行的run方法可以来源于两个地方。一个是来源于Thread类本身的run()另一个是来源于Runnable子类的run()。
这个结论是根据源代码发现的。
源代码中,Thread的run方法是先判断其封装的Runnable类型成员变量是否为null,如果不为空就掉用它的run()方法。
第一种方式就是覆盖掉Thread的run 方法,让start()方法运行Thread的run()。
第二种方式是向Thread的构造函数中传递一个实现了Runnable接口重写了run方法的类的实例。让start()运行runnable子类的run().
这两种方式我的理解是,其实Thread类可以理解为一个启动线程的类,Runnable子类是一个存储线程运行代码的类。所以第二种方式更符合java的OOP思想,启动线程类就负责启动线程,存储线程运行的代码类就存储线程的代码。各司其职,清晰了然。而第一种是一种简便的开启线程的方式。
回到正题,如何编写涉及多线程 安全问题的多线程程序呢?
我们知道了多线程安全问题主要是因为线程中有操作共享数据的代码。而据我现在所知。可以根据线程操作共享数据的代码是否相同,来分两种方式来设计程序。
第一种,对共享数据操作的代码相同。
当线程们要操作的共享数据的代码相同时可以这样设计。将操作的共享数据的代码同步并放到一个Runnable子类的run方法中。
就用多线程卖票的例子来演示。每个线程线程对共享数据——车票,都是相同的操作方式。就是卖。
第二种情况:线程对共享数据的操作代码不同。
设计步骤是:
a:将要共享的数据资源封装成单独的一个类。
将线程对共享数据的操作封装成该类的方法,一定要记得同步。
b:Runable接口的实现类,用构造函数传值的方式传入共享数据对象,在该类的run方法中完成对共享数据的操作,调用共享数据对象中相应的方法即可。有几种对共享数据的操作方式就写几个实现类。
用张老师的视屏中的一个例子演示。有四个线程对一个变量j的值进行操作,其中两个进行加一操作,两个进行减一操作。
多线程的通信:首先我问一个问题,为什么notif(), wait()方法写在同步语句块,同步函数中。能不写在里面吗?
多线程通信是要让线程与线程根据程序员的意愿协作运行。本质上讲,多线程程序在运行时,CPU在线程间的切换是随机的。所以哪个线程被运行也是随机的。而线程间的通信就是要线程按程序员的意愿去获取或失去运行资格。线程的通信通过等待唤醒机制来完成。notif(),wait()必须写在同步语句块或同步函数里。因为没有同步线程间得通信就没有了意义。因为线程间的通信就是让程序猿决定改线程何时等待,何时唤醒他线程。如果notif(),wait()不放在同步里面。CPU就会再线程间任意切换。那还谈什么程序员控制一个线程何时获取或何时失去运行资格。在实际开发中都是让A线程完整的做完某件事(完整的运行完某段代码)就让A线程唤醒B线程。自己等待,等B线程完整的做完某件事在唤醒A,自己等待。
总之,线程不需要同步,就没有通信的必要。
在写涉及到多线程通信的代码时,我的经验是先不考虑线程间通信问题。先根据上面涉及线程安全问题的思想写出代码,然后再在同步语句代码里加上有关多线程通信的代码即可。
用消费者,生产者这一经典案例来演示线程间通信。
-------<a href="http://www.itheima.com/"">android培训</a>、<a href="http://www.itheima.com/" ">java培训</a>期待与您交流!----------