一.概述
1.多进程的概述
A:线程和进程
要想说线程,首先必须得聊聊进程,因为线程是依赖于进程存在的。
B:进程概述
什么是进程呢?通过任务管理器我们就可以看到进程的存在。
概念:进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
C:多进程的意义
单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),
所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。
对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。
因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,
所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率。
2.多线程的概述
A:什么是线程
在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。是程序使用CPU的基本单位。
所以,进程是拥有资源的基本单位, 线程是CPU调度的基本单位。
B:多线程有什么意义呢?
多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
那么怎么理解这个问题呢?
我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到
CPU的执行权的概率应该比较单线程程序抢到的概率要大.那么也就是说,CPU在多线程程序
中执行的时间要比单线程多,所以就提高了程序的使用率.但是即使是多线程程序,那么他们
中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性.
C:大家注意两个词汇的区别:并行和并发。
前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
后者是物理上同时发生,指在某一个时间点同时运行多个程序。
什么是并发 ?
并发 : 指应用能够交替执行不同的任务, 其实并发有点类似于多线程的原理, 多线程并非是如果你开两个线程同时执行多个任务。
执行, 就是在你几乎不可能察觉到的速度不断去切换这两个任务, 已达到"同时执行效果", 其实并不是的, 只是计算机的速度太快, 我们无法察觉到而已. 就类似于你, 吃一口饭喝一口水, 以正常速度来看, 完全能够看的出来, 当你把这个过程以n倍速度执行时..可以想象一下.
什么是并行 ?
并行 : 指应用能够同时执行不同的任务, 例:吃饭的时候可以边吃饭边打电话, 这两件事情可以同时执行
3.java程序运行原理
A:Java程序运行原理
Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。
该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
所以 main方法运行在主线程中。
B:JVM的启动是多线程的吗: JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
二.多线程的实现方式一
1.概述
A:如何实现多线程:
如何实现呢?
由于线程是依赖进程而存在的,所以我们应该先创建一个进程(JVM)出来。
而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。
但是Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。
但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。
由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,
然后提供一些类供我们使用。我们就可以实现多线程程序了。
参考 Thread类
B:多线程程序实现的方式1
a:继承Thread类
b:步骤:
1.将一个类声明为 Thread 的子类。
2.该子类应重写 Thread 类的 run 方法。
3.接下来可以分配并启动该子类的实例
c:几个小问题:
启动线程使用的是那个方法:start()方法
线程能不能多次启动:不能,会报异常
run()和start()方法的区别
我们启动线程使用不是run方法,而应该是start方法.使该线程开始执行;
Java 虚拟机调用该线程的 run 方法。
为什么要重写run方法?
这个类是一个线程类,那么在这个类中我们可不可以写一些其他的方法呢?
我们可以在写其他的方法,那么其他方法中封装的代码都是需要被我们线程执行的吗? 不一定
那么也就是run方法中封装应该是必须被线程执行的代码.
run方法中的代码的书写原则: 一般是比较耗时的代码
2.注意事项
1.正确开启一个线程的方式是调用 start();当线程开启后,由子线程去调用run()方法来执行run()方法里面的代码
2.线程不要重复开启 重复开启会抛异常。IllegalThreadStateException
package org. westos. demo2;
public class MyTest {
public static void main ( String[ ] args) {
System. out. println ( "主线程代码执行" ) ;
System. out. println ( "主线程代码执行" ) ;
System. out. println ( "主线程代码执行" ) ;
System. out. println ( "当主线程执行到此处时,出现耗时操作,开启子线程来执行耗时代码" ) ;
MyThread myThread = new MyThread ( ) ;
myThread. start ( ) ;
System. out. println ( "下面的代码1" ) ;
System. out. println ( "下面的代码1" ) ;
System. out. println ( "下面的代码1" ) ;
System. out. println ( "下面的代码1" ) ;
MyThread myThread2 = new MyThread ( ) ;
myThread2. start ( ) ;
System. out. println ( "下面的代码2" ) ;
System. out. println ( "下面的代码2" ) ;
System. out. println ( "下面的代码2" ) ;
}
}
-- -- -- -- -- -- -- -- -- -- -- -- --
package org. westos. demo2;
public class MyThread extends Thread {
@Override
public void run ( ) {
for ( int i = 0 ; i < 100 ; i++ ) {
System. out. println ( i) ;
}
}
}
3.获取和设置线程对象名称
A:Thread类的基本获取和设置方法
public final String getName()//获取线程名称
public final void setName(String name)//设置线程名称
其实通过构造方法也可以给线程起名字
思考:
如何获取main方法所在的线程名称呢?
public static Thread currentThread()//获取当前执行的线程
我们现在是想获取主线程的名称,那么我们可不可以先获取到主线程,
如果我们能获取到主线程,那么我们就可以调用getName方法获取对应的名称.
如何获取主线程呢? public static Thread currentThread()返回对当前正在执行的线程对象的引用。
package org. westos. demo2;
public class MyThread extends Thread {
@Override
public void run ( ) {
for ( int i = 0 ; i < 100 ; i++ ) {
Thread thread = Thread. currentThread ( ) ;
String name = thread. getName ( ) ;
System. out. println ( "线程:" + name + ":" + i) ;
}
}
}
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
package org. westos. demo2;
public class MyTest {
public static void main ( String[ ] args) {
System. out. println ( "主线程执行了" ) ;
Thread th1 = Thread. currentThread ( ) ;
System. out. println ( th1) ;
th1. setName ( "主线程" ) ;
String name = th1. getName ( ) ;
System. out. println ( name) ;
MyThread th2 = new MyThread ( ) ;
MyThread th3 = new MyThread ( ) ;
MyThread th4 = new MyThread ( ) ;
th2. setName ( "李白" ) ;
th3. setName ( "杜甫" ) ;
th4. setName ( "王维" ) ;
th2. start ( ) ;
th3. start ( ) ;
th4. start ( ) ;
}
}
4.复制一个文本,一个视频
package org. westos. demo4;
import java. io. FileInputStream;
import java. io. FileNotFoundException;
import java. io. FileOutputStream;
import java. io. IOException;
import java. util. WeakHashMap;
public class CopyTextThread extends Thread {
@Override
public void run ( ) {
FileInputStream in= null;
FileOutputStream out= null;
try {
in = new FileInputStream ( "MyTest.java" ) ;
out = new FileOutputStream ( "MyTest2.java" ) ;
int len= 0 ;
byte [ ] bytes = new byte [ 1024 * 8 ] ;
while ( ( len= in. read ( bytes) ) != - 1 ) {
out. write ( bytes, 0 , len) ;
out. flush ( ) ;
}
} catch ( IOException e) {
e. printStackTrace ( ) ;
} finally {
try {
in. close ( ) ;
out. close ( ) ;
} catch ( IOException e) {
e. printStackTrace ( ) ;
}
}
}
}
-- -- -- -- -- -- -- -- -- -- -- --
package org. westos. demo4;
import java. io. FileInputStream;
import java. io. FileOutputStream;
import java. io. IOException;
public class CopyVideoThread extends Thread {
@Override
public void run ( ) {
try {
FileInputStream in = new FileInputStream ( "Rec 2020-06-06 0001.mp4" ) ;
FileOutputStream out = new FileOutputStream ( "C:\\Users\\user\\Desktop\\aa.mp4" ) ;
int len= 0 ;
byte [ ] bytes = new byte [ 1024 * 8 ] ;
while ( ( len= in. read ( bytes) ) != - 1 ) {
out. write ( bytes, 0 , len) ;
out. flush ( ) ;
}
in. close ( ) ;
out. close ( ) ;
} catch ( IOException e) {
e. printStackTrace ( ) ;
}
}
}
-- -- -- -- -- -- -- -- -- -- -- -- -- -
package org. westos. demo4;
public class MyTest {
public static void main ( String[ ] args) {
System. out. println ( "主线程执行了" ) ;
CopyTextThread th1 = new CopyTextThread ( ) ;
th1. start ( ) ;
CopyVideoThread th2 = new CopyVideoThread ( ) ;
th2. start ( ) ;
System. out. println ( "下面的代码" ) ;
System. out. println ( "下面的代码" ) ;
System. out. println ( "下面的代码" ) ;
}
}
5.线程调度及获取和设置线程优先级
A:线程的执行
假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,
线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?
B:线程有两种调度模型:
分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,
优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。
C:如何设置和获取线程优先级
public final int getPriority() //获取线程的优先级
public final void setPriority(int newPriority)//设置线程的优先级
优先级从1-10,1最小,10最大,默认优先级为5
那么这个默认的优先级是多少呢,以及我们如何来获取线程的优先级.
获取线程的优先级:
public final int getPriority()返回线程的优先级。
线程的默认优先级是5
给线程设置优先级:
public final void setPriority(int newPriority)
注意事项: 有的时候我们给线程设置了指定的优先级,但是该线程并不是按照优先级高的线程执行,那是为什么呢?
- 因为线程的优先级的大小仅仅表示这个线程被CPU执行的概率增大了.但是我们都知道多线程具有随机性,
- 所以有的时候一两次的运行说明不了问题
package org. westos. demo5;
import sun. plugin2. gluegen. runtime. CPU;
public class MyTest {
public static void main ( String[ ] args) {
MyThread th1 = new MyThread ( ) ;
MyThread th2 = new MyThread ( ) ;
MyThread th3 = new MyThread ( ) ;
th1. setPriority ( 1 ) ;
th2. setPriority ( 2 ) ;
th3. setPriority ( Thread. MAX_PRIORITY) ;
int priority1 = th1. getPriority ( ) ;
int priority2 = th2. getPriority ( ) ;
int priority3= th3. getPriority ( ) ;
System. out. println ( "线程的优先级" + priority1) ;
System. out. println ( "线程的优先级" + priority2) ;
System. out. println ( "线程的优先级" + priority3) ;
th1. setName ( "李白" ) ;
th2. setName ( "杜甫" ) ;
th3. setName ( "王维" ) ;
th1. start ( ) ;
th2. start ( ) ;
th3. start ( ) ;
}
}
-- -- -- -- -- -- -- -- -- -- --
package org. westos. demo5;
public class MyThread extends Thread {
@Override
public void run ( ) {
for ( int i = 0 ; i < 100 ; i++ ) {
System. out. println ( "线程" + this . getName ( ) + ":" + i) ;
}
}
}
三.多线程中的方法
1.休眠线程
public static void sleep(long millis) 线程休眠
注意:在哪个线程里面调用sleep()方法就休眠哪个线程。
package org. westos. demo;
public class MyThread extends Thread {
public MyThread ( ) {
}
public MyThread ( String name) {
super ( name) ;
}
@Override
public void run ( ) {
for ( int i = 0 ; i < 100 ; i++ ) {
System. out. println ( i) ;
try {
Thread. sleep ( 4000 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
}
-- -- -- -- -- -- -- -- -- -- --
package org. westos. demo;
public class MyTest {
public static void main ( String[ ] args) throws InterruptedException {
System. out. println ( "main线程开始执行" ) ;
Thread. sleep ( 2000 ) ;
MyThread th1 = new MyThread ( "th1" ) ;
MyThread th2 = new MyThread ( "th2" ) ;
th1. start ( ) ;
th2. start ( ) ;
}
}
2.加入线程
A:
加入线程:
public final void join ()
意思就是:等待该线程执行完毕了以后, 其他线程才能再次执行
注意事项:
在线程启动之后, 在调用方法
join ()可以让多个线程并发执行,变成串行(挨个排队执行,不用抢)
package org. westos. demo2;
public class MyThread extends Thread {
public MyThread ( ) {
}
public MyThread ( String name) {
super ( name) ;
}
@Override
public void run ( ) {
for ( int i = 0 ; i < 10 ; i++ ) {
System. out. println ( Thread. currentThread ( ) . getName ( ) + "===" + i) ;
}
}
}
-- -- -- -- -- -- -- -- -- -- -- -- -
package org. westos. demo2;
public class MyTest {
public static void main ( String[ ] args) throws InterruptedException {
MyThread th1 = new MyThread ( "th1" ) ;
MyThread th2 = new MyThread ( "th2" ) ;
th1. start ( ) ;
th1. join ( ) ;
th2. start ( ) ;
th2. join ( ) ;
}
}
3.礼让线程
A:礼让线程: public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。
B:
按照我们的想法,这个礼让应该是一个线程执行一次,但是通过我们的测试,效果好像不太明显.
那是为什么呢?
这个礼让是要暂停当前正在执行的线程,这个暂停的时间是相当短的,如果在这个线程暂停完毕以后,其他的线程还没有
抢占到CPU的执行权,那么这个时候这个线程应该再次和其他线程抢占CPU的执行权.
package org. westos. demo3;
public class MyThread extends Thread {
public MyThread ( ) {
}
public MyThread ( String name) {
super ( name) ;
}
@Override
public void run ( ) {
for ( int i = 0 ; i < 10 ; i++ ) {
Thread. yield ( ) ;
System. out. println ( Thread. currentThread ( ) . getName ( ) + "===" + i) ;
}
}
}
-- -- -- -- -- -- -- -- -- -- -- --
package org. westos. demo3;
public class MyTest {
public static void main ( String[ ] args) {
MyThread th1 = new MyThread ( "th1" ) ;
MyThread th2 = new MyThread ( "th2" ) ;
th1. start ( ) ;
th2. start ( ) ;
}
}
4.守护线程
A:守护线程: public final void setDaemon(boolean on):
将该线程标记为守护线程,当用户线程结束后,那么守护线程,就要马上结束。
Java用户线程和守护线程
1.用户线程和守护线程的区别
用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束。很好理解,守护线程是用来辅助用户线程的,如公司的保安和员工,各司其职,当员工都离开后,保安自然下班了。
2.用户线程和守护线程的适用场景
由两者的区别及dead时间点可知,守护线程不适合用于输入输出或计算等操作,因为用户线程执行完毕,程序就dead了,适用于辅助用户线程的场景,如JVM的垃圾回收,内存管理都是守护线程,还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。
3.创建守护线程
调用线程对象的方法setDaemon(true),设置线程为守护线程。
1)thread.setDaemon(true)必须在thread.start()之前设置。
2)在Daemon线程中产生的新线程也是Daemon的。
3)不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。
因为Daemon Thread还没来得及进行操作,虚拟机可能已经退出了。
4.Java守护线程和Linux守护进程
两者不是一个概念。Linux守护进程是后台服务进程,没有控制台。
在Windows中,你可以运行javaw来达到释放控制台的目的,在Unix下你加&在命令的最后就行了。所以守护进程并非一定需要的。
package org. westos. demo4;
public class MyThread extends Thread {
public MyThread ( ) {
}
public MyThread ( String name) {
super ( name) ;
}
@Override
public void run ( ) {
for ( int i = 0 ; i < 100 ; i++ ) {
String name = Thread. currentThread ( ) . getName ( ) ;
System. out. println ( name+ "===" + i) ;
}
}
}
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -
package org. westos. demo4;
public class MyTest {
public static void main ( String[ ] args) {
String name = Thread. currentThread ( ) . getName ( ) ;
System. out. println ( name) ;
Thread. currentThread ( ) . setName ( "刘备" ) ;
System. out. println ( Thread. currentThread ( ) . getName ( ) ) ;
for ( int i = 0 ; i < 10 ; i++ ) {
System. out. println ( Thread. currentThread ( ) . getName ( ) + "==" + i) ;
}
MyThread th1 = new MyThread ( ) ;
MyThread th2 = new MyThread ( ) ;
th1. setName ( "张飞" ) ;
th2. setName ( "关羽" ) ;
th1. setDaemon ( true ) ;
th2. setDaemon ( true ) ;
th1. start ( ) ;
th2. start ( ) ;
}
}
5.中断线程
A:中断线程
public final void stop(): 停止线程的运行
public void interrupt(): 清除线程的阻塞状态,查看API可得当线程调用
wait(),sleep(long time)方法的时候处于阻塞状态,
可以通过这个方法清除阻塞
package org. westos. demo5;
public class MyThread extends Thread {
@Override
public void run ( ) {
for ( int i = 0 ; i < 100 ; i++ ) {
try {
Thread. sleep ( 2000 ) ;
System. out. println ( Thread. currentThread ( ) . getName ( ) + "===" + i) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
}
-- -- -- -- -- -- -- -- --
package org. westos. demo5;
public class MyTest {
public static void main ( String[ ] args) throws InterruptedException {
MyThread th1 = new MyThread ( ) ;
th1. setName ( "th1" ) ;
th1. start ( ) ;
Thread. sleep ( 2000 ) ;
th1. interrupt ( ) ;
}
}
四.创建线程的第二种方式
1.概述
A:实现Runnable接口 这种方式扩展性强 实现一个接口 还可以再去继承其他类
a:如何获取线程名称
b:如何给线程设置名称
c:实现接口方式的好处
可以避免由于Java单继承带来的局限性。
package org. westos. demo6;
public class MyRunnable implements Runnable {
@Override
public void run ( ) {
for ( int i = 0 ; i < 100 ; i++ ) {
System. out. println ( Thread. currentThread ( ) . getName ( ) + "===" + i) ;
}
}
}
-- -- -- -- -- -- -- -- -- -- -- -- -- --
package org. westos. demo6;
public class MyRunnable2 implements Runnable {
@Override
public void run ( ) {
for ( int i = 0 ; i < 100 ; i++ ) {
System. out. println ( Thread. currentThread ( ) . getName ( ) + "===" + i) ;
}
}
}
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
package org. westos. demo6;
public class MyTest {
public static void main ( String[ ] args) {
MyRunnable myRunnable = new MyRunnable ( ) ;
Thread th1 = new Thread ( myRunnable) ;
th1. setName ( "th1" ) ;
th1. start ( ) ;
Thread th2 = new Thread ( new MyRunnable2 ( ) ) ;
th2. setName ( "th2" ) ;
th2. start ( ) ;
}
}
2.构造方法
Thread(Runnable target, String name)
分配新的 Thread 对象。
package org. westos. demo7;
public class MyRunnable implements Runnable {
@Override
public void run ( ) {
for ( int i = 0 ; i < 100 ; i++ ) {
System. out. println ( Thread. currentThread ( ) . getName ( ) + "===" + i) ;
}
}
}
-- -- -- -- -- -- -- -- -- --
package org. westos. demo7;
public class MyTest {
public static void main ( String[ ] args) {
MyRunnable myRunnable = new MyRunnable ( ) ;
Thread th1 = new Thread ( myRunnable, "th1" ) ;
Thread th2 = new Thread ( myRunnable, "th2" ) ;
th1. start ( ) ;
th2. start ( ) ;
}
}
五.创建线程的第三种方式
1.概述
A:实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
B:执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
C:实现步骤
1.创建一个类实现Callable 接口
2.创建一个FutureTask类将Callable接口的子类对象作为参数传进去
3.创建Thread类,将FutureTask对象作为参数传进去
4.开启线程
package org. westos. demo8;
import java. util. concurrent. Callable;
public class MyCallable implements Callable < Integer> {
private int num;
public MyCallable ( int num) {
this . num = num;
}
@Override
public Integer call ( ) throws Exception {
int sum= 0 ;
for ( int i = 1 ; i <= num; i++ ) {
sum+= i;
}
return sum;
}
}
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
package org. westos. demo8;
import javax. swing. text. TabableView;
import java. util. concurrent. Callable;
import java. util. concurrent. ExecutionException;
import java. util. concurrent. FutureTask;
public class MyTest {
public static void main ( String[ ] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable ( 100 ) ;
FutureTask< Integer> task = new FutureTask < > ( myCallable) ;
Thread th1 = new Thread ( task) ;
th1. start ( ) ;
Integer integer = task. get ( ) ;
System. out. println ( integer) ;
MyCallable myCallable2 = new MyCallable ( 20 ) ;
FutureTask< Integer> task2 = new FutureTask < > ( myCallable2) ;
Thread th2 = new Thread ( task2) ;
th2. start ( ) ;
Integer integer2 = task2. get ( ) ;
System. out. println ( integer2) ;
}
}
六.卖电影票案例
1.通过继承Thread类实现
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
通过继承Thread类实现
分析:
a: 三个窗口其实就是3个线程
b: 定义票的数量100张
c: 创建线程对象,启动线程. 每卖一张这个票数应该--
package org. westos. demo9;
public class MyThread extends Thread {
static int piao = 100 ;
public MyThread ( String name) {
super ( name) ;
}
@Override
public void run ( ) {
while ( true ) {
if ( piao>= 1 ) {
System. out. println ( this . getName ( ) + "正在出售第:" + ( piao-- ) + " 张票" ) ;
}
}
}
}
-- -- -- -- -- -- -
package org. westos. demo9;
public class MyTest {
public static void main ( String[ ] args) {
MyThread th1 = new MyThread ( "窗口1" ) ;
MyThread th2 = new MyThread ( "窗口2" ) ;
MyThread th3 = new MyThread ( "窗口3" ) ;
th1. start ( ) ;
th2. start ( ) ;
th3. start ( ) ;
}
}
2.实现Runnable接口
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
通过实现Runnable接口实现
package org. westos. demo91;
public class MyRunnable implements Runnable {
static int piao= 100 ;
@Override
public void run ( ) {
while ( true ) {
if ( piao> 0 ) {
System. out. println ( Thread. currentThread ( ) . getName ( ) + "正在出售第:" + ( piao-- ) + " 张票" ) ;
}
}
}
}
-- -- -- -- -- -- -- -- -- -- --
package org. westos. demo91;
public class MyTest {
public static void main ( String[ ] args) {
MyRunnable myRunnable = new MyRunnable ( ) ;
Thread th1 = new Thread ( myRunnable) ;
Thread th2 = new Thread ( myRunnable) ;
Thread th3 = new Thread ( myRunnable) ;
th1. start ( ) ;
th2. start ( ) ;
th3. start ( ) ;
}
}