多线程详解
本篇文章同步学习:狂神说多线程讲解
线程简介
多任务:
多任务处理是指用户可以在同一时间内运行多个应用程序,每个应用程序被称作一个任务~
多线程:
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。比如:在网抑云上一边听歌一边评论~
进程:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位;比如:一个程序的运行,就是一个进程~
一个进程中有多个线程
线程实现(重点)
Thread:继承Thread
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
//继承Thread类
public class TestThread extends Thread{
//重写run方法
@Override
public void run() {
//run方法线程体
for (int i = 0; i <20 ; i++) {
System.out.println("深夜学习~~~~~"+i);
}
}
//主线程
public static void main(String[] args) {
//创建线程对象,调用start()方法启动线程
TestThread testThread = new TestThread();
testThread.start();
for (int i = 0; i < 20; i++) {
System.out.println("加油学习~~~~~"+i);
}
}
不建议使用:避免OOP单继承的局限性
Runnable:实现Runnable接口
- 自定义线程类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,放进Thread对象中,并调用start()方法启动线程
//自定义线程类实现Runnable接口
public class TestRunnable implements Runnable{
//实现run()方法,编写线程执行体
@Override
public void run() {
//run方法线程体
for (int i = 0; i <20 ; i++) {
System.out.println("深夜学习~~~~~"+i);
}
}
//主线程
public static void main(String[] args) {
//创建线程对象,放进Thread对象中,并调用start()方法启动线程
TestRunnable testRunnable = new TestRunnable();
new Thread(testRunnable).start();
for (int i = 0; i < 20; i++) {
System.out.println("加油学习~~~~~"+i);
}
}
}
ps:new Thread(testRunnable).start();等同于Thread thread = new Thread(testRunnable);thread.start();这是静态代理模式的体现
这是静态代理模式的体现(Thread实现了Runnable接口,TestRunnable也实现了Runna接口)
推荐使用:避免单继承局限性,灵活方便,方便同一对象被多个线程使用
Callable:实现Callable接口
-
实现Callable接口,需要返回值类型
-
实现call方法,需要抛出异常
-
创建目标对象
-
创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(1);
-
提交执行
Future<String> result = ser.submit(testCallable);
-
获取结果
String r = result.get();
-
关闭服务
ser.shutdownNow();
//1.实现Callable接口
public class TestCallable implements Callable<String>{
//2.实现call方法,需要抛出异常
@Override
public String call(){
System.out.println("wzf");
return "wzf";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//3.创建目标对象
TestCallable testCallable = new TestCallable();
//4.创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(1);
//5.提交执行
Future<String> result = ser.submit(testCallable);
//6.获取结果
String r = result.get();
//7.关闭服务
ser.shutdownNow();
}
}
静态代理
-
真实对象和代理对象要实现同一接口
-
代理要代理真实角色
优点:
- 代理对象可以做很多真实对象做不了的事情
- 真实对象专注自己的事情
public class StaticProxy {
public static void main(String[] args) {
//8.实例代理对象,并执行方法
ProxyYou proxyYou = new ProxyYou(new you());
proxyYou.HappyMarry();
}
}
//1.共同的接口
interface Marry{
void HappyMarry();
}
//2.本身
class you implements Marry{
//实现接口
@Override
public void HappyMarry() {
System.out.println("本人非常开心~");
}
}
//3.代理对象
class ProxyYou implements Marry{
//4.目标对象
private Marry target;
//5.构造方法
public ProxyYou(Marry target) {
this.target = target;
}
//实现接口
@Override
public void HappyMarry() {
System.out.println("代理你,结婚前");
//6.调用目标对象方法
this.target.HappyMarry();
//7.aop思想
System.out.println("代理你,结婚后");
}
}
对比
//8.实例代理对象,并执行方法
new ProxyYou(new you()).HappyMarry();
//对比
new Thread(()->{ System.out.println("和上面的静态代理如出一辙"); }).start();
Lamda表达式
-
λ希腊字母表中排序第十一位的字母,英语名称为Lamda
-
避免匿名内部类定义过多
-
其实质属于函数式编程的概念
-
前提是函数式接口才能用lamda表达式
函数式接口的定义:
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
例如:Runnable接口源码:
@FunctionalInterface public interface Runnable { public abstract void run(); }
对于函数式接口,我们可以通过lamda表达式来创建该接口的对象;
原来我们的习惯是:
//推导lamda表达式 public class TestLamda { public static void main(String[] args) { //3.接口new实现类 StudyLamda you = new You(); you.onlyone(); } } //1.定义一个函数式接口 interface StudyLamda{ //接口里的方法是抽象的,只包含一个抽象方法 void onlyone(); } //2.实现类 class You implements StudyLamda{ @Override public void onlyone() { System.out.println("你正在学习lamda表达式"); } }
静态内部类
//推导lamda表达式 public class TestLamda { //2.静态内部类 static class You implements StudyLamda{ @Override public void onlyone() { System.out.println("你正在学习lamda表达式"); } } public static void main(String[] args) { //3.接口new实现类 StudyLamda you = new You(); you.onlyone(); } } //1.定义一个函数式接口 interface StudyLamda{ //接口里的方法是抽象的,只包含一个抽象方法 void onlyone(); }
局部静态类
//推导lamda表达式 public class TestLamda { public static void main(String[] args) { //4.局部内部类 class You implements StudyLamda{ @Override public void onlyone() { System.out.println("你正在学习lamda表达式"); } } //3.接口new实现类 StudyLamda you = new You(); you.onlyone(); } } //1.定义一个函数式接口 interface StudyLamda{ //接口里的方法是抽象的,只包含一个抽象方法 void onlyone(); }
匿名内部类:
//推导lamda表达式 public class TestLamda { public static void main(String[] args) { //匿名内部类 StudyLamda studylamda = new StudyLamda() { @Override public void onlyone() { System.out.println("你正在学习lamda表达式"); } }; studylamda.onlyone(); } } //1.定义一个函数式接口 interface StudyLamda{ //接口里的方法是抽象的,只包含一个抽象方法 void onlyone(); }
最终简化
public class TestLamda { //2.静态内部类 public static void main(String[] args) { //lamda表达式 StudyLamda studyLamda = ()->{ System.out.println("你正在学习lamda表达式~"); }; studyLamda.onlyone(); } } //1.定义一个函数式接口 interface StudyLamda{ //接口里的方法是抽象的,只包含一个抽象方法 void onlyone(); }
lamda表达的简化:
public class TestLamda2 { public static void main(String[] args) { A a = (String s)->{ System.out.println("lamda的简化过程!"+s); }; //去掉参数类型 a = (s)->{ System.out.println("lamda的简化过程!"+s); }; //简化括号 a = s->{ System.out.println("lamda的简化过程!"+s); }; //简化花括号 a = s->System.out.println("lamda的简化过程!"+s); a.B("4"); /* 总结:lamda表达式只能有一行代码情况下才能简化,前提是函数式接口 参数可以去掉类型,要去掉就都去掉,多个要加括号 */ } } interface A{ void B(String s); }
线程状态
5大状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-66wYJICI-1599924615953)(C:\Users\王智芳\Desktop\截图\ThreadStats.jpg)]
线程停止
- 建议线程正常停止,利用次数,不建议死循环
- 建议使用标志位,设置一个标志位
- 不要使用stop或者destroy等过时或者jdk不建议使用的方法
public class TestStop implements Runnable{
//设置标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("ThreadStop!"+i++);
}
}
public void s(){
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 800; i++) {
//注意写上这个输出操作,不然时间太快,效果不能显示
System.out.println("main"+i);
if(i == 600){
testStop.s();
System.out.println("主线程中让线程停止了");
}
}
}
}
线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常interruptedexeception
- sleep时间到达后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
//模拟延时,扩大问题的发生性
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
线程礼让(yield)
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让CPU重新调度,礼让不一定成功,看CPU心情
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"A").start();
new Thread(myYield,"B").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"输出完之后会礼让");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"礼让不一定成功");
}
}
线程强制执行(join)
public class TestJoin implements Runnable{
@Override
public void run() {
System.out.println("vip的线程来了~");
}
public static void main(String[] args) {
TestJoin testJoin = new TestJoin();
Thread thread =new Thread(testJoin);
thread.start();
//主线程
for (int i = 0; i < 100; i++) {
System.out.println("主线程"+i);
if(i == 80){
try {
//插队,可能会让线程阻塞
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程观测状态
- 线程状态。线程可以处于以下状态之一:
NEW
尚未启动的线程处于此状态。RUNNABLE
在Java虚拟机中执行的线程处于此状态。BLOCKED
被阻塞等待监视器锁定的线程处于此状态。WAITING
正在等待另一个线程执行特定动作的线程处于此状态。TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。TERMINATED
已退出的线程处于此状态。
Thread.State state = thread.getState();
死亡之后的线程不能启动
线程的优先级(PRIORITY)
线程优先级1~10
public class TestPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"----------->"+Thread.currentThread().getPriority());
}
public static void main(String[] args) {
TestPriority testPriority = new TestPriority();
System.out.println(Thread.currentThread().getName()+"----------->"+Thread.currentThread().getPriority());
Thread a = new Thread(testPriority);
Thread b = new Thread(testPriority);
Thread c = new Thread(testPriority);
//设置优先级,再启动
a.setPriority(3);
a.start();
b.setPriority(Thread.MAX_PRIORITY);
b.start();
c.setPriority(Thread.MIN_PRIORITY);
c.start();
}
}
优先级低只意味着获得调度的概率低,并不是优先级低就不会被调用了。这都是蓝CPU的调度
守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如:后台记录操作日志,垃圾回收等等。。。
//守护线程
public class TestGuard {
public static void main(String[] args) {
You you = new You();
God god = new God();
Thread b = new Thread(god);
//默认是false,true之后为守护线程
b.setDaemon(true);
b.start();
new Thread(you).start();
}
}
//用户线程
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("用户线程正在执行");
}
System.out.println("gameover");
}
}
//守护线程
class God implements Runnable{
@Override
public void run() {
while(true){
System.out.println("我是守护线程,守护着你");
}
}
}
线程同步(synchronized)
并发:多个线程操作同一个资源
三大不安全实例
1.买票问题
public class UnsafeBuyTicket implements Runnable{
private int ticket = 10;
@Override
public void run() {
while(true){
if(ticket<=0){
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买了"+ticket--);
}
}
public static void main(String[] args) {
UnsafeBuyTicket unsafeBuyTicket = new UnsafeBuyTicket();
new Thread(unsafeBuyTicket,"A").start();
new Thread(unsafeBuyTicket,"B").start();
new Thread(unsafeBuyTicket,"C").start();
}
}
**解决办法:不会~但是应该synchronized解决此类问题 **
可以使用lock,查看下面Lock(ReentrantLock)
2.银行问题
public class UnsafeBank {
public static void main(String[] args) {
Acount acount = new Acount(100,"wzf");
Bank ba = new Bank(acount,70,"ba");
Bank ma = new Bank(acount,70,"ma");
ba.start();
ma.start();
}
}
//账户
class Acount{
int money;//余额
String name;//卡名
public Acount(int money, String name) {
this.money = money;
this.name = name;
}
}
class Bank extends Thread{
Acount acount;
int quqian;//取多少钱
int nowqian;//手里多少钱
public Bank(Acount acount, int quqian, String name) {
super(name);
this.acount = acount;
this.quqian = quqian;
}
@Override
public void run() {
//判断有没有钱
if(acount.money - quqian<0){
System.out.println("余额不足");
return;
}
try {
//扩大事情的发生性
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
acount.money = acount.money - quqian;
nowqian = nowqian + quqian;
System.out.println(acount.name+"余额为"+acount.money);
System.out.println(this.getName()+"手上有:"+nowqian);
}
解决办法:synchronized 代码块
synchronized(acount){}//锁住增删改查的对象
3.集合问题
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 100; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}
}
解决办法:synchronized 代码块
synchronized(list){}//锁住增删改查的对象
JUC下的CopyOnWriteArryList也行;
同步代码块会引起死锁
死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
产生死锁的原因:
(1) 因为系统资源不足。
(2) 进程运行推进的顺序不合适。
(3) 资源分配不当等。
产生死锁的四个必要条件:
-
互斥条件:一个资源每次只能被一个进程使用。
-
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
-
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺
-
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {
public static void main(String[] args) {
MakeUp aaa = new MakeUp(0,"A");
MakeUp bbb = new MakeUp(2,"B");
new Thread(aaa).start();
new Thread(bbb).start();
}
}
//镜子
class Mirror{
}
//口红
class Lipstick{
}
class MakeUp implements Runnable{
static Mirror mirror = new Mirror();
static Lipstick lipstick = new Lipstick();
int choose;
String name;
public MakeUp(int choose, String name) {
this.choose = choose;
this.name = name;
}
private void makeup(){
if(choose==0){
synchronized (mirror){
System.out.println(Thread.currentThread().getName()+"锁住了mirror");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lipstick){
System.out.println(Thread.currentThread().getName()+"一秒后锁住了lipstick");
}
}
}else {
synchronized (lipstick){
System.out.println(Thread.currentThread().getName()+"锁住了lipstick");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (mirror){
System.out.println(Thread.currentThread().getName()+"一秒后锁住了mirror");
}
}
}
}
@Override
public void run() {
makeup();
}
}
Lock(ReentrantLock)
public class BuyTicket implements Runnable{
private int ticket = 10;
//定义锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
lock.lock();
try {
if(ticket<=0){
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买了"+ticket--);
}
finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"A").start();
new Thread(buyTicket,"B").start();
new Thread(buyTicket,"C").start();
}
}
线程通信
生产者和消费者
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
管程法
设置缓冲区Refrigerator
//管程法------------->利用缓冲区解决
//生产者,消费者,产品,缓冲区
public class Tube {
public static void main(String[] args) {
Refrigerator refrigerator = new Refrigerator();
Producer producer = new Producer(refrigerator);
Consumer consumer = new Consumer(refrigerator);
new Thread(producer).start();
new Thread(consumer).start();
}
}
//生产者
class Producer implements Runnable{
Refrigerator refrigerator;
public Producer(Refrigerator refrigerator) {
this.refrigerator = refrigerator;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
refrigerator.put(new Chicken(i));
System.out.println("生产了第------》"+i);
}
}
}
//消费者
class Consumer implements Runnable{
Refrigerator refrigerator;
public Consumer(Refrigerator refrigerator) {
this.refrigerator = refrigerator;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了第------------------------------》"+refrigerator.out().number);
}
}
}
//产品
class Chicken{
//标识第几只
int number;
public Chicken(int number) {
this.number = number;
}
}
//缓冲区
class Refrigerator{
Chicken[] chickens = new Chicken[10];
//计数器
int count = 0;
//放入
synchronized void put(Chicken chicken){
if(count==10){
//容器满了等待消费者西消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
chickens[count] = chicken;
count++;
this.notify();
}
//拿出
synchronized Chicken out(){
if(count==0){
//容器空了,通知生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
Chicken chicken = chickens[count];
this.notify();
return chicken;
}
}
信号灯法
设置标志位:flag
public class Signal {
public static void main(String[] args) {
Chicken1 chicken1 = new Chicken1();
Consumer1 consumer1 = new Consumer1(chicken1);
Producer1 producer1 = new Producer1(chicken1);
new Thread(producer1).start();
new Thread(consumer1).start();
}
}
//生产者
class Producer1 implements Runnable{
Chicken1 chicken1;
public Producer1(Chicken1 chicken1) {
this.chicken1 = chicken1;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(i%2==0){
this.chicken1.put(1);
}
else{
this.chicken1.put(2);
}
}
}
}
//消费者
class Consumer1 implements Runnable{
Chicken1 chicken1;
public Consumer1(Chicken1 chicken1) {
this.chicken1 = chicken1;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
this.chicken1.out();
}
}
}
//产品
class Chicken1{
//标识符
boolean flag = true;
//标识第几只
int number;
//放入
synchronized void put(int j){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产-----》"+j);
this.number = j;
this.notifyAll();
this.flag = !this.flag;
}
//拿出
synchronized void out(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费--------------------------------------》"+number);
this.notifyAll();
this.flag = !this.flag;
}
}
高级主题
线程池
一、一句话说明白shutdown和shutdownNow的区别
shutdown只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断。
而shutdownNow则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回。
文章参考:[线程池中shutdown()和shutdownNow()方法的区别](https://www.cnblogs.com/aspirant/p/10265863.html)
线程池例子:
public class TestPool {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
日记
2020.9.9 上午吃了一个鸡蛋饼去上大数据技术,下午上机温习了Linux的命令,晚上
写了大数据的作业,但是对于Shell脚本完成java环境配置不会,有机会要学习一下,一天就这样茫然的过去了~
2020.9.10 今天是教师节,回忆起以前老师,感慨万千,吃完早餐,和室友去修复宽带,然后看了十几集多线程,优化了许多内容无聊时还和室友打了两把游戏队友太菜了~