Java实现多线程基础

182 阅读6分钟

一、多线程

1.1 进程

线程是依赖于进程。 进程:是正在运行的程序

  • 每个系统进行资源分配和调用的独立单位

  • 每一个进程都有它自己的内存空间和系统资源

1.2 线程

线程:是进程中的单个顺序控制流,是一条执行路径

  • 单线程:只有一条执行路径
  • 多线程:有多条执行路径

1.3 多线程的实现方式

软件包:Java.lang  public class Thread -> 具体类  extends Object  implements Runnable Java虚拟机允许应用程序同时执行多个线程。


方式一:定义一个类继承Thread类,并重写Thread类的run方法。

public class MyThread extends Thread{
	@Override
	public void run() {
		super.run();
		for(int i = 0; i <100; i++) {
			System.out.println(i);
		}
	}
}
public static void main(String[] args) {
		MyThread my1 = new MyThread();
		MyThread my2 = new MyThread();
		//run 方法并没有启动线程
//		my1.run();
//		my2.run();
		//viod start() 导致线程开始执行;Java虚拟机调用此线程的run方法
		my1.start();
		my2.start();
	}

问题一: 为什么要重写run方法?   因为run()是用来封装线程执行的代码 问题二:run()方法和start()方法的区别?   run():封装线程执行的代码,直接调用,相当于普通方法的调用   start():启动线程;然后由VM调用此线程的run()方法

方式二:实现Runnable接口 1. 定义一个类实现Runnable接口 2. 在MyThread2类中重写run()方法 3. 创建MyThread2类的对象 4. 创建Thread类的对象,把MyThread2对象作为构造方法的参数 5. 启动线程

1.定义一个类实现Runnable接口
public class MyThread2 implements Runnable{
	@Override
	public void run() {
		for(int i = 0; i < 50; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		//这个类只实现类runable接口 没有继承Thread类
		}		
	}
}
public class MyThreadDemo2 {
	public static void main(String[] args) {
		//2:在MyRunnable类中重写run()方法
		MyThread2 my = new MyThread2();
		//3:创建MyRunnable类的对象
		//4:创建Thread类的对象,把MyRunnable对象作为构造方法的参数
//		Thread t1 = new Thread(my);
//		Thread t2 = new Thread(my);
		
		//Thread(Runable target, String name)
		Thread t1 = new Thread(my,"A");
		Thread t2 = new Thread(my,"B");	
		//启动线程
		t1.start();
		t2.start();
	}
}

多线程的实现方式:

  • 继承Tread类
  • 实现Runable接口

对比继承Thread类,实现Runable接口的好处:

  • 避免Java单继承的局限性
  • 适合多个相同的程序代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面对对象的设计思想

1.4设置和获取线程名称

Thread类中设置和获取线程名称的方法

  • viod setName(String name): 将此线程的名称更改为等于参数name
  • String getName(): 返回线程的名称
  • 或者通过构造方法也可以设置线程名称👇
MyThread类
 public class MyThread extends Thread{
	public MyThread(String name) {
		super(name);
	}

Main	//Thread(String name)
		MyThread my1 = new MyThread("线程1");
		MyThread my2 = new MyThread("线程2");	

如何获取main()方法所在线程名称? · public static Thread currentThread(): 返回对当前正在执行的线程对象的引用。

System.out.println("Thread.currentThread.getName()");

1.5线程优先级

线程有俩种调度模型

  • 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
  • 抢占式调度模型:优先级高的线程先用CPU,如果优先级相同,则随机,优先级高的获取时间片相对多一些 Java使用的是抢占式调度模型

在只有一个CPU的时候,CPU在某一时刻只能执行一条指令,线程只有得到时间片才能执行。所以多线程的程序执行是有随机性的。

Thread类中设有获取线程优先级的方法:

方法说明
public final int getPriority():放回优先级
public final void setPriority(int newPriority)更改优先级

线程默认优先级是5;范围是1-10 线程优先级高仅仅表示线程获取CPU的时间片几率高,在次数多/多次运行下,才有对比。

1.6线程控制

方法名说明
static void sleep( long millis )是当前线程停止执行指定毫秒数
viod join( )等待此线程死亡
void setDaemon( boolean on)标记为守护线程,当运行线程都是守护线程,VM将退出

1.7线程生命周期

在这里插入图片描述


二、线程同步

2.1数据安全问题的解决

判断多线程程序是否会有数据安全问题的标准:

  • 是否多线程环境
  • 是否共享数据
  • 是否有多条语句操作共享数据

如何解决:创建安全环境

如何实现:给多条语句操作共享数据的代码加锁,让每一时刻只能有一个线程执行

Java提供了同步代码块: 格式:

synchronizd(任意对象){
	多条语句操作共享数据的代码
}

相当于给代码加锁,任意对象可以看成是一把锁

同步的好处和弊端:

  • 好处:解决了多线程的数据安全问题
  • 坏处:当线程很多时,每个线程都会判断同步上的锁,很浪费资源,降低运行效率。

2.2同步方法

同步方法:把synchronized关键字加到方法上

  • 格式:修饰符 synchronized 返回值类型 方法名(参数){ }
  • 同步方法的锁的对象是:this

同步静态方法:把synchronized关键字加到静态方法上

  • 格式:修饰符 static synchronized 返回值类型 方法名(参数) { }
  • 步静态方法的锁的对象是:类名.class

2.3线程安全的类

StringBuffer:

  • 线程安全,可变的字符序列
  • JDK5开始,被StringBuilder替代。通常用StringBuilder类,因为它支持所有相同操作,但它更快,因为不执行同步

Vector:

  • 底层数据是数组 - ArrayList
  • 从Java2平台v1.2开始,该类改进了List接口,成为Collections Framework的成员。与新的集合实现不同,Vector被同步。如不需要线程安全的实现,建议使用ArrayList替代。

Hashtable:

  • 该类实现了一个哈希表,将键映射到值。任何非null对象都可以用作键或者值
  • 从Java2平台v1.2开始,该类改进,实现了Map接口,成为Collection Framework的成员。与新的集合实现不用,Hashtable被同步。如不需要线程安全的实现,建议使用HashMap替代。

2.5 Lock锁

JDK5后提供了一个新的锁对象Lock。 Lock实现提供比使用synchronized方法和语句的可以获得更加广泛的锁定操作。

  • void lock(): 获得锁
  • void unlock(): 释放锁

Lock是接口不能直接实例化,采用它的实现类ReentrantLock来实例化 ReentrantLock的构造方法:

  • ReentrantLock():创建一个ReentrantLock实例

三、生产者消费者

生产者消费模式是一个非常经典的多线程协作模式。 实际上主要是包含了俩类线程:

  • 一类是生产者线程用于生产数据
  • 一类是消费者线程用于消费数据

为了了解生产者和消费者的关系,通常会采用共享的数据区域,像是个仓库。

  • 生产者生产数据后直接放置在共享数据区域,并不需要关心消费者的行为
  • 消费者直接从共享数据区域中获取数据,并不需要关心生产者的行为

3.1生产者消费者模式概述

为了体现生产和消费过程中的等待和唤醒, Java就提供了几个方法供我们使用,这几个方法在Object类中 Object类的等待和唤醒方法:

方法名说明
void wait( )导致当前线程等待,直到另一个线程调用该对象的 notify0方法或notifyl0方法
void notify( )唤醒正在等待对象监视器的单个线程
void notifyAll( )唤醒正在等待对象监视器的所有线程