详解Java多线程(超级爆肝)


前言

如果一次只完成一件事情,会很容易实现,但在现实生活中很多事情都是同时进行的,所以在Java中为了模拟这种常态,引入了线程机制。简单来说,当程序同时完成很多事情时,就是所谓的多线程程序。 随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。 那么话不多说,进入正题,今天小伙子将记录自己线程的学习。

提示:以下是本篇文章正文内容,下面案例可供参考

1、线程简介

1.1、线程与进程

程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。-------生命周期
线程:进程可进一步细化为线程,是一个程序内部的一条执行路径

1.2、同步与异步

同步:就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事.就像早上起床后,先洗涮,然后才能吃饭,不能在洗涮没有完成时,就开始吃饭.

异步:概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

1.3、并行与并发

并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。比如:多个人同时做不同的事

并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。比如:一个CPU(采用时间片)同时执行多个任务,比如秒杀平台,多个人做同件事



2、实现线程的两种方式

2.1、继承Thread类

思路:

1.创建一个继承于Thread类的子类 (通过ctrl+o(override)输入run查找run方法)
2.覆写(重写)Thread类的run()方法
3.创建Thread子类(线程类)的对象
4.通过此对象调用start()方法(启动之后会自动调用重写的run方法执行线程)
继承Thread类创建一个新的线程语法如下:

public class MyThread extends Thread {

}

start与run方法的区别:
start方法的作用:1.启动当前线程 2.调用当前线程的重写的run方法(在主线程中生成子线程,有两条线程)
调用start方法以后,一条路径代表一个线程,同时执行两线程时,因为时间片的轮换,所以执行过程随机分配,且一个线程对象只能调用一次start方法。
run方法的作用:在主线程中调用以后,直接在主线程一条线程中执行了该线程中run的方法。(调用线程中的run方法,只调用run方法,并不新开线程)

总结:我们不能通过run方法来新开一个线程,只能调用线程中重写的run方法(可以在线程中不断的调用run方法,但是不能开启子线程,即不能同时干几件事),start是开启线程,再调用方法(即默认开启一次线程,调用一次run方法,可以同时执行几件事)

2.2、实现Runnable接口

因为Java语言中不支持多继承,这时候就需要实现Runnable接口使其具有使用线程的功能。

思路步骤

1、建立Runnable对象
2、使用参数为Runnable对象的构造方法创建Tread实例
3、调用start()方法启动线程

实现Runnable接口的语法如下:

public class MyThread extends Thread implements Runnable {

}

比较创建线程的两种方式:
开发中,优先选择实现Runable接口的方式

原因
1:实现的方式没有类的单继承性的局限性
2:实现的方式更适合用来处理多个线程有共享数据的情况
联系:Thread也是实现自Runable,两种方式都需要重写run()方法,将线程要执行的 逻辑声明在run中


3、线程的生命周期

概述:
线程具有生命周期,其中包含5种状态: 出生状态、就绪状态、运行状态、暂停状态(包括休眠、等待和阻塞等)以及死亡状态。
出生状态就是线程被创建时处于的状态,在用户使用该线程实例调用start()方法之前,线程都处于出生状态;当用户调用start()方法后,线程处于就绪状态(又被称为可执行状态);当线程得到系统资源后就进入运行状态。
一旦进入运行状态,它就会在就绪和运行状态下转换,同时也有可能进入暂停或者死亡状态。当处于运行状态下的线程调用 sleep()方法,wait()方或者阻塞解除时,会进入暂停状态;当休眠结束、调用notify()方法、notifyAll()方法或者阻塞解除时,会进入就绪状态;当线程的run()方法执行完毕,或者线程发生错误、异常时,线程进入死亡状态。
在这里插入图片描述

3.1、操作线程的方法

3.1.1、线程的休眠

sleep()方法的使用:

try{
      Thread.sleep(1000);
} catch (InterruptedException e) {
      e.printStackTrace();
}

3.1.2、线程的加入

join()方法的使用:
当某个线程使用join()方法加入另外一个线程时,另一个线程会等待该线程执行完毕后再继续执行。

3.1.3、线程的中断

以往有的时候会使用stop()方法停止线程,但JDK早已废除了stop()方法,不建议使用stop()方法来 停止一个线程的运行。现在提倡在run()方法中使用无限循环的形式,然后使用一个布尔类型标记控制循环的停止。
例如,创建一个InterruptedTest类,该类实现了Runnable接口,并设置线程正确的停止方式,

