彩蛋:
朕是小白,朕近段时间准备学一下有关线程方面的知识。(下面写的东西是我对线程的一些理解)
今天是2018年5月28号,朕准备每天更新一篇博客来激励自己一直学习,不要间断,毕竟是位又懒自制力又差的家伙,哈哈哈!
言归正传,今晚准备详细记录一下线程创建的两种方式以及这两种方式的优缺点及适应的情况等等。
多进程:。。。
多线程:在一个应用程序中,有多个顺序流同时执行。(线程:顺序执行的代码)
创建线程有两种方式:
一,(目标类来)继承Thread类,重写run方法来创建自己的线程。调用start函数就开始执行线程代码昂发啦!
从Thread类派生一个子类,并创建子类的对象。
子类应该重写Thread类的run方法,写入需要在新线程中执行的语句段。
调用start方法来启动新线程,自动进入run方法。
举例:在新线程中实现计算某个整数的阶乘的过程:
package multi_thread;
public class FactorialThreadTester {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("main thread starts");
FactorialThread thread = new FactorialThread(10);
thread.start();
System.out.println("main thread ends");
}
}
class FactorialThread extends Thread{
private int num;
public FactorialThread(int num) {
this.num = num;
}
public void run() {
int i = num;
int result = 1;
System.out.println("new thread started");
while(i>0) {
result*=i;
i--;
}
System.out.println("The factorial of "+num+" is "+result);
System.out.println("new thread ends");
}
}
运行结果:
备注:factorial(阶乘)
main thread starts
main thread ends
new thread started
The factorial of 10 is 3628800
new thread ends
注:这里之所以要另外写一个主线程(public static void main(String[] args))是为了想说明创建的新线程并不是start()后就立即执行的。它其实也需要CPU来进行调度,轮到它了它才会被执行。通过运行结果我们可以看出。
通过继承Thread类来创建线程大致流程就是上面这样。
下面我想补充一些个人感觉很有用的知识(如果急着看另一种创建线程的方式,请往下翻~~~):
Thread类常用的API方法,整理如下:
名称 | 说明 |
public Thread() | 构造一个新的线程对象(Thread类的构造函数),默认名为Thread-n,n是从0开始递增的整数 |
public Thread(Runnable target) | 构造一个新的线程对象,以一个实现Runnable接口的类的对象为参数,默认名为Thread-n,n是从0开始递增的整数(哈哈哈,从此处构造函数可以看出创建线程的两种不同方式,处处点题,我真机智!) |
public Thread(String name) | 构造一个线程对象,并同时指定线程名 |
public static Thread currentThread() | 返回一个当前正在运行的线程对象 |
public static void yield() | 使当前线程对象暂停,允许别的线程开始运行(注:yield:屈服) |
public static void sleep(long millis) | 使当前线程暂停运行指定毫秒数,但此线程并不失去已获得的锁 |
public void start() | 启动线程,JVM将调用此线程的run方法,结果是将同时运行两个线程,当前线程和执行run方法的线程 |
public void run() | Thread的子类应该重写此方法,内容应为该线程应执行的任务 |
public final void stop() | 停止线程运行,释放该线程占用的对象锁 |
public void interrupt() | 中断此线程 |
public final void join() | 如果启动了线程A,调用join方法将等待线程A死亡才能继续执行当前线程 |
public final void join(long millis) | 如果此前启动了线程A,调用join方法将等待指定毫秒数或线程A死亡才能继续执行当前线程 设置线程优先级 |
public final void setPriority( int newPriority) | 设置线程优先级 |
public final void setDaemon(Boolean on) | 设置是否为后台线程,如果当前运行线程均为后台线程则JVM停止运行。这个方法必须在start()方法前使用(注:daemon:后台程序) |
public final void checkAccess() | 判断当前线程是否有权力修改调用此方法的线程 |
public void setName(String name) | 更改本线程的名称为指定参数 |
public final boolean isAlive() | 测试线程是否处于活动状态,如果线程被启动并且没有死亡则返回true |
二,写一个类来实现Runnable接口,在初始化一个Thread类或者Thread子类的线程对象的时候 ,把该类的对象作为参数传递给那个线程对象。(其中由该类提供run方法)。
Runnable接口:只有一个run()方法
以实现Runnable接口的类的对象为参数建立新的线程
还是举这个例子:在新线程中实现计算某个整数的阶乘的过程:
package multi_thread;
public class FactorialThreadTest_Runnable {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("main thread starts");
FactorialThreadRunnable t = new FactorialThreadRunnable(10);
new Thread(t).start();
System.out.println("new thread started, main thread ends");
}
}
class FactorialThreadRunnable implements Runnable{
private int num;
public FactorialThreadRunnable(int num) {
this.num = num;
}
public void run() {
int i = num;
int result = 1;
while(i>0) {
result*=i;
i--;
}
System.out.println("The factorial of "+num+" is "+result);
System.out.println("new thread ends");
}
}
运行结果:
main thread starts
new thread started, main thread ends
The factorial of 10 is 3628800
new thread ends
再举一个使用Runnable接口实现上面多线程的例子:
package multi_thread;
public class ThreadSleepTester_Runnable {
public static void main(String[] args) {
// TODO Auto-generated method stub
TestThread_Runnable thread1 = new TestThread_Runnable();
TestThread_Runnable thread2 = new TestThread_Runnable();
TestThread_Runnable thread3 = new TestThread_Runnable();
System.out.println("Starting threads");
new Thread(thread1,"Thread1").start();
new Thread(thread2,"Thread2").start();
new Thread(thread3,"Thread3").start();
System.out.println("Threads started,main ends");
}
}
class TestThread_Runnable implements Runnable{
private int sleepTime;
public TestThread_Runnable() {
sleepTime=(int)(Math.random()*6000);
}
public void run() {
try {
System.out.println(Thread.currentThread().getName()+" going to sleep for "+sleepTime);
Thread.sleep(sleepTime);
}
catch(InterruptedException e){};
System.out.println(Thread.currentThread().getName()+" finished");
}
}
运行结果:
Starting threads
Threads started,main ends
Thread1 going to sleep for 5924
Thread3 going to sleep for 2385
Thread2 going to sleep for 2112
Thread2 finished
Thread3 finished
Thread1 finished
通过实现Runnable接口来创建线程大致流程就是上面这样。
接下来我们要思考一个问题:创建线程的两种方式各有其优缺点:
直接继承Thread类:
优点:编写简单,直接继承,重写run方法。
缺点:由于Java的单继承机制,不能再继承其他类了
使用Runnable接口:
优点有两个:一,可以继承其他类。Java不支持多继承,如果已经继承了某个基类,便需要实现Runnable接口来生成多线程。
二,可以将CPU,代码和数据分开,形成清晰的模型,便于多个线程共享资源。
其他的优缺点应该很好理解,但对于第二个(二,可以将CPU,代码和数据分开,形成清晰的模型,便于多个线程共享资源)优点,还是举个例子来说明比较好!
通过实现Runnable接口为啥多个线程就可以共享数据等资源呢???
package multi_thread;
public class ShareTargetTester {
public static void main(String[] args) {
// TODO Auto-generated method stub
TestThread_Runnable thread = new TestThread_Runnable();
System.out.println("Starting threads");
new Thread(thread,"Thread1").start();
new Thread(thread,"Thread2").start();
new Thread(thread,"Thread3").start();
System.out.println("Threads started,main ends");
}
}
class TestThread_Runnable implements Runnable{
private int sleepTime;
public TestThread_Runnable() {
sleepTime=(int)(Math.random()*6000);
}
public void run() {
try {
System.out.println(Thread.currentThread().getName()+" going to sleep for "+sleepTime);
Thread.sleep(sleepTime);
}
catch(InterruptedException e){};
System.out.println(Thread.currentThread().getName()+" finished");
}
}
运行结果:
Starting threads
Threads started,main ends
Thread1 going to sleep for 4422
Thread2 going to sleep for 4422
Thread3 going to sleep for 4422
Thread2 finished
Thread1 finished
Thread3 finished
Thread1、Thread2和Thread3同时结束。(你运行一下看看就知道啦!)
这里为啥Thread1、Thread2和Thread3休眠时间是一样的呢?好奇怪!(emmmm,寡人还是不故弄玄虚了。。。)
原因是:你创建3个新线程传给Thread的参数是一毛一样滴,所以那3个新线程就共享了一个对象里的数据(sleepTime)啊,所以就是一样的!
TestThread_Runnable thread = new TestThread_Runnable();
new Thread(thread,"Thread1").start();
new Thread(thread,"Thread2").start();
new Thread(thread,"Thread3").start();
扶朕起来,朕还可以再举一个例子:
用三个线程模拟三个售票口,总共出售10张票。(注:这三个线程应该共享10张票的数据,因为总共就10张票,三个进程一起来销售)
package multi_thread;
public class sellTicketsTester {
public static void main(String[] args) {
// TODO Auto-generated method stub
sellTickets t = new sellTickets();
new Thread(t,"Thread1").start();
new Thread(t,"Thread2").start();
new Thread(t,"Thread3").start();
}
}
class sellTickets implements Runnable{
private int tickets = 10;
public void run() {
while(tickets>0) {
System.out.println(Thread.currentThread().getName()+" is selling ticket "+tickets--);
}
}
}
运行结果:
Thread1 is selling ticket 10
Thread3 is selling ticket 8
Thread2 is selling ticket 9
Thread3 is selling ticket 6
Thread3 is selling ticket 4
Thread3 is selling ticket 3
Thread3 is selling ticket 2
Thread3 is selling ticket 1
Thread1 is selling ticket 7
Thread2 is selling ticket 5
哈哈哈,看完运行结果你就明白我滴意思了吧!
在这个例子中,创建了3 个线程,每个线程调用的是同一个sellTickets对象t中的run()方法,访问的也是同一个对象t中的变量tickets。
但是,如果是通过创建Thread类的子类来模拟售票过程,再创建3个新线程,则每个线程都有各自的方法和变量,虽然方法是相同的,但确是各有10张票,结果就会是每个线程都卖出了10张票,总共卖出了30张票,与原意就不符了!!!
(个人感觉线程间的数据共享挺重要的,也挺有意思的)
但是,上面代码运行多次后,会发现也会出现一些问题,比如某一个线程卖出第0张票的情况,比如两个不同的线程卖出同一张票的情况等等……没有保证线程的安全,具体见下:
public class sellTicketsTester {
public static void main(String[] args) {
// TODO Auto-generated method stub
sellTickets t = new sellTickets();
new Thread(t,"Thread1").start();
new Thread(t,"Thread2").start();
new Thread(t,"Thread3").start();
new Thread(t,"Thread4").start();
new Thread(t,"Thread5").start();
new Thread(t,"Thread6").start();
new Thread(t,"Thread7").start();
}
}
class sellTickets implements Runnable{
private int tickets = 10;
public void run() {
while(tickets>0) {
System.out.println(Thread.currentThread().getName()+" is selling ticket "+tickets--);
}
}
}
运行结果见下:
Thread2 is selling ticket 10
Thread2 is selling ticket 9
Thread2 is selling ticket 8
Thread2 is selling ticket 7
Thread2 is selling ticket 6
Thread2 is selling ticket 5
Thread2 is selling ticket 4
Thread2 is selling ticket 3
Thread2 is selling ticket 2
Thread2 is selling ticket 1
Thread1 is selling ticket 0
Thread3 is selling ticket 10
Thread1 is selling ticket 10
Thread2 is selling ticket 9
Thread1 is selling ticket 7
Thread3 is selling ticket 8
Thread1 is selling ticket 5
Thread2 is selling ticket 6
Thread1 is selling ticket 3
Thread3 is selling ticket 4
Thread1 is selling ticket 1
Thread2 is selling ticket 2
为了保证线程安全,应该使用同步方法。
1、在run方法前面加synchronized关键字
public class sellTicketsTester {
public static void main(String[] args) {
// TODO Auto-generated method stub
sellTickets t = new sellTickets();
new Thread(t,"Thread1").start();
new Thread(t,"Thread2").start();
new Thread(t,"Thread3").start();
new Thread(t,"Thread4").start();
new Thread(t,"Thread5").start();
new Thread(t,"Thread6").start();
new Thread(t,"Thread7").start();
}
}
class sellTickets implements Runnable{
private int tickets = 10;
public synchronized void run() {
while(tickets>0) {
System.out.println(Thread.currentThread().getName()+" is selling ticket "+tickets--);
}
}
}
2、同步代码块
public class sellTicketsTester {
public static void main(String[] args) {
// TODO Auto-generated method stub
sellTickets t = new sellTickets();
new Thread(t,"Thread1").start();
new Thread(t,"Thread2").start();
new Thread(t,"Thread3").start();
new Thread(t,"Thread4").start();
new Thread(t,"Thread5").start();
new Thread(t,"Thread6").start();
new Thread(t,"Thread7").start();
}
}
class sellTickets implements Runnable{
private int tickets = 10;
public void run() {
synchronized (this){
while(tickets>0) {
System.out.println(Thread.currentThread().getName()+" is selling ticket "+tickets--);
}
}
}
}
我还想补充一下线程有关的其他知识:
一、线程休眠:(调用sleep方法)
比如之前写的程序:
package multi_thread;
public class FactorialThreadTester {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("main thread starts");
FactorialThread thread = new FactorialThread(10);
thread.start();
System.out.println("main thread ends");
}
}
class FactorialThread extends Thread{
private int num;
public FactorialThread(int num) {
this.num = num;
}
public void run() {
int i = num;
int result = 1;
System.out.println("new thread started");
while(i>0) {
result*=i;
i--;
}
System.out.println("The factorial of "+num+" is "+result);
System.out.println("new thread ends");
}
}
运行结果:
备注:factorial(阶乘)
main thread starts
main thread ends
new thread started
The factorial of 10 is 3628800
new thread ends
现在我让主线程休眠一毫秒,运行结果就会发生改变了哦,因为主线程休眠了一毫秒!
package multi_thread;
public class FactorialThreadTester {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("main thread starts");
FactorialThread thread = new FactorialThread(10);
thread.start();
try { Thread.sleep(1);}
catch(Exception e) {};
System.out.println("main thread ends");
}
}
class FactorialThread extends Thread{
private int num;
public FactorialThread(int num) {
this.num = num;
}
public void run() {
int i = num;
int result = 1;
System.out.println("new thread started");
while(i>0) {
result*=i;
i--;
}
System.out.println("The factorial of "+num+" is "+result);
System.out.println("new thread ends");
}
}
与上一个程序不同的是我只添加了红色标记的语句,运行结果见下:
main thread starts
new thread started
The factorial of 10 is 3628800
new thread ends
main thread ends
新线程结束后main线程才结束。
多个线程休眠的情况见下:
package multi_thread;
public class ThreadSleepTester {
public static void main(String[] args) {
// TODO Auto-generated method stub
TestThread thread1 = new TestThread("Thread1");
TestThread thread2 = new TestThread("Thread2");
TestThread thread3 = new TestThread("Thread3");
System.out.println("Starting threads");
thread1.start();
thread2.start();
thread3.start();
System.out.println("Threads started,main ends");
}
}
class TestThread extends Thread{
private int sleepTime;
public TestThread(String name) {
super(name);
sleepTime=(int)(Math.random()*6000);
}
public void run() {
try {
System.out.println(getName()+" going to sleep for "+sleepTime);
Thread.sleep(sleepTime);
}
catch(InterruptedException e){};
System.out.println(getName()+" finished");
}
}
备注:Math.random()产生0~1之间的随机数。
Thread.sleep(sleepTime),其中sleepTime的单位是毫秒!
运行结果见下:
Starting threads
Threads started,main ends
Thread1 going to sleep for 5492
Thread2 going to sleep for 1295
Thread3 going to sleep for 3126
Thread2 finished
Thread3 finished
Thread1 finished
由于线程1休眠时间最长,所以最后结束;线程2休眠时间最短,所以最先结束。
每次运行,都会随机产生不同的休眠时间,所以其实每次运行的结果都不相同!
结尾也有彩蛋,嘻嘻:
eclipse变量名如何实现一改全改呢?(个人感觉还是很有用的小技巧)
双击选择要改的变量名,右键下拉菜单选择refactor重构选项,选择rename,然后就输入自己需要改成的名字,回车,搞定!