package Thread_kng; class S_thrd_1{ public void f(){ while (true){ System.out.printf("Thread main is runing!\n"); } //System.out.printf("f() is done!\n"); //compilation fail } } public class S_thread_expl{ public static void g(){ S_thrd_1 s = new S_thrd_1(); s.f(); System.out.printf("g() is done!\n"); } } |
上面的例子中, g() 函数调用了f() 函数,
g()函数在最后尝试输出"g() is done!" , 但是因为f() 是一个无限循环. 所以g() 调用f()后, 根本没机会执行后面的语句!
也就是说, 虽然g() 跟 f() 是两个不同类的函数, 但是它们是在程序中的同一个线程(主线程)内执行.
在同一个线程内, 语句总是按程序猿编写的顺序依次执行, 一条语句未执行完时, 不会跳到后面执行其他的语句.
五. 1个多线程java程序的简单例子.
例子如下:
package Thread_kng; class M_thrd_1 extends Thread{ //extends public M_thrd_1(String name){ super(name); } public void run(){ //overwrite the method run() of superclass while (true){ System.out.printf("Thread " + this.getName()+ " is runing!\n"); } //System.out.printf("f() is done!\n"); //compilation fail } } public class M_thread_expl{ public static void g(){ M_thrd_1 s = new M_thrd_1("T1"); s.start(); //start()method is extended from superclass, it will call the method run() M_thrd_1 s2 = new M_thrd_1("T2"); s2.start(); System.out.printf("g() is done!\n"); } |
上面例子中, 类 M_thrd_1继承了线程类Thread. 并重写了run方法.
在下面的g()函数中.
实例化了两个类M_thrd_1的对象s, s1 的对象.
执行了start()方法.
start()方法继承字Thread, 它会启动1新线程, 并调用run()函数.
而g()后面本身也在不断循环输出"THread Main is running"
所以输出如下:
可以 见到, 输出结果是T1,T2 和Main 交替输出在屏幕上.
实际上, 这个程序有3个线程.
其中1个就是主线程.
主线程main通过实例化两个M_thrd_1的对象, 调用其start()函数开启了两个子线程.
其中1个子线程不断输出T1
另1个子线程不断输出T2
但是主线程并不会等待其子线程执行完成, 会继续执行下面的代码,所以不断循环输出Main!
所以这个例子实际上要3个线程在输出信息到屏幕了!
六. Java 开启一条新进程的两种方式.
java开启一条新线程, 有两种方式. 但是都要利用java的线程基类Thread.
6.1 方式1. 创建Thread的派生类
这个就类似上面的例子:
具体步骤如下:
6.1.1 线程准备: 新建1个类, 继承线程类Thread. 将业务代码写入重写后的run() 方法中.
例如上面的例子中的M_thrd_1类
class M_thrd_1 extends Thread{ //extends public M_thrd_1(String name){ super(name); } public void run(){ //overwrite the method run() of superclass while (true){ System.out.printf("Thread " + this.getName()+ " is runing!\n"); } //System.out.printf("f() is done!\n"); //compilation fail } } |
可以见到, 我们将线程要执行的业务代码写入了run() 方法.
6.1.2 启动线程: 新建1个上述类的对象, 运行start()方法
例如
M_thrd_1 s = new M_thrd();
s. start();
其中start() 是类M_thrd_1 继承自超类Thread的.
我们看看JDK API 对start() 方法的定义:
void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
可以见到, 调用了start() 方法后, 会开始执行改线程, 也就是在当前线程下开启一条新线程.
而这条新线程做什么呢, 就是执行run()里面的代码.
所以我们一般只需要把也业务的代码写入run()里面就ok了. 不要重写start()方法.
注意, 如果直接执行s.run(); 则会在当前线程下执行run里面的代码, 并没有开启一条新线程. 区别很大的.
6.2 方式2. 创建1个类, 实现Runnable 接口
步骤跟上面的方式差别不大
6.2.1 线程准备: 新建1个类, 实现接口Runnable. 将业务代码写入重写后的run() 方法中.
例子:
class M_thrd_2 implements Runnable{ //extends public int id = 0; public void run(){ //overwrite the method run() of superclass while (true){ System.out.printf("%s: Thread " + this.id+ " is runing!\n", Thread.currentThread().getName()); } //System.out.printf("f() is done!\n"); //compilation fail } } |
上面的例子 M_thrd_2 就实现了接口Runnable
并重写了接口Runnable 的抽象方法run();
注意. currentThread() 是类Thread的一个静态方法, 作用是获取当前语句所在的线程.
6.2.2 启动线程: 新建1个上述类的对象s, 在新建1个类Thread的对象t, 把s作为一个参数用于t的构造方法. 并执行t.start()
貌似比方式一复杂.
其实非如下:
new Thread(new M_thrd_2()).start()
例子:
public class M_thread_expl2{ public static void g(){ M_thrd_2 s = new M_thrd_2(); s.id = 1; Thread t1 = new Thread(s); t1.start(); Thread t2 = new Thread(s); s.id = 2; t2.start(); } } |
在g() 函数中,
首先新建1个类M_thrd_2的 对象s.
并利用s作为参数 建立了两个线程类Thread的对象t1 和 t2. 并启动这两个线程.
注:
1. 这个例子中有3个线程, 其中1个主线程(也就是g() 函数所在的线程). 开启了两个子线程, 该两个子线程都在不断循环输出信息到屏幕上.
2. Thread(object ob) 是1个属于类Thread的构造方法, 其中的对象ob 必须实现Runnable 接口.
3. 这个例子执行时 , 第一个t1首先会输出id = 1, 但是当第二个线程t2开始执行后, t1会输出id=2, 因为t1, 和t2都是利用同1个对象建立的.
也就是说, 这个对象的变动会同时影响两个线程.
输出如下:
6.3 两种方式的区别
1. 方式1是创建继承类Thread的派生类, 方式2是创建实现Runnable接口的类.
2. 启动方式: 方式1是直接调用业务类的对象的start()方法, 方式2是利用业务类类的对象新建1个类Thread的对象, 并调用该对象的start()方法.
3. 如果要启动多个线程, 通过方式1需要新建多个不同业务类的对象, 方式2 则可以通过业务1个对象 构建多个Thread类对象, 但是业务对象的变动会同时影响对应的多个线程.
七. 关于线程的一些常用方法介绍.
7.1 run()
我们要吧线把要执行的业务代码写入这个方法. 上面提过了. 注意, 如果我们直接执行1个线程对象的run()方法可以合法的, 但是仍然是在当前线程内执行, 并没有开始一条新线程.
7.2 Start()
当1个线程or其派生类执行1个start()方法. 就开启1个新线程, 并调用该类的run()方法.
注意: 1个线程如果已经调用过一次start(), 再调用start()则或抛异常.
7.3 stop()
停止1个1个线程.
7.4 setName() 和 getName()
设置和获得对应线程的名字.
7.5 Thread.currentThread()
注意这个方法是一个static方法, 而上面的都不是静态方法.
这个方法返回当前执行语句所属的线程.
例如1个线程对象A. 它的run() 方法里调用了 Thread.currentThread().getName() 函数.
假如直接执行A.run() 则返回的是当前线程(而不是A线程) 的名字, 因为对象A并没有启动自己的线程.
假如执行难过A.start(), 那么该函数就返回A启动的线程的名字了.
八, 小结
这篇文章只是简介, 很多重要的方法例如 sleep() , wait() 等都没有介绍, 这些是线程控制的内容. 本吊打算在另一篇博文里介绍.