一.多线程的基本概念
多线程是Java语言的重要特性,大量应用于网络编程、服务器端程序的开发,最常见的UI界面底层原理、操作系统底层原理都大量使用了多线程。
我们可以流畅的点击软件或者游戏中的各种按钮,其实,底层就是多线程的应用。UI界面的主线程绘制界面,如果有一个耗时的操作发生则启动新的线程,完全不影响主线程的工作。当这个线程工作完毕后,再更新到主界面上。
多线程本质是单线程由cpu分配依次执行,由于间隔时间太短形成同时执行的效果。
程序:程序是一个静态的概念,一般对应于操作系统中的一个可执行文件,当我们命令开始运行程序,则加载程序到内存中。程序的运行产生了进程。
进程:执行中的程序叫做进程,进程是一个动态的概念。现代的操作系统都可以同时启动多个进程。
进程具有如下特点:
- 1. 进程是程序的一次动态执行过程, 占用特定的地址空间。
- 2. 每个进程由3部分组成:cpu、data、code。每个进程都是独立的,保有自己的cpu时间,代码和数据,即便用同一份程序产生好几个进程,它们之间还是拥有自己的这3样东西,这样的缺点是:浪费内存,cpu的负担较重。
- 3. 多任务(Multitasking)操作系统将CPU时间动态地划分给每个进程,操作系统同时执行多个进程,每个进程独立运行。以进程的观点来看,它会以为自己独占CPU的使用权。
- 4. 进程的查看
- Windows系统: Ctrl+Alt+Del,启动任务管理器即可查看所有进程。
- Unix系统: ps or top。
线程:一个进程可以产生多个线程同多个进程可以共享操作系统的某些资源一样,同一进程的多个线程也可以共享此进程的某些资源(比如:代码、数据),所以线程又被称为轻量级进程(lightweight process)。
线程具有如下特点:
- 1. 一个进程内部的一个执行单元,它是程序中的一个单一的顺序控制流程。
- 2. 一个进程可拥有多个并行的(concurrent)线程。
- 3. 一个进程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,而且它们从同一堆中分配对象并进行通信、数据交换和同步操作。
- 4. 由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快。
- 5. 线程的启动、中断、消亡,消耗的资源非常少。
![29bf6ffec2a76a7d030addd957684f29.png](https://i-blog.csdnimg.cn/blog_migrate/3c441e0ad4bf085881d92eebe75b293d.png)
线程和进程的区别:
- 1. 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销。
- 2. 线程可以看成是轻量级的进程,属于同一进程的线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。
- 3. 线程和进程最根本的区别在于:进程是资源分配的单位,线程是调度和执行的单位。
- 4. 多进程: 在操作系统中能同时运行多个任务(程序)。
- 5. 多线程: 在同一应用程序中有多个顺序流同时执行。
- 6. 线程是进程的一部分,所以线程有的时候被称为轻量级进程。
- 7. 一个没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,进程的执行过程不是一条线(线程)的,而是多条线(线程)共同完成的。
- 8. 系统在运行的时候会为每个进程分配不同的内存区域,但是不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源。那就是说,除了CPU之外(线程在运行的时候要占用CPU资源),计算机内部的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。
进程和程序的区别:
- 程序是一组指令的集合,它是静态的实体,没有执行的含义。而进程是一个动态的实体,有自己的生命周期。一般说来,一个进程肯定与一个程序相对应,并且只有一个,但是一个程序可以有多个进程,或者一个进程都没有。除此之外,进程还有并发性和交往性。简单地说,进程是程序的一部分,程序运行的时候会产生进程。
二.通过继承Thread实现多线程
继承Thread类实现多线程的步骤:
- 1. 在Java中负责实现线程功能的类是java.lang.Thread 类。
- 2. 可以通过创建 Thread的实例来创建新的线程。
- 3. 每个线程都是通过某个特定的Thread对象所对应的方法run( )来完成其操作的,方法run( )称为线程体。
- 4. 通过调用Thread类的start()方法来启动一个线程。
- 由于在Java中 一个类只能继承唯一的一个类,所以这种继承Thread类实现多线程的方法具有很大的缺陷。实现代码:
- 继承类:
package cn.sxt.ma.thread;
/**
* 多线程
* 通过继承Thread实现多线程
*/
public class MyThread extends Thread{
public void run () {
for (int i = 0; i < 10; i++) {
System.out.println("MyThread_______"+i);
}
}
}
测试类:
package cn.sxt.ma.thread;
/**
* 通过继承Thread实现多线程
* 会由于CPU的分配出现抢占资源的情况
*/
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
//调用start方法启动线程。start方法会调用线程中的run方法
mt.start();
for (int i = 0; i < 10; i++) {
System.out.println("main--------"+i);
}
}
}
运行结果:
![2d289a09fd18fe1dce10f160733cba59.png](https://i-blog.csdnimg.cn/blog_migrate/6ff312c51141ce679d1d7051cc30407b.png)
两个循环交替打印,显现了线程共同执行。
三.通过Runnable接口实现多线程
通过实现Runnable接口实现多线程解决了通过继承Thread类这种方式的缺陷
即实现Runnable接口的同时还可以继承某个类
实现Runnable接口实现多线程的步骤:
- (1)编写类实现 Runnable 接口
- (2)实现 run()方法
- (3)通过 Thread 类的 start()方法启动线程
实现Runnable接口的类:
package cn.sxt.ma.runnable;
/**
* 实现类实现Runnable接口
*
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("MyRunnable-------"+i);
}
}
}
测试类:
package cn.sxt.ma.runnable;
/**
* 实现Rnnnable接口完成多线程、这种方式线程之间抢占资源更加激烈
*
*/
public class Test {
public static void main(String[] args) {
MyRunnable rb = new MyRunnable();
//Runnable中没有start方法,所以要借助Thread的对象来使用start方法
Thread t = new Thread(rb);
t.start();
//主线程中的循环
for (int i = 0; i < 10; i++) {
System.out.println("main--------"+i);
}
}
}
由于Runnable接口中没有start方法,所以要借助Thread的对象来使用start方法,这就涉及到了静态代理模式。
静态代理模式:
代码示例:完成结婚的任务
接口:结婚
package cn.sxt.ma.marry;
public interface Marry {
public void marry();
}
代理角色:婚庆公司,实现结婚接口,并添加代理工作
package cn.sxt.ma.marry;
/**
* 代理角色
*
*/
public class MarryCompany implements Marry{
//婚庆公司给准备结婚的人结婚
private Marry m;//接口做形式参数,通过构造方法赋值,也可以直接传真实角色You,使用接口方便更多的类参与代理
public MarryCompany(Marry m) {
this.m = m;
}
public void before() {
System.out.println("婚礼前的准备工作----");
}
private void after() {
System.out.println("婚礼后的善后工作-----");
}
@Override
public void marry() {
this.before();
//调用真实角色的结婚方法
m.marry();
this.after();
}
}
真实角色类:你
package cn.sxt.ma.marry;
/**
* 真实角色的类
*
*/
public class You implements Marry {
@Override
public void marry() {
System.out.println("你与xxx结婚了");
}
}
测试类:
package cn.sxt.ma.marry;
public class Test {
public static void main(String[] args) {
//创建真实角色对象,可以通过接口new 实现类创建
Marry y = new You();
//创建代理角色对象
Marry myc = new MarryCompany(y);
myc.marry();
}
}
运行结果:
![1a0c1661b23852b96c72d64daf41c145.png](https://i-blog.csdnimg.cn/blog_migrate/099800603e4d7d22848a3205a2e2284e.png)
宝强工作:
工作接口:
package cn.sxt.ma.green;
public interface IWork {
public void work();
}
真实角色:宝强
package cn.sxt.ma.green;
/**
* 真实角色
*
*/
public class BaoQiang implements IWork {
@Override
public void work() {
System.out.println("拍电影-----------");
}
}
代理:宋喆
package cn.sxt.ma.green;
/**
* 代理角色
*
*/
public class SongZe implements IWork {
//接口定义形参,接口所有的实现类都可以导入代理
private IWork iw;
public SongZe(IWork iw) {
this.iw = iw;
}
@Override
public void work() {
System.out.println("接通告————————");
iw.work();
System.out.println("照顾老婆孩子-------");
}
public static void main(String[] args) {
//将宝强导入代理
SongZe songZe = new SongZe(new BaoQiang());
songZe.work();
}
}
运行结果:
![f2a6098ca1677d9c9c1c1bb4ba27ee6f.png](https://i-blog.csdnimg.cn/blog_migrate/7961c40fa748da2ec765ab07af9f5661.png)
四.线程的状态
- 新生状态
- 用 new 关键字建立一个线程后,该线程对象就处于新生
- 状态。处于新生状态的线程有自己的内存空间,通过调用
- start()方法进入就绪状态。
- 就绪状态
- 处于就绪状态线程具备了运行条件,但还没分配到 CPU,
- 处于线程就绪队列,等待系统为其分配 CPU。当系统选定一
- 个等待执行的线程后,它就会从就绪状态进入执行状态,该
- 动作称为“CPU 调度”。
- 运行状态
- 在运行状态的线程执行自己的 run 方法中代码,直到等待
- 某资源而阻塞或完成任何而死亡。如果在给定的时间片内没
- 有执行结束,就会被系统给换下来回到等待执行状态。
- 阻塞状态
- 处于运行状态的线程在某些情况下,如执行了 sleep(睡眠)
- 方法,或等待 I/O 设备等资源,将让出 CPU 并暂时停止自己
- 运行,进入阻塞状态。在阻塞状态的线程不能进入就绪队列。
- 只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的
- I/O 设备空闲下来,线程便转入就绪状态,重新到就绪队列
- 中排队等待,被系统选中后从原来停止的位置开始继续执行。死亡状态
- 死亡状态是线程生命周期中的最后一个阶段。线程死亡的
- 原因有三个,一个是正常运行的线程完成了它的全部工作;
- 另一个是线程被强制性地终止,如通过 stop 方法来终止一个
- 线程【不推荐使用】;三是线程抛出未捕获的异常。
示意图:
![e274c64d7bdc75ea5a04e081bfd6e10e.png](https://i-blog.csdnimg.cn/blog_migrate/26c51bcec89b6bdedc1edefd9679a4c9.jpeg)
五.终止线程
JDK提供的stop()/destory()过于粗暴,可能导致程序系统出现错误,所以一般使用一个boolean类型的变量来控制线程继续,当为变量为false时终止线程的运行。
package cn.sxt.ma.method;
/**
* 终止线程:
* 一般不使用JDK给的stop()/distroy()方法(已过时)
* 一般提供一个boolean类型的终止变量,当这个变量为false时。终止线程的运行
*
*/
public class TestThreadCiycle implements Runnable{
//标识线程是否终止
boolean live = true;
String name;
//构造方法
public TestThreadCiycle(String name) {
this.name = name;
}
@Override
public void run() {
int i = 0;
while(live) {
System.out.println(name+"---"+i++);
}
}
//终止线程
public void s() {
live=false;
}
public static void main(String[] args) {
TestThreadCiycle ttc = new TestThreadCiycle("子线程");
Thread thread = new Thread(ttc);
thread.start();//启动线程
for (int i = 0; i < 5000; i++) {
System.out.println("主线程"+i);
}
ttc.s();
}
}
![5b5849224f0cbb8a9dc5b2efd831e63f.png](https://i-blog.csdnimg.cn/blog_migrate/1ee7d48d0813f75f0a3b8d60def75e82.png)
子线程并没有运行结束,由于主线程运行完毕,条件变为false强行结束了
六.暂停线程sleep()/yield(),线程的加入join()
暂停线程执行常用的方法有sleep()和yield()方法,这两个方法的区别是:
- 1. sleep()方法:可以让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态。
- 2. yield()方法:可以让正在运行的线程直接进入就绪状态,让出CPU的使用权。
线程A在运行期间,可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。如下面示例中,“爸爸线程”要抽烟,于是联合了“儿子线程”去买烟,必须等待“儿子线程”买烟完毕,“爸爸线程”才能继续抽烟。
儿子买烟程序
爸爸让儿子去买烟,等待10分钟,如果回来就开心的抽烟,如果没回来则出门寻找
package cn.sxt.ma.method;
/**
* 线程暂停
* 应用:儿子买烟
*/
public class Cigarette {
public static void main(String[] args) {
System.out.println("儿子买烟的故事。");
Thread father = new Thread(new FatherThread());
father.start();
}
}
//父亲线程
class FatherThread implements Runnable {
@Override
public void run() {
System.out.println("爸爸想抽烟,发现烟抽完了。");
System.out.println("爸爸让儿子去买包红塔山。");
Thread son = new Thread(new SonThread());
//儿子线程启动
son.start();
System.out.println("爸爸等儿子买烟回来。");
//儿子线程加入
try {
son.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
//儿子出门买烟出现问题,父亲出门寻找
System.out.println("儿子久久未归,父亲出门焦急寻找。");
//结束JVM。如果是0则表示正常结束;如果是非0则表示非正常结束。
System.exit(1);
}
System.out.println("儿子归来。父亲高兴的接过烟,开心的抽了起来。");
}
}
//儿子买烟线程
class SonThread implements Runnable {
@Override
public void run() {
System.out.println("儿子出去买包烟。");
System.out.println("儿子出门买烟需要10分钟。");
try {
for (int i = 1; i <= 10 ; i++) {
System.out.println("第"+i+"分钟。");
//休眠1秒
Thread.sleep(1000);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
![2c26466681f867ec7e088bad37761bc1.png](https://i-blog.csdnimg.cn/blog_migrate/f93b2448c95f34a22bef65b2ac09c4a9.png)
七.获取线程基本信息的方法
![67681162ecf01a8f0944c92f0468dbb1.png](https://i-blog.csdnimg.cn/blog_migrate/466f8c02ae2b7e0cbbb4b5813b6d4855.png)
getName()
package cn.sxt.ma.method;
/**
* 获得和修改线程名
* final String getName():方法 获得线程名
* new Thread(Runnable run,String Name); 创建对象时自定义线程名
*/
public class TestGetName {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();
//创建对象并自定义线程名
Thread t2=new Thread(mr,"自定义线程名");
t2.start();
}
}
class MyRunnable1 implements Runnable{
@Override
public void run() {
Thread thread = Thread.currentThread();
//输出线程名
System.out.println(thread.getName());
}
}
![147f22cf24582fce21fdaac95fec582d.png](https://i-blog.csdnimg.cn/blog_migrate/f8a63bf86a6eea54b66870023c49c45f.png)
currentThread()获取当前线程对象:
package cn.sxt.ma.method;
/**
* 测试Thread方法
* currentThread():获取当前线程的对象,静态方法
*/
public class TestThreadMethod {
public static void main(String[] args) {
Thread t = Thread.currentThread();
//toString()方法得到内容为【线程名,线程的优先级,线程组名】
System.out.println(t.toString());
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my);
Thread t2 = new Thread(my);
Thread t3 = new Thread(my);
//启动线程,线程的启动先后顺序是由cpu决定的,并不是靠前就一定先启动
t1.start();
t2.start();
t3.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
//currentThread():获得线程对象
Thread thread = Thread.currentThread();
System.out.println(thread);//默认调用toString方法
}
}
![654c3b2f02678fa120ab8c4efa500b7a.png](https://i-blog.csdnimg.cn/blog_migrate/33f74153c0ffaa09fd42b047c1ea1b57.png)
总结:
package cn.sxt.ma.method;
/**
* 获取线程的基本信息
* getName():返回此线程的名称。
* isAlive():测试这个线程是否活着
* getPriority():返回此线程的优先级。最小值为1,最大值10,默认为5
* setPriority(int newPriority):更改此线程的优先级。
* currentThread():返回对当前正在执行的线程对象的引用(即获得当前线程的对象)
*
*/
public class TestThreadMethod2 {
public static void main(String[] args) throws InterruptedException {
//创建线程及其代理对象,并自定义线程名
Runnable r = new MyThreadtt();
Thread t = new Thread(r,"自定义线程");
//启动线程
t.start();
//打印线程名
System.out.println("name is:"+t.getName());
//打印线程优先级
System.out.println("线程优先级为:"+t.getPriority());
//判断线程是否还在运行
System.out.println(t.isAlive());
//线程暂停5秒钟
Thread.currentThread().sleep(5000);
System.out.println("over!");
}
}
class MyThreadtt implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
![40910050e9c61bd25b9f266df32921dd.png](https://i-blog.csdnimg.cn/blog_migrate/da01986ff852c0507b66a8047f38ae92.png)
八.线程的优先级
1. 处于就绪状态的线程,会进入“就绪队列”等待JVM来挑选。
2. 线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5。
3. 使用下列方法获得或设置线程对象的优先级。
int getPriority();
void setPriority(int newPriority);
注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。
package cn.sxt.ma.method;
/**
* 线程的优先级:
* 线程的优先级用数字表示,范围从0~10,数字越大优先级越高,默认为5.
* int getPriority();
* void setPriority(int newOriority);设置线程优先级
* 注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。
*
*/
public class TestPriority {
public static void main(String[] args) {
MyThread thread = new MyThread("自定义线程1");
//设置优先级为最高
thread.setPriority(10);
thread.start();
MyThread thread2 = new MyThread("自定义线程2-----");
thread2.setPriority(1);
thread2.start();
}
}
class MyThread extends Thread {
//构造方法
public MyThread(String name) {
//调用父类的名字
super(name);
}
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName()+":"+i);
}
}
}
![01b23e4e7164e0cd135d88744347a1e5.png](https://i-blog.csdnimg.cn/blog_migrate/c3eb45508667ad57bfee1ee986402942.png)
从运行结果可以看出优先级高的并不是一定优先执行,只是cpu在选择时权重较大