public class InterrupedTest implements Runnable{
        private boolean isContinue = false;    //设置一个标记变量,默认值为false
        @Override
        public void run() {
            while(true){
                //...
                if(isContinue){
                    break;
                }
            }
        }
        public void setContinue(){
            this.isContinue = true;
        }
    }


4、线程的同步

4.1、线程安全

什么是线程安全问题呢?
线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

上述例子中:创建三个窗口卖票,总票数为100张票
1.卖票过程中,出现了重票(票被反复的卖出,ticket未被减少时就打印出了)错票。
2.问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进 来,也来操作车票。(将此过程的代码看作一个区域,当有线程进去时,装锁,不让别的线程进去)
生动理解的例子:有一个厕所,有人进去了,但是没有上锁,于是别人不知道你进去了,别人也进去了对厕所也使用造成错误。
3.如何解决:当一个线程在操作ticket时,其他线程不能参与进来,直到此线程的生命周期结束
4.在java中,我们通过同步机制,来解决线程的安全问题。


4.2、线程同步机制

方式一: 同步代码块

使用关键字Synchronized同步监视器(锁)

Synchronized(object){
//需要被同步的代码
}

public static void main(String[] args) {
        //线程不安全
        //解决方案1:同步代码块

        //Object o = new Object();
        Runnable run = new Ticket();

        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();

    }
    static class Ticket implements Runnable{
        private  int count = 10;
        private Object o = new Object();
        @Override
        public void run() {
            while (true){
                synchronized(o){
                    if(count > 0){
                        System.out.println("正在准备卖票!");
                        count--;
                        System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
                        try{
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        break;
                    }
                }
            }
        }
    }

注意:
操作共享数据的代码(所有线程共享的数据的操作的代码)(视作卫生间区域(所有人共享的厕所)),即为需要共享的代码(同步代码块,在同步代码块中,相当于是一个单线程,效率低)
共享数据:多个线程共同操作的数据,比如公共厕所就类比共享数据
同步监视器(俗称:锁):任何一个的对象都可以充当锁。(但是为了可读性一般设置英文成lock)当锁住以后只能有一个线程能进去(要求:多个线程必须要共用同一把锁,比如火车上的厕所,同一个标志表示有人)
Runable天生共享锁,而Thread中需要用static对象或者this关键字或者当前类(window。class)来充当唯一锁


方式二: 同步方法

使用同步方法,对方法进行synchronized关键字修饰
将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。
对于runnable接口实现多线程,只需要将同步方法用synchronized修饰
而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一)

 public static void main(String[] args) {
        //线程不安全
        //解决方案2:同步方法

        //Object o = new Object();
        Runnable run = new demo2.Ticket();

        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();

    }
    static class Ticket implements Runnable{
        private  int count = 10;

        @Override
        public void run() {
            while (true){
                boolean flag = sale();
                if(!flag){
                    break;
                }
            }
        }
        public synchronized boolean sale(){
            if(count > 0){
                System.out.println("正在准备卖票!");
                count--;
                System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
                try{
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return true;
            }
            return false;
        }
    }

总结

1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。
2.非静态的同步方法,同步监视器是this静态的同步方法,同步监视器是当前类本身。继承自Thread class

方式三: 显示锁 Lock类 ReentrantLock子类

public static void main(String[] args) {
        //线程不安全
        //解决方案3:显示锁 Lock类  ReentrantLock子类
        //同步代码块与同步方法都是隐式锁

        Runnable run = new demo2.Ticket();

        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();

    }
    static class Ticket implements Runnable{
        private  int count = 10;
        private Lock l = new ReentrantLock();
        @Override
        public void run() {
            while (true){
                l.lock();
                    if(count > 0){
                        System.out.println("正在准备卖票!");
                        count--;
                        System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
                        try{
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        break;
                    }
                    l.unlock();
                }
            }
        }

总结:Synchronized与lock的异同?

相同:二者都可以用来解决线程安全问题
不同:synchronized机制在执行完相应的代码逻辑以后,自动 的释放同步监视器
lock 需要手动 的启动同步(lock()),同时结束同步也需要手动的实现(unlock())(同时以为着lock的方式更为灵活)

优先使用顺序:
LOCK -->> 同步代码块 -->> 同步方法
遇到问题解决步骤:

判断线程是否有安全问题,以及如何解决:
1.先判断是否多线程
2.再判断是否有共享数据
3.是否并发的对共享数据进行操作
4.选择上述三种方法解决线程安全问题


5、线程的死锁问题

线程死锁的理解:僵持,谁都不放手,一双筷子,我一只你一只,都等对方放手(死锁,两者都进入阻塞,谁都吃不了饭,进行不了下面吃饭的操作)
出现死锁以后,不会出现提示,只是所有线程都处于阻塞状态,无法继续

public class demo {
    public static void main(String[] args) {
        Police p = new Police();
        Culrite c = new Culrite();

        new MyThread(p,c).start();
        p.say(c);
    }
    static class MyThread extends Thread{
        private Police p;
        private Culrite c;
        public MyThread(Police p, Culrite c){
            this.p = p;
            this.c = c;
        }

        @Override
        public void run() {
            c.say(p);
        }
    }
    static class Culrite{
        public synchronized void say(Police p){
            System.out.println("罪犯:你放了我,我放人质");
            p.react();
        }
        public synchronized void react(){
            System.out.println("罪犯被放走了,人质也被放了");
        }
    }
    static class Police{
        public synchronized void say(Culrite c){
            System.out.println("警察:你放了人质,我放你走");
            c.react();
        }
        public synchronized void react(){
            System.out.println("人质被救了,但罪犯逃走了");
        }
    }

代码运行如下(执行不下去了(苦笑)):
在这里插入图片描述

如何解决死锁问题

1.减少同步共享变量
2.采用专门的算法,多个线程之间规定先后执行的顺序,规避死锁问题
3.减少锁的嵌套。

6、线程的通信

6.1、常用通信方法

通信方法描述
wait()一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程,就唤醒优先级高的线程
notifyAll一旦执行此方法,就会唤醒所有被wait()的线程

特别提醒:这三个方法均只能使用在同步代码块或者同步方法中。

sleep和wait的异同:

相同点一旦执行方法以后,都会使得当前的进程进入阻塞状态
不同点1.两个方法声明的位置不同,Thread类中声明sleep,Object类中声明wait。
2.调用的要求不同,sleep可以在任何需要的场景下调用,wait必须使用在同步代码块或者同步方法中
3.关于是否释放同步监视器,如果两个方法都使用在同步代码块或同步方法中,sleep不会释放,wait会释放**

经典例题:生产者/消费者问题:

生产者(Priductor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如20个),如果生产者视图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产:如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

这里可能出现两个问题:
生产者比消费者快的时候,消费者会漏掉一些数据没有收到。
消费者比生产者快时,消费者会去相同的数据。

//交替实现,保证线程安全,
// 经典例题:生产者(厨师)与消费者(服务员)问题
public class demo {
    public static void main(String[] args) {
        Food f = new Food();
        new Cook(f).start();
        new Waiter(f).start();

    }
    //厨师
    static class Cook extends Thread{
        private Food f;
        public Cook(Food f) {
            this.f = f;
        }

        @Override
        public void run() {
            for(int i=0; i<100;i++){
                if(i%2==0){
                    f.setNameTaste("老干妈小米粥","香辣味");
                }else{
                    f.setNameTaste("煎饼果子","甜辣味");
                }
            }
        }
    }

    //服务生
    static class Waiter extends Thread{
        private Food f;
        public Waiter(Food f) {
            this.f = f;
        }

        @Override
        public void run() {
            for(int i=0; i<100;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                f.get();
            }
        }
    }

    //食物
    static class Food{
        private String name;
        private String taste;
        private boolean flag = true;

        public synchronized void setNameTaste(String name, String taste) {
            if(flag){
                this.name = name;
                this.taste = taste;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                flag = false;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public synchronized void get(){
            if(!flag){
                System.out.println("服务生端走的菜的名称是:"+name+",味道是:"+taste);
            }
            flag = true;
            this.notifyAll();
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}



总结

以上就是今天要讲的内容,本文简单介绍了多线程的一部分内容。学习多线程编程就像进入了一个全新的领域,它与以往的编程思想截然不同,初学者应该积极转换编程思维,进入多线程编程的思维模式。 好了,今天就到这里吧
weixin073智慧旅游平台开发微信小程序+ssm后端毕业源码案例设计 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
python017基于Python贫困生资助管理系统带vue前后端分离毕业源码案例设计 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
weixin102旅游社交微信小程序+ssm后端毕业源码案例设计 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值