多线程
2018-01-20 本文已影响0人
鉴闻俗说
一、线程的概念
1、什么是线程
线程就是程序中单独顺序的控制流。线程本身不能运行,它只能用于程序中。
2、什么是多线程
多线程是指在单个程序中可以同时运行多个不同的线程执行不同的任务。
3、什么是进程
进程就是运行中的程序。一个进程可以运行着多个线程。
4、线程使用的资源和环境
线程是程序内的顺序控制流,只能使用分配给程序的资源和环境。
5、多线程编程的目的
最大限度地利用CPU资源。当某一个线程的处理不需要占用CPU而只和I/O等资源打交道时,让需要占用CPU资源的其它线程有机会获得CPU资源。
6、单线程
Java中如果我们没有产生自己的线程,那么系统就会给我们产生一个线程(主线程,main方法就在主线程上运行),我们的程序都是由线程来执行的。
7、多线程
- 一个进程可以包含一个或多个线程;
- 一个程序实现多个代码同时交替运行就需要多个线程;
- CPU随机抽出时间,让我们的程序一会做这个事情,一会做另一个事情。
8、多任务处理的两种方式
- 基于进程的多任务处理
进程本质上是一个执行的程序。因此基于进程的多任务处理的特点是允许你的计算机同时运行两个或更多的程序。 - 基于线程的多任务处理
线程是最小的执行单位。这意味着一个程序可以同时执行两个或者多个任务。
9、线程与进程的区别
(1)多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响。
(2)线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的栈,所以线程切换比进程切换的负担小。
(3)多线程程序比多进程程序需要更少的管理费用。进程需要独立的地址空间,进程间通信昂贵、受限,进程间的转换昂贵。线程共享地址空间、同一个进程,线程间通信便宜、转换成本低。
(4)多线程可以帮助你编写出CPU最大利用率的高效程序,使得空闲时间保持最低。
二、线程的实现
在Java中通过run方法为线程指明要完成的任务。有两种技术来为线程提供run方法。
1、继承Thread类并重载run方法
- Thread类:是专门用来创建线程和对线程进行操作的类。Thread中定义了许多方法对线程进行操作。
- Thread类在缺省情况下run方法什么都不做。可以通过继承Thread类并重写Thread类的run方法实现用户线程。
- 总体结构如下
class Thread1 extends Thread
{
@Override
public void run()
{
...
}
}
Thread1 t1 = new Thread1();
t1.start();
- demo
/**
* 通过继承Thread类并重写run方法实现线程
* 本例中创建了两个不同的线程
*
*/
public class ThreadTest
{
public static void main(String[] args)
{
Thread1 t1 = new Thread1("thread first");
Thread2 t2 = new Thread2("thread second");
System.out.println(t1.getName());
System.out.println(t2.getName());
//start方法是启动线程的唯一方法
t1.start();
t2.start();
}
}
class Thread1 extends Thread
{
public Thread1(String name)
{
super(name);
}
@Override
public void run()
{
for(int i = 0; i < 100; i++)
System.out.println("hello world:" + i);
}
}
class Thread2 extends Thread
{
public Thread2(String name)
{
super(name);
}
@Override
public void run()
{
for(int i = 0; i < 100; i++)
System.out.println("welcom:" + i);
}
}
2、实现Runnable接口的类实现run方法
- 通过建立一个实现了Runnable接口的类,并以它作为线程的目标对象来创建一个线程。
- Runnable接口:定义了一个抽象方法run()。定义如下:
public interface java.lang.Runnable
{
public abstract void run();
}
- 总体框架如下
class MyThread implements Runnable
{
@Override
public void run()
{
...
}
}
MyThread r = new MyThread();
Thread t = new Thread(r);
t.start();
- demo
/**
* 通过定义实现Runnable接口的类实现线程
* 本例创建了两个不同的线程
*
*/
public class ThreadTest2
{
public static void main(String[] args)
{
//构造一个实现了Runnable接口的匿名内部类
// Thread thread = new Thread(new Runnable()
// {
// @Override
// public void run()
// {
// for(int i = 0; i < 100; i++)
// System.out.println("hello world:" + i);
// }
// });
Thread t1 = new Thread(new MyThread());
Thread t2 = new Thread(new MyThread2());
System.out.println(t1.getName());
System.out.println(t2.getName());
t1.start();
t2.start();
}
}
class MyThread implements Runnable
{
@Override
public void run()
{
for(int i = 0; i < 100; i++)
System.out.println("hello world:" + i);
}
}
class MyThread2 implements Runnable
{
@Override
public void run()
{
for(int i = 0; i < 100; i++)
System.out.println("welcom:" + i);
}
}
3、总结
- 两种方法均需执行线程的start方法为线程分配必须的系统资源、调度线程运行并执行线程的run方法。
- 在具体应用中,采用哪种方法来构造线程体要视情况而定。通常,当一个线程已继承了另一个类时,就应该用第二种方法来构造,即实现Runnable接口。
- 停止线程的方式。不能使用Thread类的stop()方法来终止线程的执行。一般要设定一个变量,在run方法中是一个循环,循环每次检查该变量,如果满足条件则继续执行,否则跳出循环,线程结束。
public class MyThread implements Runnable
{
private boolean flag = true;
public void run()
{
while(flag)
{...}
}
public void stopRunnning()
{flag = false;}
}
public class ControlThread
{
private Runnnable r = new MyThread();
private Thread t = new Thread(r);
public void startThread()
{t.start();}
public void stopThread()
{r.stopRunning();}
}
- 不能依靠线程的优先级来决定线程的执行顺序。
- 关于成员变量与局部变量
(1)成员变量:如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,它们对该成员变量是彼此影响的(也就是说一个线程对成员变量的改变会影响到另一个线程),用下面例子中的成员变量i加以说明:
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 = 0;
@Override
public void run()
{
while(true)
{
System.out.println("number: " + i++);
try
{
Thread.sleep((long)(Math.random() * 1000));
}
catch (InterruptedException e)
{
e.printStackTrace();
}
if (i == 5)
{
break;
}
}
}
}
输出结果为:
image.png
(2)局部变量:如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,该局部变量的改变不会影响到其他线程。用下面例子的局部变量i加以说明:
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
{
@Override
public void run()
{
//局部变量
int i = 0;
while(true)
{
System.out.println("number: " + i++);
try
{
Thread.sleep((long)(Math.random() * 1000));
}
catch (InterruptedException e)
{
e.printStackTrace();
}
if (i == 5)
{
break;
}
}
}
}
输出结果为:
image.png
三、线程的生命周期
1、线程的状态转换图
image.png- 创建状态:用new操作符创建一个新的线程对象时,该线程处于创建状态。处于创建状态的线程只是一个空的线程对象,系统不为它分配资源。
- 可运行状态:执行线程的start()方法将为线程分配必须的系统资源,安排其运行,并调用线程体的run()方法,这样就使得该线程处于可运行(Runnable)状态。这一状态并不是运行中状态(Running),因为线程也许实际上并未真正运行。
- 运行状态:当处于可运行状态的线程抢占到CPU资源就会转入运行状态。可运行状态与运行状态能够双向切换。
-
不可运行状态:
当发生下列事件时,处于运行状态的线程会转入到不可运行状态。
(1)调用了sleep()方法;
(2)线程调用wait方法等待特定条件的满足;
(3)线程输入、输出阻塞。
返回可运行状态:
(1)处于睡眠状态的线程在指定的时间过去后;
(2)如果线程在等待某一条件,另一个对象必须通过notify()或notify()方法通知等待线程条件的改变;
(3)如果线程是因为输入/输出阻塞,等待输入/输出完成。 - 消亡状态:当线程的run()方法执行结束后,该线程自然消亡。
四、线程的优先级
1、线程的优先级及其设置
设置优先级是为了在多线程环境中便于系统对线程的调度,优先级高的线程将优先执行。
一个线程的优先级设置需要遵循以下原则:
- 线程创建时,子继承父的优先级;
- 线程创建后,可通过调用setPriority()方法改变优先级。
- 线程的优先级是1-10之间的正整数。
1--MIN_PRIORITY,
10--MAX_PRIORITY,
5--NORM_PRIORITY.
2、线程的调度策略
线程调度器选择优先级高的线程运行。但是,如果发生以下情况,就会终止线程的运行。
- 线程体中调用了yield()方法,让出了对CPU的占用权;
- 线程体中调用了sleep()方法,使线程进入睡眠状态;
- 线程由于I/O操作而受阻塞;
- 在支持时间片的系统中,该线程的时间片用完。