有关JAVA多线程的理解
不同于c++等语言的调用操作系统的线程调控机制来实现多线程,java语言内置了多线程的api包,因此可以更加方便的使用多线程技术。
(1)线程的问题。
进程是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展至消亡的过程。线程是比进程更小的单位,一个进程执行过程中可以产生多个线程,每个线程有自身的产生、存在和消亡的过程,也是一个动态的概念。每个进程都有一段专用的内存区域,而线程间可以共享相同的内存区域(包括代码和数据),并利用这些共享单元来实现数据交换、实时通信与必要的同步操作。
每个Java程序都有一个默认的主线程。Java程序总是从主类的main方法开始执行。当JVM加载代码,发现main方法后就启动一个线程,这个线程就称作"主线程",该线程负责执行main方法。在main方法中再创建的线程就是其他线程。
如果main方法中没有创建其他线程,那么当main方法返回时JVM就会结束Java应用程序。但如果main方法中创建了其他线程,那么JVM就要在主线程和其他线程之间轮流切换,保证每个线程都有机会使用CPU资源,main方法返回(主线程结束)JVM也不会结束,要一直等到该程序所有线程全部结束才结束Java程序(另外一种情况是:程序中调用了Runtime类的exit方法,并且安全管理器允许退出操作发生。这时JVM也会结束该程序)。
(2)JAVA多线程的两种使用方法
1、继承Thread类,覆盖run()方法:使用Thread子类创建线程的优点是可以在子类中增加新的成员变量或方法,使线程具有某种属性或功能。但Java不支持多继承,Thread类的子类不能再扩展其他的类。
2、实现Runnable接口:用Thread类直接创建线程对象,使用构造函数Thread(Runnable
target)(参数target是一个Runnable接口),创建线程对象时必须向构造方法参数传递一个实现Runnable接口类的实例,该实例对象称作所创线程的目标对象。当线程调用start()方法,一旦轮到它使用CPU资源,目标对象自动调用接口中的run()方法(接口回调)。
线程间可以共享相同的内存单元(包括代码和数据),并利用这些共享单元来实现数据交换、实时通信与必要的同步操作。对于Thread(Runnable
target)创建的使用同一目标对象的线程,可以共享该目标对象的成员变量和方法。
另外,创建目标对象类在必要时还可以是某个特定类的子类,因此,使用Runnable接口比使用Thread的子类更具有灵活性。
在线程中启动其他线程,当线程调用start()方法启动,使之从新建态进入就绪队列,一旦得到CPU资源就脱离创建它的主线程,开始自己的生命周期。个人觉得,接口技术相比与thread类更加深层次。
(3)多线程中的常用方法
【此处摘自jinguo的ITeye博客】
start():线程调用该方法将启动线程,从新建态进入就绪队列,一旦享用CPU资源就可以脱离创建它的线程,独立开始自己的生命周期。
run():Thread类的run()方法与Runnable接口中的run()方法功能和作用相同,都用来定义线程对象被调度后所进行的操作,都是系统自动调用而用户不得引用的方法。run()方法执行完毕,线程就成死亡状态,即线程释放了分配给它的内存(死亡态线程不能再调用start()方法)。在线程没有结束run()方法前,不能让线程再调用start()方法,否则将发生IllegalThreadStateException异常。
sleep(int
millsecond):有时,优先级高的线程需要优先级低的线程做一些工作来配合它,此时为让优先级高的线程让出CPU资源,使得优先级低的线程有机会运行,可以使用sleep(int
millsecond)方法。线程在休眠时被打断,JVM就抛出InterruptedException异常。因此,必须在try-catch语句块中调用sleep方法。
isAlive():当线程调用start()方法并占有CPU资源后该线程的run()方法开始运行,在run()方法没有结束之前调用isAlive()返回true,当线程处于新建态或死亡态时调用isAlive()返回false。
注意:一个已经运行的线程在没有进入死亡态时,不要再给它分配实体,由于线程只能引用最后分配的实体,先前的实体就成为了"垃圾",并且不能被垃圾回收机制收集。
currentThread():是Thread类的类方法,可以用类名调用,返回当前正在使用CPU资源的线程。
interrupt():当线程调用sleep()方法处于休眠状态,一个占有CPU资源的线程可以让休眠的线程调用interrupt()方法"吵醒"自己,即导致线程发生IllegalThreadStateException异常,从而结束休眠,重新排队等待CPU资源。
GUI线程:JVM在运行包含图形界面应用程序时,会自动启动更多线程,其中有两个重要的线程:AWT-EventQueue和AWT-Windows。AWT-EventQueue线程负责处理GUI事件,AWT-Windows线程负责将窗体或组件绘制到桌面。
线程同步:(用synchronized修饰某个方法,该方法修改需要同步的变量;或用volatile修饰基本变量)
当两个或多个线程同时访问一个变量,并且一个线程需要修改这个变量时,应对这样的问题进行处理,否则可能发生混乱。
要处理线程同步,可以把修改数据的方法用关键字synchronized修饰。一个方法使用synchronized修饰,当一个线程A使用这个方法时,其他线程想使用该方法时就必须等待,直到线程A使用完该方法。所谓同步就是多个线程都需要使用一个synchronized修饰的方法。
volatile比同步简单,只适合于控制对基本变量(整数、布尔变量等)的单个实例的访问。java中的volatile关键字与C++中一样,用volatile修饰的变量在读写操作时不会进行优化(取cache里的值以提高io速度),而是直接对主存进行操作,这表示所有线程在任何时候看到的volatile变量值都相同。
在同步方法中使用wait()、notify()、notifyAll()方法:
当一个线程使用的同步方法中用到某个变量,而此变量又需要其他线程修改后才能符合本线程需要,那么可以在同步方法中使用wait()方法。中断方法的执行,使本线程等待,暂时让出CPU资源,并允许其他线程使用这个同步方法。其他线程如果在使用这个同步方法时不需要等待,那么它使用完这个同步方法时应当用notifyAll()方法通知所有由于使用这个同步方法而处于等待的线程结束等待。曾中断的线程就会从中断处继续执行,并遵循"先中断先继续"的原则。如果用的notify()方法,那么只是通知等待中的线程中某一个结束等待。
计时器线程Timer:(Timer还有很多高级操作,详细见JDK,这里做个概述)
java.swing.Timer类用于周期性地执行某些操作。有两个常用构造函数
public
Timer(int delay, ActionListener
listener):参数listener是计时器的监视器,计时器发生振铃的事件是ActionEvent类型事件,当振铃事件发生,监视器会监视到这个事件并回调ActionListener接口中的actionPerformed(ActionEvent
e)方法。
public Timer(int
delay):使用该构造方法,计时器要再调用addActionListener(ActionListener
listener)方法获得监视器。
如果想让计时器只震动一次,可以让计时器调用setRepeats(boolean
b)方法,参数b取false即可。
计时器还可以调用setInitialDelay(int
delay)方法设置首次振铃的延时,如果没有设置首次振铃默认延时为构造函数中的参数delay。
还可以调用getDelay()和setDelay(int
delay)获取和设置延时。
计时器创建后调用start()启动,调用stop()停止,即挂起,调用restart()重新启动计时器,即恢复线程。
线程联合:
一个线程A在占有CPU资源期间,可以让其他线程调用join()方法和本线程联合,如:
B.join();
此时称A在运行期间联合了B。这时A线程立刻终端执行,一直等到它联合的线程B执行完毕,A线程再重新排队等待CPU资源。但如果A准备联合的线程B已经结束,则B.join()不会产生任何效果。
守护线程:
线程默认是非守护线程,非守护线程也称用户线程,一个线程调用setDaemon(boolean
on)方法可以将自己设置成一个守护(Daemon)线程,如:
thread.setDaemon(true);
一个线程必须在自己运行之前设置自己是否是守护线程。守护线程是当程序中所有用户线程都已结束运行时即使守护线程的run()方法还有需要执行的语句,守护线程也会立刻结束运行。因此守护线程用于做一些不是很严格的工作,当线程随时结束时不会产生什么不良后果。