多线程

  • ​​1.线程和进程的定义​​
  • ​​1.2.线程进程的区别体现在几个方面:​​
  • ​​1.3.我们的理解:​​
  • ​​2.多线程的2种或者4种实现方法​​
  • ​​2.1.Java多线程实现的方式有四种​​
  • ​​补充:​​
  • ​​3.多线程的创建之Thread的步骤​​
  • ​​4.通过实现runnable接口实现多线程​​
  • ​​4.2.解释说明​​
  • ​​4.3.比较​​
  • ​​4.4.实现Runnable的原理​​
  • ​​4.5.实现Runnable的好处​​
  • ​​5.利用匿名内部类+runnable接口实现多线程​​
  • ​​优点:​​
  • ​​6.Thread和Runnable的区别​​
  • ​​6.1.通过继承Thread类模拟售票​​
  • ​​通过结果可以看成是卖了10张票但是一共就5张票,所以此时只能说明这种方式实现的时候是没有共享资源的 ( ==没有共享tickets== )​​
  • ​​6.2.通过实现Runnable接口实现售票​​

1.线程和进程的定义

  • 进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。
  • 线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位

1.2.线程进程的区别体现在几个方面:

  • 因为进程拥有独立的堆栈空间和数据段,所以每当启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这对于多进程来说十分“奢侈”,系统开销比较大,而线程不一样,线程拥有独立的堆栈空间,但是共享数据段,它们彼此之间使用相同的地址空间,共享大部分数据,比进程更节俭,开销比较小,切换速度也比进程快,效率高,但是正由于进程之间独立的特点,使得进程安全性比较高,也因为进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。一个线程死掉就等于整个进程死掉。
  • 体现在通信机制上面,正因为进程之间互不干扰,相互独立,进程的通信机制相对很复杂,譬如管道,信号,消息队列,共享内存,套接字等通信机制,而线程由于共享数据段所以通信机制很方便。
  • 属于同一个进程的所有线程共享该进程的所有资源,包括文件描述符。而不同的进程相互独立。
  • 线程又称为轻量级进程,进程有进程控制块,线程有线程控制块;
  • 线程必定也只能属于一个进程,而进程可以拥有多个线程而且至少拥有一个线程;
  • 简而言之:开启酷狗音乐只是一个进程,同时可以听歌和下载歌曲(2个线程)

1.3.我们的理解:

  • 进程是指在系统中正在运行的一个应用程序;程序一旦运行就是进程,或者更专业化来说:进程是指程序执行时的一个实例。
  • 线程是进程的一个实体。
  • 进程——资源分配的最小单位,线程——程序执行的最小单位

2.多线程的2种或者4种实现方法

ps:网上说有4种实现方法,但是也有一部分人是持但对意见的,那么这4种方法究竟如何呢?

2.1.Java多线程实现的方式有四种

  • 继承Thread类,重写run方法
  • 实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target
  • 通过Callable和Future/FutureTask创建多线程
  • 通过线程池创建多线程

补充:

  • 前面两种可以归结为一类:无返回值,原因很简单,通过重写run方法,run方式的返回值是void,所以没有办法返回结果。
  • 后面两种可以归结成一类:有返回值,通过Callable接口,就要实现call方法,这个方法的返回值是Object,所以返回的结果可以放在Object对象中

3.多线程的创建之Thread的步骤

  1. 定义一个类继承Thread
  2. 重写run方法
  3. 创建子类对象,就是创建线程对象
  4. 调用start方法,开启多线程并执行,同时还会告诉jvm去调用run方法
package com.Li.xc01;
/**
* @Description:通过继承thread实现多线程
* @auther:Li Ya Hui
* @Time:2021年4月20日下午7:39:10
*/
public class JiSuanQi extends Thread {

@Override
public void run() {
// System.out.println("计算器:run()");
// System.out.println("当前线程的名字:"+Thread.currentThread().getName());
for (int i = 0; i < 10; i++) {
System.out.println("主函数的名字\t"+getName());
//Thread.currentThread().getName()可以缩写,但是为了语义化,一般都要写
}
}
//此构造期为更改线程名字
public JiSuanQi(String name)
{
super(name);
}
}

