实训第三周/星期五 多线程

多线程

这个要求自己了解的很重要,没时间讲自学

简单粗略的的介绍

多线程是干什么的?

用来解决,并发做多件事,高效利用cpu资源

如何创建一个线程?

唯一:

new Thread()

他的任务是什么?

把runnable中的 run() 方法作为一个任务去执行.

cpu做的是什么工作?

执行代码

在这里插入图片描述
线程是不是越多越好?为什么?

不是.
在这里插入图片描述
如图:车太多 ,cpu不够
引发了 cpu抢占:上下文转换

原因1:内存会有压力,内存不足,

  • 一个线程最大栈大小1M

原因2:执行单位有限,执行不足

原因3:从一个运行的线程切换到另一个 非常消耗资源,影响性能

原因4: 线程执行完任务 需要回收 ,否则内存会被填满

原因5:时间计算: 线程的创建,销毁 都要时间 于是
计算一:创建时间+销毁时间>执行任务时间 就不合理

解决思路:
我们当然想用多线程,并发做事,让线程吧代码交给cpu执行,同时 关键是如何将线程数量 控制的合适,cpu也可以被充分利用

关键点深挖: **合适数量 : 构建 一个 池

内存----优化为–>线程池:

在这里插入图片描述

1. 不用再考虑创建,消除线程
2. 任务用什么表示?**

Runnable: 将任务送到cpu处理

Callable: 可抛出异常 ,观察者(future 未来)

在这里插入图片描述

仓库用什么?

BlockingQueue阻塞队列,线程是安全

在队列为空时获取阻塞 ,在队列满时放入阻塞

对于不能立刻满足,可能在将来某个时刻满足的操作:
四种:

BlockingQueue
抛出一个异常
返回一个特殊值:null/false
前面操作完之前,无限阻塞当前线程
给定最大时间限制内阻塞
出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e, time, unit)
移除remove()poll()take()poll(time, unit)
检查element()peek()不可用不可用
  1. offer: 将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果当前没有可用的空间,则返回 false,不会抛异常:

  2. put:
    将指定元素插入此队列中,将等待可用的空间.通俗点说就是>maxSize 时候,阻塞,直到能够有空间插入元素

  3. take: 获取并移除此队列的头部,在元素变得可用之前一直等待 。queue的长度 == 0 的时候,一直阻塞

  4. add: 和collection的add一样,没什么可以说的。如果当前没有可用的空间,则抛出 IllegalStateException。

应用源码编写:

根据上图 来思考 有什么 ? 怎么写?

