多线程
这个要求自己了解的很重要,没时间讲自学
简单粗略的的介绍
多线程是干什么的?
用来解决,并发做多件事,高效利用cpu资源
如何创建一个线程?
唯一:
new Thread()
他的任务是什么?
把runnable中的 run() 方法作为一个任务去执行.
cpu做的是什么工作?
执行代码
线程是不是越多越好?为什么?
不是.
如图:车太多 ,cpu不够
引发了 cpu抢占:上下文转换
原因1:内存会有压力,内存不足,
- 一个线程最大栈大小1M
原因2:执行单位有限,执行不足
原因3:从一个运行的线程切换到另一个 非常消耗资源,影响性能
原因4: 线程执行完任务 需要回收 ,否则内存会被填满
原因5:时间计算: 线程的创建,销毁 都要时间 于是
计算一:创建时间+销毁时间>执行任务时间 就不合理
解决思路:
我们当然想用多线程,并发做事,让线程吧代码交给cpu执行,同时 关键是如何将线程数量 控制的合适,cpu也可以被充分利用
关键点深挖: **合适数量 : 构建 一个 池
内存----优化为–>线程池:
1. 不用再考虑创建,消除线程
2. 任务用什么表示?**
Runnable: 将任务送到cpu处理
Callable: 可抛出异常 ,观察者(future 未来)
仓库用什么?
BlockingQueue阻塞队列,线程是安全
在队列为空时获取阻塞 ,在队列满时放入阻塞
对于不能立刻满足,可能在将来某个时刻满足的操作:
四种:
出异常 | 特殊值 | 阻塞 | 超时 | |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
移除 | remove() | poll() | take() | poll(time, unit) |
检查 | element() | peek() | 不可用 | 不可用 |
-
offer: 将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果当前没有可用的空间,则返回 false,不会抛异常:
-
put:
将指定元素插入此队列中,将等待可用的空间.通俗点说就是>maxSize 时候,阻塞,直到能够有空间插入元素 -
take: 获取并移除此队列的头部,在元素变得可用之前一直等待 。queue的长度 == 0 的时候,一直阻塞
-
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执行完毕
- 线程池中断添加
改进思路:
- 仓库结束被提交任务
public boolean submit(Runnable task){
if(isWorking){
return this.blockingQueue.offer(task);
} else {
return false;
}
}
- 仓库内有的任务 执行完 清空
this.pool.blockingQueue.size()> 0
- 线程拿任务 不用再阻塞 ,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()+"执行完毕");
}
}
}
- 一旦任务阻塞 ,我们强行中断他
改进后的代码:
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+2)
cpu数量的 1-2倍
- 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()后返回,线程终止执行。