/**
* @Description: 测试类 通过继承thread类实现多线程 多线程 之 Thread的步骤
* @auther:Li Ya Hui
* @Time:2021年4月20日下午7:17:50
*/
public class Test {
public static void main(String[] args) {

for (int i = 0; i < 10; i++) {
System.out.println("主函数的名字\t"+Thread.currentThread().getName());
}

System.out.println("主函数001");
System.out.println("主函数002");
System.out.println("主函数003");

//主函数的线程名字:main
System.out.println("当前线程的名字"+Thread.currentThread().getName());

//开启多线程,创建子类对象
JiSuanQi jiSuanQi = new JiSuanQi("计算器线程");

//当调用start方法的时候,jvm会自动执行run方法,
//其他非主函数的线程的名字:Thread-0...... 但是可以通过有参数构造器进行修改
jiSuanQi.start();
}
}

通过测试发现thread里的run方法不会因为main方法的结束而受到影响

4.通过实现runnable接口实现多线程

  1. 定义类实现Runnable接口
  2. 覆盖/实现接口中的run方法
  3. 创建Thread类的对象
  4. 将Runnable接口的实现类对象作为参数传递给Thread类的构造函数
  5. 调用Thread类的start方法开启线程
//Runable 接口  的实现类
/**
* @Description: 通过Runable实现类 给Thread传参数实现 多线程
* @auther:Li Ya Hui
* @Time:2021年4月20日下午8:34:21
*/
public class Prims implements Runnable{
@Override
public void run() {
while (true)
{
try {
//线程睡眠 1000毫秒
Thread.sleep(1000);
System.out.println("当前线程的内容:"+Thread.currentThread().getName());
} catch (InterruptedException e) {//线程中断异常
//异常处理
System.out.println("当前"+Thread.currentThread().getName()+"线程终端异常");
}
}
}
}
//测试类
package com.Li.xc02;
/**
* @Description: 测试 runnable 给Thread传参数实现 多线程
* @auther:Li Ya Hui
* @Time:2021年4月21日下午3:05:40
*/
public class Test {

public static void main(String[] args) throws InterruptedException {
System.out.println("主线程执行的内容001");
//实例化runnable,
Prims a = new Prims();
//参数形式传递给Thread
Thread TH = new Thread(a);
//主线程睡眠
Thread.sleep(2000);
System.out.println("主线程执行的内容002");
//子线程开启
TH.start();
System.out.println("主线程执行的内容003");
}
}

4.2.解释说明

Thread类中包括构造函数Thread(Runnable target)和Thread(Runnable target,String name),可以床底Runnable接口,说明构造函数支持传入一个Runnable接口的对象。
构造函数Thread(Runnable target)不光可以传入Runnable接口的对象,还可以传入一个Thread类的对象。这样的好处是可以完全将Thread对象中的run()方法交由其他线程进行调用。

4.3.比较

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
main函数,实例化线程对象也有所不同,
extends Thread :t.start();
implements Runnable : new Thread(t).start();
使用Runnable,增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

4.4.实现Runnable的原理

  • 为什么需要定一个类去实现Runnable接口呢?继承Thread类和实现Runnable接口有啥区别呢?
  • 实现Runnable接口,避免了继承Thread类的单继承局限性。覆盖/实现Runnable接口中的run方法,将线程任务代码定义到run方法中。
  • 创建Thread类的对象,只有创建Thread类的对象才可以创建线程。线程任务已被封装到Runnable接口的run方法中,而这个run方法所属于Runnable接口的实现对象,所以将这个实现类对象作为参数传递给Thread的构造函数,这样,线程对象创建时就可以明确的得到要运行的线程的任务。
  • 简而言之:实现Runable接口进而实现接口中的run方法,然后创建实例化Thread类,将线程任务所在的对象/类以参数的形式传递给Thread的构造函数最终实现多线程

4.5.实现Runnable的好处

  • 第二种方式,实现Runnable接口避免了单继承的局限性,所以较为常用。实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。继承Thread类,线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,又有线程任务。实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。
  • 简而言之:避免了单继承的局限性,安全,耦合度低

5.利用匿名内部类+runnable接口实现多线程

