Java学习笔记(十一)多线程详细归纳

目录

一、什么是线程

1.1进程

1.1.1进程的定义

1.1.2 进程的组成

1.1.3 进程的体现

1.2.线程

1.2.1线程的基本概念

二、java中多线程的实现方式

2.1. 继承Thread类的方式

2.1.1 步骤

2.1.2 实现代码

2.2 实现Runnable接口方式

2.2.1 步骤

2.2.2 实现代码

2.3两种方式的比较

2.3.1 继承Thread方式的实现原理

2.3.2实现Runnable接口方式原理

2.3.3从java程序设计角度

2.4 Thread类常用的方法

2.4.1实例方法

2.4.2 静态方法

三、多线程的安全问题

3.1 同步代码块

3.1.1 同步代码块的格式

3.2 同步方法

3.3 同步锁

四、线程池

4.1 使用Executors工具类获取线程池对象


一、什么是线程

1.1进程

1.1.1进程的定义

了解线程就必须先知道进程,下面是进程的几种定义:

  1. 进程是程序的一次执行过程
  2. 进程是一个程序及其数据在处理机上顺序执行时所发生的活动
  3. 进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位

1.1.2 进程的组成

进程是一个独立的运行单位,也是操作系统进行资源分配和调度的基本单位。

进程是由进程控制块(PCB)、程序段数据段组成的。

1.1.3 进程的体现

在Window系统中可以直接打开任务管理器查看当前运行的进程

而我们运行的Java通常可以看成一个进程

1.2.线程

进程是有自己独立的资源的,如内存,堆栈。在对于单核的Cpu来说,所有的进程都是并发执行的,即在宏观上看Cpu运行着不同的进程,而在微观具体到某一时间片里,cpu只能运行一个进程。这样就会有着不断的进程切换的过程。在进程的切换过程中,由于每个进程的资源是不同的,需要保存进程的运行现场,这会消耗cpu的资源。

引入进程,就是为了更好地使程序并发执行,提高资源利用率和系统吞吐量,增加并发度。

引入线程,是为了减少程序在并发执行时所付出的时空开销,提高操作系统的并发性能。

1.2.1线程的基本概念

线程最简单的理解就是“轻量级进程”,它是一个基本的Cpu执行单位,也是程序执行流的最小单元,由线程ID、程序计数器、寄存器集合和堆栈组成。

注意:

  1. 线程是不拥有系统资源的,只拥有一点在运行中必不可少的资源。
  2. 进程才能拥有独立的系统的资源,而进程内的线程将会共享所在进程的资源,这也是为什么线程切换起来会比进程切换快的原因。

 

二、java中多线程的实现方式

2.1. 继承Thread类的方式

2.1.1 步骤

  1. 定义一个类,继承Thread类
  2. 在自定义类中重写run方法,用于定义新线程要运行的内容
  3. 创建自定义类型的对象
  4. 调用线程的启动方法:start 方法

2.1.2 实现代码

public class Mythread extends Thread{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i = 0; i < 100; i++) {
			System.out.println(getName() + ": " + i); //getName()为Thread实例化方法获取线程名字
		}
	}
}

	public static void main(String[] args) {		
		// 继承方式
		Mythread th1 = new Mythread("th1");
		Mythread th2 = new Mythread("th2");
		th1.start();
		th2.start();
		
		for(int i = 0; i < 100; i++) {
            //Thread.currentThread(),Thread的静态方法,返回当前线程对象
			System.out.println(Thread.currentThread().getName() + ": " + i);
		}
		
	}

2.2 实现Runnable接口方式

2.2.1 步骤

  1. 定义一个任务类,实现Runnable接口
  2. 重写任务类中的run方法,用于定义任务的内容
  3. 创建任务类对象,表示任务
  4. 创建一个thread类型的对象,用于执行任务类对象
  5. 调用线程对象的start方法,开启新线程

2.2.2 实现代码

public static void main(String[] args) {
		//实现方式
		Thread th1 = new Thread(new MyRunnable());
		th1.start();
		for(int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + ": " + i);
		}
}

