线程Thread,多线程Multi-Thread
1、线程概念
什么是线程:线程就是程序中单独顺序的流控制。线程本身不能运行,它只能用于程序中。
什么是多线程:多线程则指的是在单个程序中可以同时运行多个不同的线程执行不同的任务.
线程是程序内的顺序控制流,只能使用分配给程序的资源和环境。
多线程编程的目的,就是"最大限度地利用CPU资源",当某一线程的处理不需要占用CPU而只和I/O等资源打交道时,让需要占用CPU资源的其它线程有机会获得CPU资源。从根本上说,这就是多线程编程的最终目的。
单线程:当程序启动运行时,就自动产生一个线程,主方法main就在这个主线程上运行;java中如果我们自己没有产生线程,那么系统就会给我们产生一个线程(主线程,main方法就在主线程上运行),我们的程序都是由线程来执行的。
2、进程:执行中的程序(程序是静态的概念,进程是动态的概念),看windows任务管理器里的进程。
3、进程与线程的区别:
多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响.
线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。
4、多线程:一个进程可以包含一个或多个线程; 一个程序实现多个代码同时交替运行就需要产生多个线程; CPU随机的抽出时间,让我们的程序一会做这件事情,一会做另外一件事情
同其他大多数编程语言不同,Java内置支持多线程编程(multithreaded programming)。多线程程序包含两条或两条以上并发运行的部分,把程序中每个这样的部分都叫作一个线程(thread)。每个线程都有独立的执行路径,因此多线程是多任务处理的一种特殊形式。
多任务处理被所有的现代操作系统所支持。然而,多任务处理有两种截然不同的类型:基于进程的和基于线程的。
1)基于进程的多任务处理是更熟悉的形式。进程(process)本质上是一个执行的程序。因此基于进程的多任务处理的特点是允许你的计算机同时运行两个或更多的程序。举例来说,基于进程的多任务处理使你在运用文本编辑器的时候可以同时运行Java编译器。在基于进程的多任务处理中,程序是调度程序所分派的最小代码单位。
2)而在基于线程(thread-based)的多任务处理环境中,线程是最小的执行单位。这意味着一个程序可以同时执行两个或者多个任务的功能。例如,一个文本编辑器可以在打印的同时格式化文本。
多线程程序比多进程程序需要更少的管理费用。进程是重量级的任务,需要分配给它们独立的地址空间。进程间通信是昂贵和受限的。进程间的转换也是很需要花费的。另一方面,线程是轻量级的选手。它们共享相同的地址空间并且共同分享同一个进程。线程间通信是便宜的,线程间的转换也是低成本的。
多线程可帮助你编写出CPU最大利用率的高效程序,使得空闲时间保持最低。这对Java运行的交互式的网络互连环境是至关重要的,因为空闲时间是公共的。例如,网络的数据传输速率远低于计算机处理能力,而本地文件系统资源的读写速度也远低于CPU的处理能力。当然,用户输入也比计算机慢很多。在传统的单线程环境中,程序必须等待每一个这样的任务完成以后才能执行下一步—尽管CPU有很多空闲时间。多线程使你能够获得并充分利用这些空闲时间。
5、Java线程的实现
在Java中通过run方法为线程指明要完成的任务,有两种技术来为线程提供run方法。
1)继承Thread类并重写run方法。
2)通过定义实现Runnable接口的类进而实现run方法。
第一种方式:继承Thread类:
public class ThreadTest
{
public static void main(String[] args)
{
Thread1 t1 = new Thread1();
t1.start();
}
}
class Thread1 extends Thread
{
@Override
public void run()
{
for(int i = 0;i<100;i++)
{
System.out.println("hello:"+ i );
}
}
}
将我们希望线程执行的代码放到run方法中,然后通过start方法来启动线程,start方法首先为线程的执行准备好系统资源,然后再去调用run方法。当某个类继承了Thread类之后,该类就叫做一个线程类。
两个线程:
public class ThreadTest
{
public static void main(String[] args)
{
Thread1 t1 = new Thread1();
Thread2 t2 = new Thread2();
t1.start(); //这里绝对不能写成t1.run();
t2.start(); //这里绝对不能写成t2.run();
}
}
class Thread1 extends Thread
{
@Override
public void run()
{
for(int i = 0;i<100;i++)
{
System.out.println("hello:"+ i );
}
}
}
class Thread2 extends Thread
{
public void run()
{
for(int i = 0;i<100;i++)
{
System.out.println("world welcome:"+ i );
}
}
}
一个进程至少包含一个线程;对于单核CPU来说,某一时刻只能有一个线程在执行(微观串行),从宏观角度来看,多个线程在同时执行(宏观并行);对于双核或双核以上的CPU来说,可以真正做到微观并行。
第二种方式:实现Runable接口:
Runable只有一个方法run(),我们需要重写此方法。(可以使用内部匿名类来实现)
public class ThreadRunable
{
public static void main(String[] args)
{
Thread t1 = new Thread(new MyThread());
Thread t2 = new Thread(new MyThread2());
// Thread t1 = new Thread(new Runnable() //使用内部匿名类
// {
// public void run()
// {
// for(int i = 0; i<100;i++)
// {
// System.out.println(i);
// }
// }
// });
t1.start();
t2.start();
}
}
class MyThread implements Runnable
{
@Override
public void run()
{
for(int i = 0; i<100;i++)
{
System.out.println(i);
}
}
}
class MyThread2 implements Runnable
{
@Override
public void run()
{
for(int i = 0; i<100;i++)
{
System.out.println("hello" + i);
}
}
}
使用实现Runnable接口的方式来实现线程,应该是启动了Runnable中的run方法。
为什么传递一个实现Runnable接口的类就可以调用其run方法呢,需要分析Thread的源代码
6、Thread源代码分析:
1)通过类的定义:public class Thread implements Runnable 可知Thread类也实现了Runnable接口。所以Thread实现了Runnable接口中的run方法。
2)构造方法:
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
private static int threadInitNumber;
private static synchronized int nextThreadNum() { return threadInitNumber++; }
nextThreadNum()返回一个从0开始的整数,与Thread-字符串连接,形成默认的线程名字。如果生成一个Thread对象:Thread t1 = new Thread(new MyThread());则t1.getName返回Thread-0,再生成一个Thread t2 = new Thread(new MyThread());则t2.getName为Thread-1,以此类推。
如果这样定义Thread
class Thread1 extends Thread
{
public Thread1(String name)
{
super(name);}@Overridepublic void run(){for(int i = 0;i<100;i++){System.out.println("hello:"+ i );}}}构造Thread对象时:Thread1 t1 = new Thread1(“My Thread”);则t1.getName就为My Thread。 总结: 当生成一个线程对象时,如果没有为其设定名字,那么线程对象的名字将使用如下形式:Thread-number,该number将是自动增加的,并被所有的Thread对象所共享(因为它是static的成员变量)。 3)关于run方法
public void run() {
if (target != null) {
target.run();
}
}
通过这个方法的源代码,我们可以看出,此方法要么什么都不执行,要么执行target的run方法,target是一个实现了Runnable接口的对象,通过public Thread(Runnable target)方式传递进来。通过init()方法,有一句this.target = target;private void init(ThreadGroup g, Runnable target, String name,long stackSize)当使用第一种方式来生成线程对象时,我们需要重写run方法,因为Thread类的run方法此时什么事情也不做。当使用第二种方式来生成线程对象时,我们需要实现Runnable接口的run方法,然后使用new Thread(new MyThread())(假如MyThread已经实现了Runnable接口)来生成线程对象,这时的线程对象的run方法就会调用MyThread类的run方法,这样我们自己编写的run方法就执行了。
7、总结 1).两种方法均需执行线程的start方法为线程分配必须的系统资源、调度线程运行并执行线程的run方法。 2).在具体应用中,采用哪种方法来构造线程体要视情况而定。通常,当一个线程已继承了另一个类时,就应该用第二种方法来构造,即实现Runnable接口。 3).线程的消亡不能通过调用一个stop()命令。而是让run()方法自然结束。线程的停止(推荐的两种方法):public class MyThread implements Runnable{ private boolean flag=true; public void run() { while (flag) {…} }public void stopRunning(){ flag=false;}}public class ControlThread{ private Runnable r=new MyThread(); private Thread t=new Thread(r); public void startThread() { t.start(); } publi void stopThread() { r.stopRunning();}}
8、线程的生命周期
线程的生命周期:一个线程从创建到消亡的过程。线程的生命周期可分为四个状态: 1).创建状态 2).可运行状态 3).不可运行状态 4). 消亡状态线程状态转换图:
1)创建状态•当用new操作符创建一个新的线程对象时,该线程处于创建状态。•处于创建状态的线程只是一个空的线程对象,系统不为它分配资源
2.)可运行状态•执行线程的start()方法将为线程分配必须的系统资源,安排其运行,并调用线程体—run()方法,这样就使得该线程处于可运行( Runnable )状态。•这一状态并不是运行中状态(Running ),因为线程也许实际上并未真正运行。
3)不可运行状态当发生下列事件时,处于运行状态的线程会转入到不可运行状态。•调用了sleep()方法;•线程调用wait方法等待特定条件的满足•线程输入/输出阻塞
返回可运行状态:•处于睡眠状态的线程在指定的时间过去后•如果线程在等待某一条件,另一个对象必须通过notify()或notifyAll()方法通知等待线程条件的改变•如果线程是因为输入/输出阻塞,等待输入/输出完成
4)消亡状态当线程的run方法执行结束后,该线程自然消亡。
9、线程优先级
1) 线程的优先级及其设置设置优先级是为了在多线程环境中便于系统对线程的调度,优先级高的线程将优先执行。一个线程的优先级设置遵从以下原则: –线程创建时,子继承父的优先级 –线程创建后,可通过调用setPriority()方法改变优先级。 –线程的优先级是1-10之间的正整数。 1 - MIN_PRIORITY, 10 – MAX_PRIORITY 5- NORM_PRIORITY
2) 线程的调度策略线程调度器选择优先级最高的线程运行。但是,如果发生以下情况,就会终止线程的运行。 •线程体中调用了yield()方法,让出了对CPU的占用权 •线程体中调用了sleep()方法, 使线程进入睡眠状态 •线程由于I/O操作而受阻塞 •另一个更高优先级的线程出现。 •在支持时间片的系统中,该线程的时间片用完。
10、线程资源使用问题:
public class ThreadTest3
{
public static void main(String[] args)
{
Runnable r = new HelloThread();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
class HelloThread implements Runnable
{
// int i; //成员变量
@Override
public void run()
{
int i = 0; //局部变量
while(true)
{
System.out.println("num:"+ i++);
try
{
Thread.sleep((long)(Math.random()*1000));
} catch (InterruptedException e)
{
e.printStackTrace();
}
if(50 == i)
{
break;
}
}
}
}
使用成员变量,顺序打印,50个数,使用局部变量,无序打印,100个数
a)关于成员变量与局部变量:如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,他们对该成员变量是彼此影响的(也就是说一个线程对成员变量的改变会影响到另一个线程)。
b) 如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。
11、 停止线程的方式:不能使用Thread类的stop方法来终止线程的执行。一般要设定一个变量,在run方法中是一个循环,循环每次检查该变量,如果满足条件则继续执行,否则跳出循环,线程结束。
不能依靠线程的优先级来决定线程的执行顺序。