package com.Li.xc02;


/**
* @Description: 利用匿名内部类+runnable接口实现多线程
* @auther:Li Ya Hui
* @Time:2021年4月21日下午6:49:12
*/
public class Test02 {

public static void main(String[] args) {

System.out.println("主函数线程打印:001");

Thread thread = new Thread(new Runnable()
{
@Override
public void run()
{
System.out.println("当前线程的名字为:"+Thread.currentThread().getName());
}
});
thread.start();

System.out.println("主函数线程打印:002");

}
}

优点:

  1. 线程任务类只是实现了Runnable接口,可以继续继承其他类,而且可以继续实现其他的功能
  2. 同一个线程任务对象可以被包装成多个线程对象
  3. 适合多个相同的程序代码的线程去共享同一个资源
  4. 实现解棍操作,代码可以被多个线程共享,线程任务代码和线程独立

6.Thread和Runnable的区别

6.1.通过继承Thread类模拟售票

package com.Li.xc03;
/**
*
* @Description:买票类 测试通过继承Thread类模拟售票
* @auther:Li Ya Hui
* @Time:2021年4月21日下午9:04:36
*/
public class Mythread extends Thread{
public int tickets = 5;

public void run() {
for (int i = 1; i < 10; i++) {
System.out.println("当前线程为:"+getName()+":"+(tickets--));
}
}
//线程添加名字
public Mythread(String name) {
super(name);
// TODO Auto-generated constructor stub
}
}

package com.Li.xc03;
/**
* @Description: 测试类 测试通过继承Thread类模拟售票
* @auther:Li Ya Hui
* @Time:2021年4月21日下午9:13:41
*/
public class Test {
public static void main(String[] args) {
Mythread myA = new Mythread("A窗口");
Mythread myB = new Mythread("B窗口");
myA.start();
myB.start();
//通过结果可以看成是卖了10张票但是一共就5张票,所以此时只能说明这种方式实现的时候是没有共享资源的
}
}
通过结果可以看成是卖了10张票但是一共就5张票,所以此时只能说明这种方式实现的时候是没有共享资源的 ( 没有共享tickets )

6.2.通过实现Runnable接口实现售票

可以实现共享资源的效果,但是会同时进行买票的问题,延伸出了互斥锁

synchronized (this)//同步锁    //  mutex互斥 在此处是锁的效果,即互斥锁
{
if (tickets > 0)
{
System.out.println("当前线程的名字:" + Thread.currentThread().getName() + ":" + (tickets--));
}
}

互斥锁使得,多个线程不可同时访问一个资源

案例展示

package com.Li.xc04;
/**
* @Description: 通过实现 Runnable接口 实现多线程-模拟售票
* @auther:Li Ya Hui
* @Time:2021年4月21日下午9:30:16
*/
public class Mythread implements Runnable {

//总票数
public int tickets = 500;

@Override
public void run() {
for (int i = 0 ;i<10000 ; i++) {
//this 指代当前类的意思 在此处表示为给当前的类加上互斥锁的效果
synchronized (this)//同步锁 // mutex互斥 在此处是锁的效果,即互斥锁
{
if (tickets > 0) {
System.out.println("当前线程的名字:" + Thread.currentThread().getName() + ":" + (tickets--));
}
}
}
}
}

package com.Li.xc04;
/**
* @Description: 测试类 测试Runnable的实现类运用多个线程来共享资源去买票,不会有数据读脏的可能
* @auther:Li Ya Hui
* @Time:2021年4月21日下午10:08:55
*/
public class Test {

public static void main(String[] args) {
//实例化我的售票类
Mythread myRunThread1 = new Mythread();

//实例两个线程 用来分工
Thread thread1 = new Thread(myRunThread1, "1号窗口");
Thread thread2 = new Thread(myRunThread1, "线程二");

thread1.start();
thread2.start();

//通过结果我们发现,会有数据读脏的可能性
//所以要使用互斥锁来避免同时读取数据问题
}
}

Runnable总结:

  • 实现Runnable接口比继承Thread类所具有的优势:
  • 适合多个相同的程序代码的线程去处理同一个资源
  • 可以避免java中的单继承的限制
  • 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
  • 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类