public class MyRunnable implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + ": " + i);
		}
	}
}

2.3两种方式的比较

从代码复杂程度来看,很明显,继承Thread 的方式比较简单,而实现Runnable 接口的方式比较复杂

2.3.1 继承Thread方式的实现原理

继承方式:调用start方法,调用start0方法,start0是本地方法(native),由虚拟机实现,是C语言实现的方法,所以在java中看不到代码。本地方法start0返回来调用java中的run方法,run方法已经在子类中重写过了,所以最终运行的是子类重写了的run方法

2.3.2实现Runnable接口方式原理

构造方法中,将Runnable的实现类对象传入构造方法中,经过一路init方法的传递,最终,用于给Thread类型中的某个成员变量(target)赋值;调用对象的start方法,最终也是返回来调用Thread类中的run方法,判断当前的成员变量target是否为null,如果不为null,就调用target的run方法,而这个run方法我们已经重写过了,最终运行的是我们重写过的run方法。

2.3.3从java程序设计角度

由于java中只支持单继承、不支持多继承,但可以实现多个接口

  • 继承方式:某个类继承了Thread类,那么就无法继承其他业务中需要的类型,就限制了我们的设计。所以扩展性较差。将线程对象和任务内容绑定在了一起,耦合性较强、灵活性较差
  • 实现方式:某个类通过实现Runnable的方式完成了多线程的设计,仍然可以继承当前业务中的其他类型,扩展性较强。将线程对象和任务对象分离,耦合性就降低,灵活性增强:同一个任务可以被多个线程对象执行,某个线程对象也可以执行其他的任务对象。并且将来还可以将任务类对象,提交到线程池中运行;任务类对象可以被不同线程运行,方便进行线程之间的数据交互。

2.4 Thread类常用的方法

2.4.1实例方法

  • String getName()  -  获取线程名称,默认为Thread-x,x为从0开始的序号
  • void setName(String name)  -   设置线程名称, 可在启动前设置,也可以在启动后设置
  • void start()  -  线程开始执行
  • long getId()   -   获取线程的标识符
  • int getPriority()  -  返回此线程的优先级  
  • void setPriority(int newPriority)  -  设置线程优先级,优先级数字越大,优先级越高,默认为5,最大为10,最小为1,Thread内有MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY  三个优先级常量对应上述数字
  • void setDaemon(boolean on)  -  设置此线程为守护线程(即后台线程,如垃圾回收线程,若非守护线程都死亡,程序将即将退出)
  • void join()  -  等待这个线程实例死亡,即执行结束
  • void join(long millis)  -  等待这个线程实例millis毫秒
		Mythread th1 = new Mythread();
		
		for(int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + ": " + i);
			if(i == 4) {
				th1.start();
				th1.join();
			}
		}

/**执行结果
main: 0
main: 1
main: 2
main: 3
main: 4
Thread-0: 0
Thread-0: 1
Thread-0: 2
Thread-0: 3
Thread-0: 4
Thread-0: 5
Thread-0: 6
Thread-0: 7
Thread-0: 8
Thread-0: 9
main: 5
main: 6
main: 7
main: 8
main: 9
*/

2.4.2 静态方法

  • static void sleep(long millis)   -  暂停当前线程
  • static Thread currentThread()  -  返回当前线程对象

三、多线程的安全问题

线程的执行顺序是随机的,在某个线程执行没有完成的时候,cpu就可能被其他线程抢走,结果导致当前代码中的一些数据发生错误。

public class Myprint {
	
	public void printHello() {
			System.out.print("H");
			System.out.print("e");
			System.out.print("l");
			System.out.print("l");
			System.out.print("o");
			System.out.print(" ");
	}
	
	public void printWorld() {
			System.out.print("W");
			System.out.print("o");
			System.out.print("r");
			System.out.print("l");
			System.out.print("d");
			System.out.print("\n");		
	}
}	
public static void main(String[] args) throws InterruptedException {
		Myprint p = new Myprint();
		
		new Thread() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				while(true) {
					p.printHello();
				}
			}
		}.start();
		
		new Thread() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				while(true) {
					p.printWorld();
				}
			}
		}.start();
}