package com.ThreadPool;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class FixedSizeThreadPool {

	//1. 需要仓库[阻塞队列],他的内部是 放[阻塞]  任务
	private BlockingQueue<Runnable> blockingQueue;
	//2. 线程集合 ,图中的小卡车集合
	private List<Thread> workers;
	
	// 2.1 具体 干活的线程(具体的卡车--线程..take()拿任务)
	public static class Worker extends Thread{
		
		
		private FixedSizeThreadPool pool;           //解释我是属于哪个池
		public Worker( FixedSizeThreadPool pool) {  //解释我是属于哪个池
			this.pool = pool;
		}
		
		
		@Override
		public void run(){
			while(true){
				Runnable task =null;
				//队列 拿东西  需要阻塞+++在队列为空时获取阻塞 ,在队列满时放入阻塞
				try {
					task = this.pool.blockingQueue.take();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				if(task!=null){
					task.run();//具体任务
					System.out.println("线程:"+Thread.currentThread().getName()+"执行完毕");
					
					
				}
			}
			
		}
		
		
		
		
	}
	//线程池 初始化 仓库大小 设置 ,线程数量设置

	public FixedSizeThreadPool(int poolSize ,int taskSize) {
		if(poolSize<=0||taskSize<=0)
			throw new IllegalArgumentException("非法参数");
			
			this.blockingQueue = new LinkedBlockingQueue<>(taskSize);
			this.workers = Collections.synchronizedList(new ArrayList<>());
			
			//线程传了几次实例化几次
			for(int i=0 ;i <poolSize ;i++){
					Worker worker = new Worker(this);
					worker.start();
					workers.add(worker);
	
		}
		
	}
	
	//把任务提交到仓库中的 方法
	
	public boolean submit(Runnable task){
		return this.blockingQueue.offer(task);
		
	}
	
	 public static void main(String[] args) {
		
 	FixedSizeThreadPool pool = new FixedSizeThreadPool(3,6);
		
		for(int i = 0; i <6 ; i++){
			  pool.submit(new Runnable(){
				
			       	@Override 
				    public void run (){
					    System.out.println("放入一个线程");
					    
					    try {
							Thread.sleep(2000L);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
				      }
				
			   });
			
		}
		
	}
	
}



执行结果:
放入一个线程
放入一个线程
放入一个线程
线程:Thread-2执行完毕
放入一个线程
线程:Thread-0执行完毕
放入一个线程
线程:Thread-1执行完毕
放入一个线程
线程:Thread-1执行完毕
线程:Thread-0执行完毕
线程:Thread-2执行完毕

  • 线程池中断添加

改进思路:

  1. 仓库结束被提交任务

public boolean submit(Runnable task){
if(isWorking){
return this.blockingQueue.offer(task);
} else {
return false;
}
}

  1. 仓库内有的任务 执行完 清空

this.pool.blockingQueue.size()> 0

  1. 线程拿任务 不用再阻塞 ,task = this.pool.blockingQueue.poll();
@Override
		public void run(){
			while(this.pool.isWorking || this.pool.blockingQueue.size()> 0){
				Runnable task =null;
				//队列 拿东西  需要阻塞+++在队列为空时获取阻塞 ,在队列满时放入阻塞
				try {
					if(this.pool.isWorking ){
					task = this.pool.blockingQueue.take();
					} else {
						task = this.pool.blockingQueue.poll();
					}
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				if(task!=null){
					task.run();//具体任务
					System.out.println("线程:"+Thread.currentThread().getName()+"执行完毕");
					
					
				}
			}
			
		}
  1. 一旦任务阻塞 ,我们强行中断他

改进后的代码:

private volatile boolean isWorking = true;
public void shutDown(){
//关闭线程
this.isWorking =isWorking;
for(Thread thread:workers){
if( thread.getState().equals(Thread.State.BLOCKED) ){
thread.interrupt();//线程中断
}
}
}

如何确定合适数量的线程
  1. 计算型任务(1+2)

cpu数量的 1-2倍

  1. IO型任务(HTTP请求,)

一般会多一些,具体根据IO阻塞时长(执行时间) 进行决定

系统架构设计

在这里插入图片描述

负载均衡器,单台机器扛不住怎么办?

集群后进行分流

应用层,服务层 :系统压力大 怎么办?

多线程 ,性能优化 ,高性能RPC

和数据访问层
读多写少

用缓存

写多读少

用缓冲

数据库压力大

分表分库

平台层 :系统快速扩容

机器集群搭建 docker+k8s


线程优先级

线程的优先级是用来决定何时从一个运行的线程切换到另一个。这叫“上下文转换” (context switch)。
非常消耗cpu资源

  • 优先级高的线程比优先级低的线程获得更多的CPU时间。实际上,线程获得的CPU时间通常由包括优先级在内的多个因素决定

设置线程的优先级, 用setPriority()方法,该方法也是Tread 的成员。它的通常形式为:

final void setPriority(int level)

这 里, level指定 了对所调 用的线 程的新的 优先权 的设置 。

Level的 值必 须在MIN_PRIORITY到MAX_PRIORITY范围内。通常,它们的值分别是1和10。要返回一个线程为默认的优先级,指定NORM_PRIORITY,通常值为5。这些优先级在Thread中都被定义为final型变量。

你可以通过调用Thread的getPriority()方法来获得当前的优先级设置。该方法如下:

final int getPriority( )

下面的例子阐述了两个不同优先级的线程,运行于具有优先权的平台,这与运行于无优先级的平台不同。一个线程通过Thread.NORM_PRIORITY设置了高于普通优先级两级的级数,另一线程设置的优先级则低于普通级两级。两线程被启动并允许运行10秒。每个线程执行一个循环,记录反复的次数。 10秒后,主线程终止了两线程。每个线程经过循环的次数被显示。

// Demonstrate thread priorities.
class clicker implements Runnable {
int click = 0;
Thread t;
private volatile boolean running = true;
public clicker(int p) {
t = new Thread(this);
t.setPriority(p);
}
public void run() {
while (running) {
click++;
}
}
public void stop() {
running = false;
}
public void start() {
t.start();
}
}
class HiLoPri {
public static void main(String args[]) {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
clicker hi = new clicker(Thread.NORM_PRIORITY + 2);
clicker lo = new clicker(Thread.NORM_PRIORITY - 2);
lo.start();
hi.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("Main thread interrupted.");
}
lo.stop();
hi.stop();
// Wait for child threads to terminate.
try {
hi.t.join();
lo.t.join();
} catch (InterruptedException e) {
System.out.println("InterruptedException caught");
}
System.out.println("Low-priority thread: " + lo.click);
System.out.println("High-priority thread: " + hi.click);
}
}

while (running) {
click++;
}
如果不用volatile, Java可以自由的优化循环: running的值被存在CPU的一个寄存器中,每次重复不一定需要复检。 volatile的运用阻止了该优化,告知Java running可以改变,改变方式并不以直接代码形式显示。


同步性

每个对象都拥有自己的隐式管程,当对象的同步方法被调用时管程自动载入。一旦一个线程包含在一个同步方法中,没有其他线程可以调用相同对象的同步方法。
使用同步方法
synchronized方法

同步语句
该类不是你自己,而是第三方创建的,你不能获得它的源代码。这样,你不能在相关方法前加synchronized修饰符。怎样才能使该类的一个对象同步化呢?很幸运,解决方法很简单:你只需将对这个类定义的方法的调用放入一个synchronized块内就可以了。


死锁

需要避免的与多任务处理有关的特殊错误类型是死锁(deadlock)。死锁发生在当两个线程对一对同步对象有循环依赖关系时。例如,假定一个线程进入了对象X的管程而另一个线程进入了对象Y的管程。如果X的线程试图调用Y的同步方法,它将像预料的一样被锁定。而Y的线程同样希望调用X的一些同步方法,线程永远等待,因为为到达X,必须释放自己的Y的锁定以使第一个线程可以完成。


Thread 类和Runnable 接口

Java的多线程系统建立于Thread类,它的方法,它的共伴接口Runnable基础上。 Thread类封装了线程的执行。既然你不能直接引用运行着的线程的状态,你要通过它的代理处理它,于是Thread 实例产生了。为创建一个新的线程,你的程序必须扩展Thread 或实现Runnable接口。
Thread类定义了好几种方法来帮助管理线程。
在这里插入图片描述


主 线 程

当Java程序启动时,一个线程立刻运行,该线程通常叫做程序的主线程(main thread)因为它是程序开始时就执行的。

主线程的重要性体现在两方面:

  • 它是产生其他子线程的线程
  • 通常它必须最后完成执行,因为它执行各种关闭动作。

尽管主线程在程序启动时自动创建,但它可以由一个Thread对象控制。
为此,你必须调用方法currentThread()获得它的一个引用, currentThread()是Thread类的公有的静态成员。
它的通常形式如下:

static Thread currentThread( )

该方法返回一个调用它的线程的引用。一旦你获得主线程的引用,你就可以像控制其他线程那样控制主线程

class CurrentThreadDemo {
public static void main(String args[]) {
Thread t = Thread.currentThread();
System.out.println("Current thread: " + t);
// change the name of the thread
t.setName("My Thread");
System.out.println("After name change: " + t);
try {
for(int n = 5; n > 0; n--) {
System.out.println(n);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Main thread interrupted");
}
}
}
在本程序中,当前线程(自然是主线程)的引用通过调用currentThread()获得,该引用
保存在局部变量t中。然后,程序显示了线程的信息。接着程序调用setName()改变线程的内
部名称。线程信息又被显示。然后,一个循环数从5开始递减,每数一次暂停一秒。暂停是
由sleep()方法来完成的。 Sleep()语句明确规定延迟时间是1毫秒。注意循环外的try/catch块。
Thread类的sleep()方法可能引发一个InterruptedException异常。这种情形会在其他线程想要打搅沉睡线程时发生。本例只是打印了它是否被打断的消息。在实际的程序中,你必须灵活处理此类问题。下面是本程序的输出:
Current thread: Thread[main,5,main]
After name change: Thread[My Thread,5,main]
5
4
3
2
1

注意t作为语句println()中参数运用时输出的产生。该显示顺序:线程名称,优先级以及组的名称。默认情况下,主线程的名称是main。它的优先级是5,这也是默认值, main也是所属线程组的名称。一个线程组(thread group)是一种将线程作为一个整体集合的状态控制的数据结构。这个过程由专有的运行时环境来处理,在此就不赘述了。线程名改变后, t又被输出。这次,显示了新的线程名。
让我们更仔细的研究程序中Thread类定义的方法。 sleep()方法按照毫秒级的时间指示使
线程从被调用到挂起。它的通常形式如下:

static void sleep(long milliseconds) throws InterruptedException

挂起的时间被明确定义为毫秒。该方法可能引发InterruptedException异常。
sleep()方法还有第二种形式,显示如下,该方法允许你指定时间是以毫秒还是以纳秒为周期。

static void sleep(long milliseconds, int nanoseconds) throws
InterruptedException

第二种形式仅当允许以纳秒为时间周期时可用。
如上述程序所示,你可以用setName()设置线程名称,用getName()来获得线程名称(该过程在程序中没有体现)。这些方法都是Thread 类的成员,声明如下:

final void setName(String threadName)
final String getName( )
这里, threadName 特指线程名称


创 建 线 程

Java定义了两种方式:
· 实现Runnable 接口。
· 可以继承Thread类

实现Runnable接口
创建线程的最简单的方法就是创建一个实现Runnable 接口的类。 Runnable抽象了一个执行代码单元。你可以通过实现Runnable接口的方法创建每一个对象的线程。
为实现Runnable 接口,[一个类仅需实现一个run()的简单方法],该方法声明如下:

public void run( )

在run()中可以定义代码来构建新的线程。理解下面内容是至关重要的: run()方法能够像主线程那样调用其他方法,引用其他类,声明变量。仅有的不同是run()在程序中确立另一个并发的线程执行入口。
当run()返回时,该线程结束。
在你已经创建了实现Runnable接口的类以后,你要在[类内部实例化一个Thread类的对象]。 Thread 类定义了好几种构造函数。我们会用到的如下:

Thread(Runnable threadOb, String threadName)

该构造函数中, threadOb是一个实现Runnable接口类的实例。 这定义了线程执行的起点。
新线程的名称由threadName定义。
建立新的线程后,它并不运行直到调用了它的start()方法,该方法在Thread 类中定义。
本质上, start() 执行的是一个对run()的调用。 Start()方法声明如下:

void start( )

下面的例子是创建一个新的线程并启动它运行:
// Create a second thread.
class NewThread implements Runnable {
Thread t;
NewThread() {
// Create a new, second thread
t = new Thread(this, "Demo Thread");
System.out.println("Child thread: " + t);
t.start(); // Start the thread
}
// This is the entry point for the second thread.
public void run() {
try {
for(int i = 5; i > 0; i--) {
System.out.println("Child Thread: " + i);
Thread.sleep(500);
}
} catch (InterruptedException e) {
System.out.println("Child interrupted.");
}
System.out.println("Exiting child thread.");
}
}
class ThreadDemo {
public static void main(String args[]) {
new NewThread(); // create a new thread
try {
for(int i = 5; i > 0; i--) {
System.out.println("Main Thread: " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Main thread interrupted.");
}
System.out.println("Main thread exiting.");
}
}
在NewThread 构造函数中,新的Thread对象由下面的语句创建: :
t = new Thread(this, "Demo Thread");
通过前面的语句this 表明在this对象中你想要新的线程调用run()方法。然后, start()调用,以run()方法为开始启动了线程的执行。这使子线程for 循环开始执行。调用start()之
后, NewThread 的构造函数返回到main()。当主线程被恢复,它到达for 循环。两个线程继
续运行,共享CPU,直到它们的循环结束。该程序的输出如下:

Child thread: Thread[Demo Thread,5,main]
Main Thread: 5
Child Thread: 5
Child Thread: 4
Main Thread: 4
Child Thread: 3
Child Thread: 2
Main Thread: 3
Child Thread: 1
Exiting child thread.
Main Thread: 2
Main Thread: 1
Main thread exiting.
如前面提到的,在多线程程序中,通常主线程必须是结束运行的最后一个线程。实际上,一些老的JVM,如果主线程先于子线程结束, Java的运行时间系统就可能“挂起”。
前述程序保证了主线程最后结束,因为主线程沉睡周期1000毫秒,而子线程仅为500毫秒。
这就使子线程在主线程结束之前先结束。简而言之,你将看到等待线程结束的更好途径。

扩展Thread
创建线程的另一个途径是创建一个新类来扩展Thread类,然后创建该类的实例。当一个类继承Thread时,它必须重载run()方法,这个run()方法是新线程的入口。它也必须调用start()方法去启动新线程执行。下面用扩展thread类重写前面的程序:

// Create a second thread by extending Thread
class NewThread extends Thread {
NewThread() {
// Create a new, second thread
super("Demo Thread");
System.out.println("Child thread: " + this);
start(); // Start the thread
}
// This is the entry point for the second thread.
public void run() {
try {
for(int i = 5; i > 0; i--) {
System.out.println("Child Thread: " + i);
Thread.sleep(500);
}
} catch (InterruptedException e) {
System.out.println("Child interrupted.");
}
System.out.println("Exiting child thread.");
}
}
class ExtendThread {
public static void main(String args[]) {
new NewThread(); // create a new thread
try {
for(int i = 5; i > 0; i--) {
System.out.println("Main Thread: " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Main thread interrupted.");
}
System.out.println("Main thread exiting.");
}
}

该程序生成和前述版本相同的输出。子线程是由实例化NewThread对象生成的,该对象从Thread类派生。注意NewThread 中super()的调用。该方法调用了下列形式的Thread构造函数:
public Thread(String threadName)
这里, threadName指定线程名称

使用isAlive()和join() 满足你希望主线程最后结束

有两种方法可以判定一个线程是否结束。第一,可以在线程中调用isAlive()。这种方法
由Thread定义,它的通常形式如下:

final boolean isAlive( )

如果所调用线程仍在运行, isAlive()方法返回true,如果不是则返回false。
但isAlive()很少用到,等待线程结束的更常用的方法是调用join(),描述如下:

final void join( ) throws InterruptedException

该方法等待所调用线程结束。该名字来自于要求线程等待直到指定线程参与的概念。
join()的附加形式允许给等待指定线程结束定义一个最大时间。
下面是前面例子的改进版本。运用join()以确保主线程最后结束。同样,它也演示了isAlive()方法。

// Using join() to wait for threads to finish.
class NewThread implements Runnable {
String name; // name of thread
Thread t;
NewThread(String threadname) {
name = threadname;
t = new Thread(this, name);
System.out.println("New thread: " + t);
t.start(); // Start the thread
}
// This is the entry point for thread.
public void run() {第 11 章 多线程编程 201
try {
for(int i = 5; i > 0; i--) {
System.out.println(name + ": " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println(name + " interrupted.");
}
System.out.println(name + " exiting.");
}
}
class DemoJoin {
public static void main(String args[]) {
NewThread ob1 = new NewThread("One");
NewThread ob2 = new NewThread("Two");
NewThread ob3 = new NewThread("Three");
System.out.println("Thread One is alive: "
+ ob1.t.isAlive());
System.out.println("Thread Two is alive: "
+ ob2.t.isAlive());
System.out.println("Thread Three is alive: "
+ ob3.t.isAlive());
// wait for threads to finish
try {
System.out.println("Waiting for threads to finish.");
ob1.t.join();
ob2.t.join();
ob3.t.join();
} catch (InterruptedException e) {
System.out.println("Main thread Interrupted");
}
System.out.println("Thread One is alive: "
+ ob1.t.isAlive());
System.out.println("Thread Two is alive: "
+ ob2.t.isAlive());
System.out.println("Thread Three is alive: "
+ ob3.t.isAlive());
System.out.println("Main thread exiting.");
}
}
程序输出如下所示:
New thread: Thread[One,5,main]
New thread: Thread[Two,5,main]
New thread: Thread[Three,5,main]
Thread One is alive: true
Thread Two is alive: true
Thread Three is alive: true
Waiting for threads to finish.
One: 5第 1 部分 Java 语言 202
Two: 5
Three: 5
One: 4
Two: 4
Three: 4
One: 3
Two: 3
Three: 3
One: 2
Two: 2
Three: 2
One: 1
Two: 1
Three: 1
Two exiting.
Three exiting.
One exiting.
Thread One is alive: false
Thread Two is alive: false
Thread Three is alive: false
Main thread exiting.

如你所见,调用join()后返回,线程终止执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值