Java语言使用多线程实现一个程序中多个任务同时运行。线程运行与进程中,进程是操作系统运行的各种应用程序。一个进程中可以包含一个或多个线程,一个线程就是程序中执行部分的分支。使用多线程就可以开辟另一个执行分支去完成这些耗时的操作,而当作分支,即当前主线程可以继续执行其他业务代码。
线程具有生命周期,它包含3个状态,分别为出生状态、就绪状态和运行状态。出生状态就是用户在创建线程时处于的状态,在用户使用该线程实例调用start()方法之前线程都处于出生状态;当用户调用start()方法后,线程序处于就绪状态(又被称为可执行状态),当线程得到系统资源后就进入运行状态。
一旦线程进入可执行状态,它会在就绪和执行状态下辗转,同时也有可能进入等待、休眠、阻塞或死亡状态。当处于运行状态下的线程调用Thread类中的wait()方法,该线程就处于等待状态,进入等待状态的线程必须调用Thread类的notify()方法才能被唤醒,而notifyAll()方法是将所有处于等待状态下的线程唤醒;当线程调用一个Thread类中的sleep()方法,线程就进入休眠状态;如果一个线程在运行状态下发出输入/输出请求,该线程将进入阻塞状态,在其等待输入/输出结束时,线程进入就绪状态。对于阻塞的线程来说,即使系统资源空闲,线程依然不能回到执行状态;当线程的run()方法执行完毕,线程进入死亡状态。
虽然多线程看起来像同时执行,但事实上在同一时间点上只有一个线程被执行,只是线程之间切换较快,所以才会使人产生线程时同时进行的假象。在Windows操作系统中,系统会为每个线程分配一小段CPU时间片,当CPU时间结束后会将当前线程换为下一个线程,即使该线程没有结束。
使线程序处于就绪状态有以下几种可能:调用sleep()方法,调用wait()方法、等待输入/输出完成。当线程处于就绪状态后可以有以下几种方式使线程再次进入可执行状态:线程调用notify()方法、线程调用notifyAll()方法、线程调用interrupt()方法、线程的休眠时间结束、输入/输出结束。
Java中的主方法其实就是启动主线程的入口,Thread类中的currentThread()方法可以获取当前线程并输出该线程的名称。
Thread类是Java语言的线程类,它位于java.lang包中。该类的实例对象是线程对象,所以集成该类编写线程子类是实现多线程的方法之一。Thread类的start()方法用于启动一个线程对象。多次启动一个线程,或者启动一个已经运行的线程对象是非法的,会抛出IllegalThreadStateException异常对象。Thread类的静态方法sleep()用于休眠当前线程指定的毫秒数。
如果使用继承Thread类的方法编写线程类,将导致无法继承其他的父类。Java语言提供了Runnable接口,使继承了其他类之后同样可以实现该接口达到创建线程的目的。Runnable()接口同样定义了run()方法,实现该接口的同时,必须实现接口中的run()方法。
实现Runnable接口的对象需要传递给Thread类的构造方法,通过Thread的构造方法去创建线程类,也就是说Runnable接口的实现对象需要传递给Thread类的实例对象才能启动线程。Thread类中有很多构造方法,常用的接收Runnable接口参数的构造方法如下:
方法 | 说明 |
---|---|
Thread(Runnable target) | 使用Runnable接口实现对象创建线程对象 |
Thread(Runnable target, String name) | 使用Runnable接口实现对象创建线程对象,并指定线程的名称 |
使用Runnable接口启动新线程的步骤如下:(1)建立Runnable接口实例对象;(2)以Runnable接口实例对象创建Thread实例对象;(3)调用Thread线程对象的start()方法启动线程。
启动一个新的线程,不是直接调用Thread子类对象的run()方法,而是调用Thread子类的start()方法。Thread类的start()方法会产生一个新的线程,该线程运行Thread子类的run()方法。
一种能控制线程暂停的做法是条用sleep()方法,sleep()方法需要一个参数用于指定该线程休眠(也就是暂停)的时间,这个时间以毫秒为单位。它通常是在run()方法的循环体中被使用。在run()方法中使用布尔型标记控制循环的停止。如果线程因为是使用了sleep()或wait()方法进入了就绪状态,可以使用Thread类中的interrupt()方法使线程离开run()方法,同时结束线程,但程序会抛出InterruptedException异常。在某个线程使用join()方法加入到另外一个线程时,另一个线程会等待该线程执行完毕再继续执行。
每个线程都具有各自的优先级,线程的优先级可以在程序中表明该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使用哪个线程进入运行状态。Thread类中包含的成员变量代表了线程的优先级,比如Thread.MIN_PRIORITY(常数1)、Thread.MAX_PRIORITY(常数10)、Thread.NORM_PRIORITY(常数5),其中每个Java的优先级都在Thread.MIN_PRIORITY~Thread.MAX_PRIORITY之间,在默认情况下,每个线程的优先级都是Thread.NORM_PRIORITY,每个新产生的线程都继承了父线程的优先级。线程的优先级可以使用setPriority方法调整,如果使用该方法设置优先级不在1~10之内,将产生一个IllegalArgumentException异常。
如何解决资源共享的问题,基本上所有解决多线程资源冲突问题的方法,都是在指定时间段内只允许一个线程访问共享资源,这是就需要给共享资源上一道锁。(1)同步块,在Java中提供的同步机制能有效地防止资源冲突,同步机制使用synchronized关键字。通常是将共享资源的操作放置在synchronized定义的代码块内,这样当其他线程也获取到这个锁时,必须等待代码块的对象锁被释放时才能进入该区域。(2)同步方法是在方法前面修饰synchronized关键字,当某个对象调用了同步方法,其他方法必须等待该同步方法执行完毕才能被执行。
Swing程序是使用单线程机制进行界面绘制与实践处理的。在Java中,Swing事件处理线程由EventQueue类定义。EventQueue是一个与平台无关的类,它将来自于底层类和受信任的应用程序类的时间列入队列。该类提供了两个方法在事件队列中执行其他线程:invokeLater方法和invokeAndWait方法。invokeLater方法将调用指定Runnable接口实现对象中的run方法,执行该方法中的业务逻辑,这些业务逻辑将被异步执行,也就是异步执行run()方法,这样Swing界面处理与业务处理可以同时进行,不影响程序界面。invokeAndWait方法同样会调用指定Runnable接口实现对象中的run方法,执行该方法中的业务逻辑。不同的是,invokeAndWait()方法将等待Runable对象的run()方法确实被执行以后才返回到方法调用处。这些业务逻辑将被异步执行,也就是异步执行run()方法。抛出异常:InterruptedException和InvocationTargetException。这两个方法都是静态方法,可以直接通过类名调用,不用创建对象。
GUI事件处理机制是建立交互式应用程序的关键技术,其中事件是用户在程序界面上的各种操作,而各种事件的业务处理由相应的监听器来完成。每个组件都是java.awt.Component类的子类,它们都继承了Component类的方法,其中addKeyListener()方法用于向组件注册按键事件的监听器,当用户在组件上按下键盘上的按键时,将产生KeyEvent类型的事件对象,这个事件对象将被注册到组件上的监听器捕获,并由监听器指定的方法来处理。
所谓事件就是程序运行过程中可能发生的各种情况。事件在Java语言中也是一种对象,它们都是java.awt.AwtEvent类的直接子类或间接子类。AWTEvent类继承于EventObject类。常用AWT事件类的功能及产生条件如下:
事件类 | 说明 |
---|---|
ActionEvent | 动作事件,在用户单击按钮、选择菜单、列表等操作时产生该事件 |
AdjustmentEvent | 调整事件,在滚动条变化时产生该事件 |
ComponentEvent | 组件事件,在组件隐藏、显示、移动等操作中产生 |
ContainerEvent | 容器组件,在容器发生变化时产生该事件,例如向容器中添加组件 |
FocusEvent | 焦点事件,在组件获得焦点与失去焦点时,触发该事件 |
HierarchyEvent | 层次事件,当组件级别改变时触发事件 |
InputMethodEvent | 输入法事件,输入法改变时触发的事件 |
ItemEvent | 选项事件,复选框组件、复选框菜单项、列表、下拉选择框等组件的数据改变时触发的事件 |
KeyEvent | 按键事件,按下键盘按键或抬起键盘按键时触发的事件 |
MouseEvent | 鼠标事件,当改变鼠标状态时触发的事件,例如单击鼠标按键 |
MouseWheelEvent | 鼠标滚轮事件,当滚动鼠标滚轮时触发的事件 |
PaintEvent | 绘图事件,发生绘图事件时触发 |
TextEvent | 文本事件,当文本框和文本域组件的内容发生改变时触发该事件 |
WindowsEvent | 窗口事件,当窗体被打开、关闭、最小化、最大化、还原和激活时触发的事件 |
事件监听器用于监听指定的事件类型,它们是Swing定义的不同的接口的实现对象。各种类型的组件都可以产生不同的事件对象,这些事件对象由指定的监听器捕获,并调用指定事件类型的处理方法来处理事件。有的事件监听器接口中定义了多个方法,在实现这样的监听器接口时就必须实现接口中的所有方法。
适配器是事件监听接口的实现类,这些实现类实现了相应的事件监听器接口,并为接口中的所有方法定义了空的实现方法,也就是声明了接口中定义的所有方法,但是方法体是空的。在定义自己的事件监听器时,继承相应的适配器类,然后重新需要的事件方法即可。由于适配器中提供的都是监听器接口的空方法实现,实例化适配器的实例对象没有任何意义,所有适配器被定义为abstract抽象类,但是其中的事件方法都不是抽象的。这样可以有选择地重写这些指定方法。每个方法的声明格式都带有空的{}花括号,即空方法体。在重写某个方法时,要在方法体中编写指定的事件处理代码。
执行System类的exit()方法退出当前Java程序和使用EXIT_ON_CLOSE方式通过关闭按钮关闭窗体是不一样的,后者只是关闭窗体(如果当前窗体是程序的一个模块,那么不会影响到程序的继续执行),而前者将退出这个程序。
鼠标事件监听器由MouseListener接口和MouseMotionListener接口定义,它们分别定义了捕获不同鼠标操作的方法:
方法 | 说明 |
---|---|
mouseClicked(MouseEvent e) | 处理鼠标单击事件的方法 |
mouseEntered(MouseEvent e) | 鼠标进入组件区域中执行的方法 |
mouseExited(MouseEvent e) | 鼠标离开组件区域时执行的方法 |
mousePressed(MouseEvent e) | 按下鼠标按键时执行的方法 |
mouseReleased(MouseEvent e) | 释放鼠标按键时执行的方法 |
方法 | 说明 |
---|---|
mouseMoved(MouseEvent e) | 处理鼠标移动事件的方法 |
mouseDragged(MouseEvent e) | 处理鼠标拖动事件的方法 |
方法 | 说明 |
---|---|
getButton() | 返回更改了状态的鼠标按键(如果有) |
getClickCount() | 返回与此事件关联的鼠标单击次数 |
getLocationOnScreen() | 返回鼠标相对于屏幕的绝对x,y左边 |
getPoint() | 返回事件相对于源组件的x,y坐标 |
translatePoint(int x,int y) | 通过将事件坐标加上指定的x(水平)和y(垂直)偏移量,将事件的坐标平移到新位置 |
键盘事件监听器由KeyListener接口定义,编写键盘事件监听器必须实现该接口中的3个方法,这3个方法分别处理不同的按键事件:
方法 | 说明 |
---|---|
keyPressed(KeyEvent e) | 按下某个按键时调用此方法 |
keyReleased(KeyEvent e) | 释放某个按键时调用此方法 |
keyTyped(KeyEvent e) | 输入某个按键时调用此方法 |
对于键盘事件监听器的适配器是KeyAdapter类,可以继承该类,然后重写需要的单个方法;如果需要处理所有键盘事件的监听,那么干脆实现KeyListener接口,总之需要根据程序需求选择实现KeyListener接口或继承KeyAdapter抽象类。
方法 | 返回值类型 | 说明 |
---|---|---|
getKeyChar() | char | 返回与此事件中的键关联的字符 |
getKeyCode() | int | 返回与此事件中的键关联的整数keyCode |
getKeyModifiers(int modifiers) | String | 返回描述修改键的String,如Shift或Ctrl+Shift |
getKeyText(int keyCode) | String | 返回描述keyCode的String,如HOME、F1或A |
isActionKey() | boolean | 返回此事件中的键是否为“动作”键 |
setKeyChar(char keyChar) | 设置keyCode值,以表示某个逻辑字符 | |
setKeyCode(int keyCode) | 设置keyCode值,以表示某个物理键 |
除了按键事件常用的方法之外,KeyEvent类还定义了按键常量,这些常量分别对应着按键的键值。常量名称以“VK_”为前缀。如VK_A常量代表按键A,它的键值是65。
Integer类的parseInt()方法将整数字符串转换成int基本类型的整数。该方法要求被转换的字符串对象只包含整数,否则会抛出NumberFormatException类的异常对象,说明数字格式异常。
Date类用于表示日期时间,它位于java.util包中。Date类最简单的构造方法就是默认的无参数的Date()构造方法,它使用系统中当前日期和时间创建并初始化Date类的实例对象。Date类的另一个构造方法是Date(long date),这个构造方法接收一个long类型的整数并初始化Date对象,这个long类型的整数是标准基准时间(称为“历元(epoch)”,即1970年1月1日00:00:00)开始的毫秒数。System类的currentTimeMillis()方法可以获取系统当前时间距历元的毫秒数。
after方法用于测试此日期是否在指定日期之后,返回true或false。befor方法与之相反。compareTo方法用于比较两个日期对象的顺序,常用语多个Date对象的排序,若相等,则返回0,否则返回一个大于0的值或小于0的值。
getTime方法返回自1970年1月1日00:00:00GMT以来此Date对象表示的毫秒数;setTime方法用于设置此Date对象,以表示1970年1月1日00:00:00GMT以后time毫秒的时间点。String类的静态format()方法通过特殊转义符作为参数可以实现对日期和时间的格式化。format()方法用于创建格式化的字符串,有两种重载形式:format(String format,Object ... args),该方法使用指定的格式字符串和参数返回一个格式化字符串。格式化后的新字符串使用本地默认的语言环境;format(Local local,String format,Object...args)。对于日期的格式化,可以使用转义符,把Date类的实例对象格式化。使用format()方法不但可以完成日期的格式化,也可实现时间的格式化。
转换符 | 说明 | 示例 |
---|---|---|
%tF | "年-月-日"格式(4位年份) | 2008-03-25 |
%tD | "月/日/年"格式(2位年份) | 03/25/08 |
%tc | 全部日期和时间信息 | 星期二 三月 25 15:20:00 CST 2008 |
%tT | "时:分:秒 PM(AM)"格式(12时制) | 03:22:06下午 |
%tT | "时:分:秒"格式(24时制) | 15:23:50 |
%tR | “时:分”格式(24时制) | 15:25 |
在Java语言中提供了一个执行数学基本运算的Math类,这个类包括一些常见的数学运算方法、这些方法包括三角函数方法、指数函数方法、对数函数方法、平方根函数方法等常用数学函数,除此之外该类还提供了一些常用的数学常量,如PI、E等。Math类包含的所有用于数学运算的函数方法,这些方法都是静态的,所以每个方法只要使用“Math.数学方法”就可以调用了。Math类还提供了角度和弧度相互转换的方法,如toRadians()、toDegrees()方法,但值得注意的是,角度和弧度的互换通常是不精确的。
在具体问题中取整操作使用的很普遍,Java中Math类的关键取整方法如下:
方法 | 说明 | 返回值类型 |
---|---|---|
ceil(double a) | 返回大于等于参数的最小整数 | double |
floor(double a) | 返回小于等于参数的最大整数 | double |
rint(double a) | 返回与参数最接近的整数,如果两个同为整数且数值接近,则结果取偶数 | double |
round(float a) | 将参数加上1/2后返回与参数最近的整数 | int |
round(double a) | 将参数加上1/2后返回与参数最近的整数,然后强制转换为长整型 | long |
Java中主要提供了两种方式产生随机数,分别为调用Math类的random方法和Random类提供的产生各种数据类型随机数的方法。在Math类中存在一个random()方法,用于产生随机数字,这个方法默认产生大于等于0.0,小于1.0的double型随机数,即0≤Math.random()<1.0。m+(int)(Math.Random()*n)返回一个大于等于m小于m+n(不包括m+n)之间的随机数。
使用Math类的random()方法也可以随机生成字符,如生成字符a~z之间的字符:(char)('a'+Math.random()*('z'-'a'+1));求任意两个字符之间的随机字符,可以使用:(char)(char1+Math.random()*(char2-char1+1));
Java还提供了一种可以获取随机数的方式,那就是java.util.Random类,通过实例化一个Random对象创建一个随机数生成器。以这种形式实例化对象时,Java编译器以系统当前时间作为随机数生成器的种子,另外也可以在实例化Random类对象时,设置随机数生成器的种子。
Java主要对浮点型数据进行数字格式化操作,其中浮点型数据包括double型和float型数据。在java中使用java.text.DecimalFormat类格式化数字,在Java中对没有格式化的数据遵循以下原则:如果数据绝对值大于0.01并小于10000000,java将以常规小数形式表示;如果数据绝对值小于0.01或大于10000000,使用科学计数法表示。
DecimalFormat是NumberFormat的一个子类,用于格式化十进制数字,它可以将一些数字格式化为整数、浮点数、科学计数法、百分数等。通过使用该类可以为要输出的数字加上单位或控制数字的精度。一般情况下可以在实例化DecimalFormat对象时传递数字格式,也可以通过DecimalFormat类中的applyPattern()方法来实现数字格式化。当格式化数字时,在DecimalFormat类中使用一些特殊字符构成一个格式化模板,使数字按照一定特殊字符规则进行匹配。
以“0”特殊字符构成的模板进行格式化,当某位数字不存在是,将补位显示0;以“#”特殊字符构成的模板进行格式化,格式化后的数字位数与数字本身的位数一致。还可以使用一些特殊方法对数字进行格式化设置:setGroupingSize()方法用于设置格式化数字的分组大小,而setGroupingUsed()方法用于设置是否可以对数字进行分组操作。