如上述例子中希望的是能完整得打印出Hello、World,而运行结果却不尽人意

3.1 同步代码块

使用一种格式,达到让某段代码执行的时候,cpu不要切换到影响当前代码的代码上去这种格式,可以确保cpu在执行A线程的时候,不会切换到影响A线程执行的其他线程上去

3.1.1 同步代码块的格式

需要用到synchronized 关键字

synchronized (锁对象) {
 	 //需要同步的代码
}

如对Myprint类进行修改:

public class Myprint {
	private Object obj = new Object();
	
	public void printHello() {
		synchronized (obj) {
			System.out.print("H");
			System.out.print("e");
			System.out.print("l");
			System.out.print("l");
			System.out.print("o");
			System.out.print(" ");
		}
	}
	
	public void printWorld() {
		synchronized (obj) {
			System.out.print("W");
			System.out.print("o");
			System.out.print("r");
			System.out.print("l");
			System.out.print("d");
			System.out.print("\n");
		}
	}
}

cpu想去执行同步代码块的时候,需要先获取到锁对象,获取之后就可以运行代码块中的内容;当cpu正在执行当前代码块的内容时,cpu可以切换到其他代码,但是不能切换到具有相同锁对象的代码上。

当cpu执行完当前代码块中的代码之后,就会释放锁对象,cpu就可以运行其他具有当前锁对象的同步代码块了

这样就能完整得打印出Hello World

3.2 同步方法

如上述的Myprint内的方法可修改为

public class Myprint {
	
	public synchronized void printHello() {
			System.out.print("H");
			System.out.print("e");
			System.out.print("l");
			System.out.print("l");
			System.out.print("o");
			System.out.print(" ");
	}
	
	public synchronized void printWorld() {
			System.out.print("W");
			System.out.print("o");
			System.out.print("r");
			System.out.print("l");
			System.out.print("d");
			System.out.print("\n");
	}
}

用synchronized修饰方法效果会如上同步代码块一样,而此时锁的对象默认为 this

3.3 同步锁

也可用Lock类对同步代码进行加锁、释放锁,以此来保证线程执行的原子性

import java.util.concurrent.locks.ReentrantLock;

public class Myprint {
	private ReentrantLock lock = new ReentrantLock();
	
	public void printHello() {
		lock.lock();	
		System.out.print("H");
		System.out.print("e");
		System.out.print("l");
		System.out.print("l");
		System.out.print("o");
		System.out.print(" ");
		lock.unlock();	
	}
	
	public synchronized void printWorld() {
		lock.lock();	
		System.out.print("W");
		System.out.print("o");
		System.out.print("r");
		System.out.print("l");
		System.out.print("d");
		System.out.print("\n");
		lock.unlock();	
	}
}

四、线程池

在没有引入线程池前,我们使用一条线程时,先将线程对象创建出来,启动线程,在运行过程中,可能完成任务,也可能会在中途被任务内容中断掉,任务还没有完成;或是正常完成,线程对象结束就变成垃圾对象被垃圾回收器回收。如果在整个程序中有很多的小任务,任务所需要执行的时间又很少,就会有大量的时间浪费在线程对象的创建和死亡中。

而引入线程池后,系统会在创建大量空闲的线程,程序将一个 Runnable对象传给线程池,线程池就会启动一个空闲的线程来执行它们的run()方法,当run()方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run()。

4.1 使用Executors工具类获取线程池对象

public static void main(String[] args) throws InterruptedException {
		ExecutorService es = Executors.newFixedThreadPool(2);  //创建拥有2个线程的线程池
		MyRunnable th1 = new MyRunnable("th1");
		MyRunnable th2 = new MyRunnable("th2");
		es.submit(th1);
		es.submit(th2);
		es.shutdown();		//结束线程池
		es.shutdownNow();	//结束线程池,已经开始运行的保证完成,提交却没有运行的不再运行
}

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值