2021.6.28 多线程
什么是多线程:
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运算单位,而多线程就是指从软件或者硬件上实现多个线程并发执行的技术,具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。
简单来说:线程是程序中一个单一的顺序控制流程;而多线程就是在单个程序中同时运行多个线程来完成不同的工作。
多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。多个线程是同一时间需要完成多项任务的时候实现的。
优点:
1,多线程技术可以加快程序的运行速度,使程序的响应速度更快,因为用户界面可以在进行其他工作的同时一直处于活动状态。
2,可以把占据长时间的程序中的任务放到后台去处理,同时执行其他操作,提高效率
3,当前没有进行处理的任务时可以将处理器时间让其他任务
4,可以让同一个程序的不同部分并发执行,释放一些珍贵的资源如内存占用等等。
5,可以随时停止任务
6,可以分别设置各个任务的优先级以优化性能
缺点:
1,因为多线程需要开辟内存,而且多线程切换需要时间因此会很消耗系统内存
2,线程的终止会对程序产生影响
3,由于多个线程之间存在共享数据,因此容易出现线程死锁的情况
4,对线程进行管理要求额外的cpu开销。线程的使用会给系统带来上下文切换的额外负担
1,多线程有什么用?
1)发挥多核cpu的优势
随着工业的进步,现在的笔记本,台式机乃至商用的应用服务器至少也都是双核的,4核,8核甚至16核,如果是单线程的程序,那么在双核cpu上就浪费了50%,单核cpu上,所谓的多线程那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换的比较快,看着像多个线程同时运行罢了。多核cpu上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程可以真正发挥出多先线程的优势来,达到充分利用cpu的目的。
2)防止阻塞
从程序运行效率的角度来说,单核不但不会发挥出多线程的优势,反而会因为在单核cpu上运行多线程导致线程上下文的切换,而降低程序的整体的效率.但是单核cpu我们还是要应用多线程,就是为了防止阻塞。如果单核cpu使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其他任务的执行。
3)便于建模
这就是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦,但是如果这个大的任务A分解程几个小任务,任务B,任务C,任务D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。
2,线程和进程的区别
进程和线程的主要差别在于他们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃以后,在保护模式下不会对其他进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但是线程之间没有的单独的地址空间,一个线程死掉就等于整个进程的死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换的时候,耗费资源比较大,效率要差一些。但是对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程.
3,java实现线程的方式
- 继承Thread 类实现多线程
2)实现Runnable 接口实现多线程
3)使用 ExecutorService ,Callable ,Future 实现有返回结果的多线程。
4,启动线程的方法 start()和 run()有什么区别
只有调用了start() 方法,才会表现出多线程的特征,不同线程的 run()方法里面的代码交替执行。如果只是调用run() 方法,那么代码还是同步执行的,必须等待一个线程的 run()方法里的代码全部执行完毕之后,另外一个线程才可以执行其 run()方法里面的代码。
5,如何终止一个线程,以及如何优雅的终止线程
stop,不推荐
6,线程的生命周期有哪几种状态,以及他们之间的流转
- new:创建线程,没开始启动
- runnable:表示线程触发start()方式调用,线程正式启动,线程处于运行中状态
- blocked:表示线程阻塞,等待获取锁(资源锁),如碰到synchronized,lock等关键字等占用临界区的情况,一旦获取到锁就进行runnable 状态继续运行。
- waiting:表示线程处于无限制等待状态,等待一个特殊的事件来重新唤醒,如wait()方法进行等待的线程等待一个 notify() 或者 notifyAll()方法,通过join()方法进行等待的线程等待目标线程运行结束而唤醒,一旦通过相关事件唤醒线程,线程就进入到了runnable 状态继续运行。
- Timed_waiting:表示线程进入到了一个有时限的等待,如sleep(3000),等待3 秒后线程重新进入到 runnable 状态继续执行。
- terminated:表示线程执行完毕后,进行终止状态。需要注意的是,一旦线程通过start方法启动后就再也不能回到初始 new 状态,线程终止后也再不能回到 runnable 状态。
7,线程中的wait()和 sleep() 方法有什么区别
sleep 方法 和 wait() 方法都可以用来放弃cpu 一定的时间,不用点在于 如果线程持有某个对象的监视器,sleep 方法不会放弃这个对象的监视器,wait 方法会放弃这个对象的监视器。
8,多线程同步有哪些方法
- synchronized 关键字
- lock 锁实现
- 分布式锁等
9,死锁
死锁就是两个线程互相等待对方释放对象锁
10,多线程之间如何通信
wait
notify
11,线程怎样拿到返回结果
实现callable接口
12,volatile 关键字
主要作用:
1,多线程主要围绕可见性和原子性两个特性而展开,使用volatile 关键字修饰的变量,保证了其再多线程之间的可见性,即每次读取到 volatile 变量,一定是最新的数据
2,代码底层执行:java代码-》字节码-》根据字节码对应的c/c++ 代码——》c/c++代码被编译程汇编语言-》和硬件电路交互,现实中,为了获取更好性能的jvm可能会对指令进行重排序,多线程下可能会出现一些意想不到的结果。使用volatile 则会对禁止语义重排序,当然这也一定程度上降低了代码执行效率从实践角度而言,volatile 的一个重要作用就是和CAS结合,保证原子性,详细的可以参见
java.util.concurrent.atomitc 包下的类,如AtomicInteger
13,新建 t1,t2,t3三个线程,如何保证他们的执行顺序
使用join方法
14,线程池
不使用线程池
每个线程都要通过 new Thread(xxRunnable).start()的方式创建并运行一个线程
当线程少的话这不会是问题,但是真实环境可能会开启多个线程让系统和程序达到最佳效率,当线程数达到一定数量就会耗尽系统的cpu和内存资源,也会造成gc频繁收集和停顿,因为每次创建和销毁一个线程都是需要消耗系统资源的,如果为每个任务都创建线程这无疑是一个很大的性能瓶颈。所以,线程池中的线程复用极大节省了系统资源,当线程一段时间不再有任务处理时它也会自动销毁,而不会常驻内存。
1.线程
1.1线程的一些方法
1)yield
让出当前cpu资源,等待调度重新去竞争
2)join
1.2静态代理模式
静态代理模式就是多线程的底部实现原理
关于多线程
- 真实角色和代理角色都实现了一个公共的接口
- 代理角色可以是做真实角色做不了的事情
示例
package com.zp;
/**
* @author zhangpeng
* @version 1.0
* @description 静态代理模式 :多线程的底层原理
* @date 2021/11/14 22:48
*/
public class ThreadDemo4 {
public static void main(String[] args) {
new WendingCompany(new Me()).HappyMary();
/**
* 关于 静态代理和多线程的故事
* 1、静态代理模式是多线程的基础
* 2、无论是使用 继承Thread还是实现Runnable接口,实际上他俩使用的都是实现了Runnable接口,
* 所以Runnable接口的run方法实际上就是可以看作一个公共方法,
* 3、Thread和Runnable看作代理对象,继承或者实现它的那个类看作是真实对象,start就是启动方法
*/
new Thread(()-> System.out.println("测试lambda表达式方式使用多线程")).start();
}
}
//需要共同实现的接口
interface Mary {
//方法
void HappyMary();
}
//真实角色
class Me implements Mary{
//实现方法
@Override
public void HappyMary() {
System.out.println("要结婚了");
}
}
//代理角色
class WendingCompany implements Mary {
private Mary target;
public WendingCompany(Mary target) {
this.target = target;
}
@Override
public void HappyMary() {
before();
this.target.HappyMary();
after();
}
//结婚之后做的事情
private void after() {
System.out.println("收拾");
}
//结婚之前做的事情
private void before() {
System.out.println("准备");
}
}
1.3lambda
lambda是jdk1.8的新特性,在于简化代码
package com.zp;
/**
* @author zhangpeng
* @version 1.0
* @description 测试lambda 与 函数式接口 和 匿名内部类
* @date 2021/11/15 23:14
*/
public class TreadDemo4 {
//简化1:通过静态内部类、
static class Like2 implements ILike {
@Override
public void test() {
System.out.println("I like lambda2");
}
}
public static void main(String[] args) {
//输出:接口new 实现类
ILike like = new Like();
like.test();
Like2 like2 = new Like2();
like2.test();
//简化2:局部内部类
class Like3 implements ILike {
@Override
public void test() {
System.out.println("I like lambda3");
}
}
like = new Like3();
like.test();
//简化3:使用匿名内部类
like = new Like() {
@Override
public void test() {
System.out.println("I like lambda4");
}
};
like.test();
//简化4:使用lambda
like = () ->{
System.out.println("I like lambda5");
};
like.test();
// 关于 lambda:
//1.由参数,lambda运算符,函数体组成
//2.参数:当参数 只有一个的时候可以去掉(),并且可以去掉参数的数据类型,java会自动推到
//3.函数体:当函数体只有一句话的时候可以去掉{},
//4.lambda的基础就是:匿名内部类和函数式接口
};
}
//1.定义函数式接口:就是只有一个方法的接口
interface ILike {
void test();
}
//2.接口的实现类
class Like implements ILike {
@Override
public void test() {
System.out.println("I like lambda");
}
}
1.4停止线程
关于停止线程:使用外部标志位来停止线程
package com.zp;
/**
* @author zhangpeng
* @version 1.0
* @description 测试线程停止
* @date 2021/11/16 22:21
*/
public class ThreadDemo5 implements Runnable{
/**
* 1、让线程自己停下来
* 2、建议使用标志位来停止线程
* 3、不推荐使用已经过时的方法:stop,destroy
*/
//1、标识位
boolean flag = true;
@Override
public void run() {
int i= 0;
while (flag) {
System.out.println("Thread-->"+i++);
}
}
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
ThreadDemo5 thread = new ThreadDemo5();
new Thread(thread).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main:->"+i);
if (i==900) {
thread.stop();
System.out.println("线程该停止了");
}
}
}
}
1.5 sleep
sleep方法:一般都是用来做一些即时的方法
sleep方法也不会释放锁
package com.zp;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.Date;
/**
* @author zhangpeng
* @version 1.0
* @description 测试sleep方法
* @date 2021/11/16 22:43
*/
public class TreadDemo6 implements Runnable{
@Override
public void run() {
}
public static void main(String[] args) throws InterruptedException {
//1.模拟倒计时
timeDown();
//2.模拟系统时计
Date date = new Date(System.currentTimeMillis());
while (true) {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("hh:MM:ss").format(date));
date = new Date(System.currentTimeMillis());
}
}
//1.模拟倒计时
static void timeDown() throws InterruptedException {
int num = 10;
while (true) {
Thread.sleep(1000);
System.out.println(num--);
if (num<=0) {
break;
}
}
}
}
1.6yield
礼让
1.7线程的状态
线程大致分为5种状态:new,就绪,运行,阻塞,结束
-
new
尚未启动的线程处于此状态
-
runnable
在java虚拟机中执行的线程处于此状态
-
blocked
被阻塞等待监视器锁定的线程处于此状态
-
waiting
正在等待另一个线程执行特定动作的线程处于此状态
-
timed_waiting
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
-
terminated
已退出的线程处于此状态
状态图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dD1Tnfe8-1638285447112)(C:\Users\zp\Desktop\java\java多线程\多线程图库\线程的状态.jpg)]
1.8synchronized
两种用法:同步方法和同步代码块=
- 同步方法:是修饰方法的,锁的是this,也就是类的实例
- 同步代码块:锁的是共享变量
同步代码块-实例
未加锁之前
package com.zp;
import java.util.ArrayList;
public class ThreadSynchronized3 {
public static void main(String[] args) throws InterruptedException {
ArrayList<String> arrayList = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
//synchronized : 这里是同步代码块 锁的是共享变量(也就是变化的量)
arrayList.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(3000);
System.out.println(arrayList.size());
}
}
//输出:不等于
加锁之后
package com.zp;
import java.util.ArrayList;
public class ThreadSynchronized3 {
public static void main(String[] args) throws InterruptedException {
ArrayList<String> arrayList = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
//synchronized : 这里是同步代码块 锁的是共享变量(也就是变化的量)
synchronized (arrayList) {
arrayList.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(3000);
System.out.println(arrayList.size());
}
}
//输出:10000
同步方法-实例
未加锁之前
package com.zp;
public class ThreadSynchronized2 {
public static void main(String[] args) {
Account account = new Account(100, "基金");
bank one = new bank(account, 50, "买白酒");
bank two = new bank(account, 100, "买军工");
one.start();
two.start();
}
}
//账户
class Account{
int money;//余额
String name;
public Account(int money,String name) {
this.money = money;
this.name = name;
}
}
//银行
class bank extends Thread{
Account account;//账户
int drawMoney;//取钱
int nowMoney;//现钱
public bank(Account account, int drawMoney, String name) {
super(name);
this.account = account;
this.drawMoney = drawMoney;
}
@Override
public void run() {
//synchronized :这里是同步代码块,锁的共享变量
synchronized (account) {
//首先判断有没有钱
if (account.money-drawMoney<0) {
System.out.println(Thread.currentThread().getName()+"需要:"+this.drawMoney+ " 钱不够了");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//账户的钱
account.money -=drawMoney;
//现钱
nowMoney+=drawMoney;
System.out.println(this.getName()+"的钱:"+nowMoney);
System.out.println(account.name+"余额为:"+account.money);
}
}
}
//输出:
/*
买军工的钱:100
基金余额为:-50
买白酒的钱:50
基金余额为:-50
*/
加锁之后
package com.zp;
public class ThreadSynchronized2 {
public static void main(String[] args) {
Account account = new Account(100, "基金");
bank one = new bank(account, 50, "买白酒");
bank two = new bank(account, 100, "买军工");
one.start();
two.start();
}
}
//账户
class Account{
int money;//余额
String name;
public Account(int money,String name) {
this.money = money;
this.name = name;
}
}
//银行
class bank extends Thread{
Account account;//账户
int drawMoney;//取钱
int nowMoney;//现钱
public bank(Account account, int drawMoney, String name) {
super(name);
this.account = account;
this.drawMoney = drawMoney;
}
@Override
public void run() {
//synchronized :这里是同步代码块,锁的共享变量
synchronized (account) {
//首先判断有没有钱
if (account.money-drawMoney<0) {
System.out.println(Thread.currentThread().getName()+"需要:"+this.drawMoney+ " 钱不够了");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//账户的钱
account.money -=drawMoney;
//现钱
nowMoney+=drawMoney;
System.out.println(this.getName()+"的钱:"+nowMoney);
System.out.println(account.name+"余额为:"+account.money);
}
}
}
//输出:
/*
买白酒的钱:50
基金余额为:50
买军工需要:100 钱不够了
*/
1.8.1关于synchronized的理解
说实话,认识它很久了,但是一直都是一知半解,今天打算好好理理它
1.9生产者消费者模式-管程法
通过一个缓冲区,生产者生产,消费者消费;来做到一个多线程之间的通信
- wait:等待(不同与sleep,wait会释放cpu资源,就是释放锁)
- notify/notifyAll,(唤醒/唤醒所有的线程)
package com.zp;
import java.awt.print.Pageable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
/**
* @author zhangpeng
* @version 1.0
* @description 生产者,消费者模式,管程法
* @date 2021/11/30 20:35
*/
public class ThreadDemo7 {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
new Producter(synContainer).start();
new ConSumer(synContainer).start();
}
}
//生产者
class Producter extends Thread {
SynContainer synContainer;
public Producter(SynContainer synContainer) {
this.synContainer = synContainer;
}
@Override
public void run() {
for (int i = 1; i < 100; i++) {
synContainer.setPhone(new Phone(i));
System.out.println("生产了:"+i+"个Phone");
}
}
}
//消费者
class ConSumer extends Thread {
SynContainer synContainer;
public ConSumer(SynContainer synContainer) {
this.synContainer = synContainer;
}
@Override
public void run() {
for (int i = 1; i < 100; i++) {
synContainer.getPhone();
System.out.println("消费了第:"+i+"个手机");
}
}
}
//产品
class Phone {
int num;
public Phone(int num) {
this.num = num;
}
}
//缓冲区
class SynContainer {
//容器
List<Phone> list = new ArrayList<>();
//Phone[] phones = new Phone[10];
//容器计数器
int count= 0;
//生产
synchronized void setPhone(Phone phone) {
//如果容器满了,需要通知消费者消费
// if (phones.length== count) {
if (list.size()== 10) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,需要生产者生产
//phones[count] = phone;
list.add(phone);
count++;
//通知消费者消费
this.notifyAll();
}
//消费
synchronized Phone getPhone() {
//判断能否消费
if (count == 0) {
//等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count --;
//Phone phone = phones[count];
Phone phone = list.get(count);
//消费完了通知生产
this.notifyAll();
return phone;
}
}
1.10生产者消费者模式-信号灯法
信号灯法:就是创建一个控制变量,通过改变控制变量的值来实现线程之间的通信
package com.zp;
/**
* @author zhangpeng
* @version 1.0
* @description 生产者,消费者模式,信号法
* @date 2021/11/30 22:08
*/
public class ThreadDemo8 {
public static void main(String[] args) {
Views views = new Views();
new Dancer(views).start();
new Player(views).start();
}
}
//生产者,舞蹈区up
class Dancer extends Thread{
Views views;
public Dancer(Views views) {
this.views = views;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2==0) {
this.views.dance("欣小萌的舞蹈");
} else {
this.views.dance("开卷");
}
}
}
}
//消费者,我->观众老爷
class Player extends Thread{
Views views;
public Player(Views views) {
this.views = views;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.views.play();
}
}
}
//产品,视频
class Views {
//产品,视频
String view;
/**
* 控制变量
* t:生产者生产
* f: 消费者消费
*/
boolean flag = true;
//生产
public synchronized void dance(String view) {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("打开了:"+view);
this.notifyAll();
this.view = view;
this.flag = !this.flag;
}
//消费
public synchronized void play() {
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("我观看了:"+view);
this.notifyAll();
this.flag = !this.flag;
}
}
1.11线程池
关于线程池:线程池是用来管理线程的,一是减少资源消耗,二是方便管理
线程池的好处
创建线程池的方法
-
Executors
工具类,线程池的工厂类,用于创建并返回不同类型的线程池
-
ExecutorService
真正的线程池接口,常见子类:ThreadPoolExecutor
常见方法
void executor(Runnable command)
:执行任务/命令,没有返回值,一般用来执行Runnable<T> Future<T> submit(Callable<T> task)
:执行任务,有返回值,一般用来执行Callablevoid shutDown()
:关闭线程池
实例
package com.zp;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author zhangpeng
* @version 1.0
* @description 线程池
* @date 2021/11/30 23:03
*/
public class ThreadDemo9 {
public static void main(String[] args) {
//Executors:创建线程池工具类
//newFixedThreadPool :参数 int 线程数量
ExecutorService executorService = Executors.newFixedThreadPool(5);
//执行方法
executorService.execute(new ThreadPool());
executorService.execute(new ThreadPool());
executorService.execute(new ThreadPool());
executorService.execute(new ThreadPool());
//关闭连接
executorService.shutdown();
}
}
//实现Runnable接口
class ThreadPool implements Runnable {
@Override
public void run() {
System.out.println("该睡觉了");
}
}
1.12多线程的总结
这个总结没讲啥,就是创建线程的三个方式;顺带说了一嘴线程的同步。总的来说关于多线程的章节,讲的太简单了;后续学习的话,推荐看java编程的逻辑一书,讲的很通俗很全面,看完可以接着看多线程并发的艺术和java多线程编程核心技术,java并发编程等书