8_3、Java基本语法之线程的生命周期与同步

news2024/11/16 11:49:03

一、线程的生命周期

        JDK中用Thread.State类定义了线程的几种状态 要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类 及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:

        新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。

        就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源。

        运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能。

        阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态。

        死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束。

二、线程同步

        1、线程同步的引入问题:三个窗口同时售卖总共100张火车票。

        2、问题的发现:使用继承Thread类的方式与使用实现Runnable接口的方式都会出现错票、重票等问题。

①、使用继承Thread类的方式

class Window extends Thread{

    private static int ticket = 100;

    @Override
    public void run() {

        while(true){
            if(ticket > 0){
                System.out.println(getName() + "卖出票,剩余:" + ticket);
                ticket--;
            }else{
                return;
            }
        }

    }
}


public class WindowTest {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();

        w1.setName("窗口一");
        w2.setName("窗口二");
        w3.setName("窗口三");

        w1.start();
        w2.start();
        w3.start();
    }
}

此时,运行程序会发现出现了重票、错票等问题。

②、使用实现Runnable接口的方式

class Window1 implements Runnable{
    private  int ticket = 100;
    @Override
    public void run() {
        while(true){
            if(ticket > 0){
                System.out.println(Thread.currentThread().getName() + "卖出一张票,票号为:" + ticket + "张票");
                ticket--;
            }else{
                break;
            }
        }
    }
}


public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w1 = new Window1();

        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        t1.start();
        t2.start();
        t3.start();

    }
}

 此时,运行程序会发现出现了重票、错票等问题。

 3、总结:

①.多线程出现了安全问题

②. 问题的原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

③. 解决办法: 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

4、Java的SDK提供了同步机制解决以上线程不安全的办法,分别为:

①同步代码块

synchronized (对象){

        // 需要被同步的代码;

}

        以三个窗口卖票问题为例使用同步代码块分别解决使用继承Thread类与实现Runnable接口方式的线程不安全问题:

a.使用继承Thread类

class Window2 extends Thread{

    private static int ticket = 100;
    private static Object obj = new Object();

    @Override
    public void run() {

        while(true){
            //正确的
//            synchronized (obj){
            synchronized (Window2.class){//Class clazz = Window.class; Window.class表示当前类,只加载一次
            //错误的:因为此时this代表t1、t2、t3
//          synchronized(this){
                if(ticket > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(getName() + "卖出票,剩余:" + ticket);
                    ticket--;
                }else{
                    return;
                }
            }
        }

    }
}


public class WindowTest2 {
    public static void main(String[] args) {
        Window2 w1 = new Window2();
        Window2 w2 = new Window2();
        Window2 w3 = new Window2();

        w1.setName("窗口一");
        w2.setName("窗口二");
        w3.setName("窗口三");

        w1.start();
        w2.start();
        w3.start();
    }
}

运行结果:不会出现错票、重票

 补充:通过继承Thread类来解决线程安全问题时,可以考虑使用任何一个类的对象(Dog dog = new Dog())或者线程类本身(Window2.class)来充当同步监视器。

b、实现Runnable接口方式

class Window1 implements Runnable {
    private int ticket = 100;
//    Object obj = new Object();
//    Dog dog = new Dog();


    @Override
    public void run() {
//        Object obj = new Object();
        while (true) {
            synchronized(this){
            //synchronized (dog) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,票号为:" + ticket + "张票");
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}


public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w1 = new Window1();

        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        t1.start();
        t2.start();
        t3.start();

    }
}

运行结果:不会出现错票、重票

补充:通过实现Runnable接口来解决线程安全问题时,可以考虑使用this来充当同步监视器。

②同步方法:synchronized还可以放在方法声明中,表示整个方法为同步方法。如果被操作的共享数据全都声明在一个方法中,那么不妨将这个方法声明为同步方法。

public synchronized void show (String name){

        ….

}

        以三个窗口卖票问题为例使用同步方法分别解决使用继承Thread类与实现Runnable接口方式的线程不安全问题:

a.使用继承Thread类

class Window4 extends Thread{

    private static int ticket = 100;

    @Override
    public void run() {

        while(true){
            show();
        }
    }

    private static synchronized void show(){//此时同步监视器是:Window4.class
//    private synchronized void show(){//当前同步监视器:this。代表t1、t2、t3,此时是错误的
        if(ticket > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "卖出票,剩余:" + ticket);
            ticket--;
        }
    }

}



public class WindowTest4 {
    public static void main(String[] args) {
        Window4 w1 = new Window4();
        Window4 w2 = new Window4();
        Window4 w3 = new Window4();

        w1.setName("窗口一");
        w2.setName("窗口二");
        w3.setName("窗口三");

        w1.start();
        w2.start();
        w3.start();
    }
}

运行结果:不会出现错票、重票

 b、实现Runnable接口方式

class Window3 implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            show();
        }

    }

    public synchronized void show(){//this

            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "卖出一张票,票号为:" + ticket + "张票");
                ticket--;
            }
    }
}


public class WindowTest3 {
    public static void main(String[] args) {
        Window3 w1 = new Window3();

        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }
}

运行结果:不会出现错票、重票

注意:必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就 无法保证共享资源的安全

③锁:Lock——>jdk5.0新增特性。java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象 加锁,线程开始访问共享资源之前应先获得Lock对象。ReentrantLock类实现了Lock,它拥有synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

class A{

        private final ReentrantLock lock = new ReenTrantLock();

        public void m(){

                 lock.lock();

                try{

                        //保证线程安全的代码;

                } finally{

                        lock.unlock();

                }

        }

}

使用Lock的方式解决Runnable实现类的线程安全问题.

class Window implements Runnable{
    private int ticket = 100;
    //1.创建ReentrantLock类的对象
    private ReentrantLock lock = new ReentrantLock();


    @Override
    public void run() {
        while(true){
            try {
                //调用锁定方法lock()
                lock.lock();
                if(ticket > 0){

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                    System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }
            finally {
                //调用解锁方法unlock()
                lock.unlock();
            }
        }
    }
}


public class LockTest {
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("线程1");
        t2.setName("线程2");
        t3.setName("线程3");

        t1.start();
        t2.start();
        t3.start();
    }
}

注意:如果同步代码有异常,要将unlock()写入finally语句块。

 

面试题:synchronized 与Lock的异同?

相同:二者都可以解决线程安全问题。

不同:synchronized机制,在执行完响相应的同步代码后,会自动的释放同步监视器。 Lock锁需要手动的启动同步(lock()),当相应的同步代码执行后,也需要手动的结束(unlock())。

总结:优先使用顺序:  Lock——>同步代码块(已经进入了方法体,分配了相应资源)——>同步方法(在方法体之外)

多线程扩展:

a、线程安全的单例模式(懒汉式):要求懒汉式与饿汉式会手写

class Bank{
    private Bank(){}

    private static Bank instance = null;

    public static Bank getInstance(){
        //方式1:效率稍差
//        synchronized (Bank.class) {
//            if(instance == null){
//                instance = new Bank();
//            }
//            return instance;
//        }
//    }
        //方式2:效率稍高
        if(instance == null) {
            synchronized (Bank.class) {
                if (instance == null) {
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

b、线程的死锁问题:

        死锁 :不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁。出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续。

class A {
	public synchronized void foo(B b) {
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 进入了A实例的foo方法"); // ①
		try {
			Thread.sleep(200);
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 企图调用B实例的last方法"); // ③
		b.last();
	}

	public synchronized void last() {
		System.out.println("进入了A类的last方法内部");
	}
}

class B {
	public synchronized void bar(A a) {
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 进入了B实例的bar方法"); // ②
		try {
			Thread.sleep(200);
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 企图调用A实例的last方法"); // ④
		a.last();
	}

	public synchronized void last() {
		System.out.println("进入了B类的last方法内部");
	}
}

public class DeadLock implements Runnable {
	A a = new A();
	B b = new B();

	public void init() {
		Thread.currentThread().setName("主线程");
		// 调用a对象的foo方法
		a.foo(b);
		System.out.println("进入了主线程之后");
	}

	public void run() {
		Thread.currentThread().setName("副线程");
		// 调用b对象的bar方法
		b.bar(a);
		System.out.println("进入了副线程之后");
	}

	public static void main(String[] args) {
		DeadLock dl = new DeadLock();
		new Thread(dl).start();
		dl.init();
	}
}

运行中(无法主动结束):

解决方法 ①专门的算法、原则②尽量减少同步资源的定义 ③尽量避免嵌套同步 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/70968.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于ARIMA、SVM、随机森林销售的时间序列预测

如今DT(数据技术)时代,数据变得越来越重要,其核心应用“预测”也成为互联网行业以及产业变革的重要力量。最近我们被客户要求撰写关于销售时间序列预测的研究报告,包括一些图形和统计输出。对于零售行业来说&#xff0…

Elsevier(爱思唯尔)LaTex 模板详细说明

Elsevier 模板的使用 官方网站提供的 Latex Instructions,Elsevier 模板下载地址:elsarticle-template.zip [ 如果不了解文档类的作用,可以参考:documentclass ] Elsevier 提供了 3 种自定义的文档类: elsarticle…

大厂软件测试流程完整版

目 1.概述 1.1目的 有效的保证软件质量; 有效的制定不同测试类型(软件系统测试、音频主观性测试、Field Trial、专项测试、自动化测试、性能测试、用户体验测试)的软件测试计划; 按照计划进行测试,发现软件中存在…

Session | web应用的session机制、session的实现原理

目录 一:web应用的session机制 二:session的实现原理 一:web应用的session机制 (1)什么是会话? ①会话对应的英语单词:session ②用户打开浏览器,进行一系列操作,然后…

[附源码]计算机毕业设计楼盘销售管理系统Springboot程序

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

session,cookie,token详解

session,cookie,token详解 1.session 1.1 session的作用是什么 session的作用是用于保存每个用户的专用信息;当用户访问时,服务器都会为每个用户分配唯一的Session ID,而且当访问其他程序时可以从用户的session中取出该用户的数据为用户服务。…

将 Cpar 文件导入 2019 版的 Carsim 后,无法打开 video+plot 是什么问题?

大家在进行联合仿真的过程中,首先要将你的 Carsim 右上角的锁打开! 解锁之后要明确仿真动画(video)和图像(plot)只有在联合仿真运行完了之后才会有,这个时候需要点击 Simulink 模型界面那个绿色…

Elasticsearch 基本操作

👉 Elasticsearch 基本操作 💎 1  RESTful REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。Web 应用程序最重要的 REST 原则是,客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器…

基于改进量子粒子群算法的电力系统经济调度(Matlab代码实现)

💥💥💥💞💞💞欢迎来到本博客❤️❤️❤️💥💥💥 🎉作者研究:🏅🏅🏅本科计算机专业,研究生电气学硕…

【CUDA学习笔记】OneFlow公众号CUDA算子优化文章学习笔记

1 CUDA学习资料合集 【OneFlow】岁末年初,为你打包了一份技术合订本 2 GPU概念介绍 《GPU的硬件结构与执行原理 —— 开源100天,OneFlow送上“百天大礼包”:深度学习框架如何进行性能优化 》 2.1 内存模型 2.1.1 Bank介绍 《GPU硬件结构…

微信小程序开发笔记 进阶篇④——getPhoneNumber 获取用户手机号码(小程序云)

文章目录一、前言二、前端代码wxml三、前端代码js四、云函数五、程序流程一、前言 大部分微信小程序开发者都会有这样的需求:获取小程序用户的手机号码。但是,因为小程序用户的手机号码属于重要信息,为了安全,所以需要如下一系列较…

transforms的二十二个方法(transforms用法非常详细)

变换是常见的图像变换,其可以适应连接在一起的ComposeComposeCompose, 此外,还有torchvision.transforms.functionaltorchvision.transforms.functionaltorchvision.transforms.functional模块,功能转换可以对转换进行细粒度控制,…

Redis - 数据概念与操作

1.Redis数据类型 Redis存储的是key-value结构的数据,其中key是字符串类型,value有5种数据类型: (1)字符串 string 如:“hello,world” (2)列表 list 如:a b c d a &…

【Jmeter】接口测试工具常用配置

目录 一、简介 二、安装和配置 三、Jmeter常用组件 四、编写一个HTTP接口脚本 五、断言 一、简介 JMeter,一个100%的纯Java桌面应用,由Apache组织的开放源代码项目,它是接口功能、自动化、性 能测试的工具。具有高可扩展性、…

用于NLP的Python:使用Keras的多标签文本LSTM神经网络分类

介绍 在本文中,我们将看到如何开发具有多个输出的文本分类模型。我们开发一个文本分类模型,该模型可分析文本注释并预测与该注释关联的多个标签。 最近我们被客户要求撰写关于NLP的研究报告,包括一些图形和统计输出。多标签分类问题实际上是…

SPP-24《区块链技术及应用报告》

本文根据中科院计算所研究员孙毅博士的报告,总结区块链的基本原理、认识误区、技术挑战及应用场景。时间:2022-12-07 在2022十四五数字经济发展规划中,区块链列入重点发展规划。在数据为王的时代,谁拥有了数据,谁就拥有…

【网络安全工程师】从零基础到进阶,看这一篇就够了

学前感言 1.这是一条需要坚持的道路,如果你只有三分钟的热情那么可以放弃往下看了。 2.多练多想,不要离开了教程什么都不会,最好看完教程自己独立完成技术方面的开发。 3.有问题多google,baidu…我们往往都遇不到好心的大神,谁…

深度学习中常见问题及知识点补充(持续更新中)

1. 问题描述 出现原因:tensorflow版本与keras版本不对应 (图片是取自一位叫皮肤科大白的博主)如果两个版本不对应就会出现上述问题 解决办法:查找自己tensorflow的版本号,根据tensorflow版本安装对应版本的keras #…

深聊性能测试,从入门到放弃之: Windows系统性能监控(二) 资源监控器介绍及使用。

资源监控器介绍及使用1、引言2、资源监视器2.1 打开方式2.2 基本介绍2.3 使用3、总结1、引言 小屌丝:鱼哥,我看了你这篇《Windows系统性能监控(一) 性能监视器介绍及使用》,让我学到了好多知识。 小鱼:嗯,我自己在写这…

关于kunit的二点够用就行知识概念

前面我们写过一篇关于Kunit怎么快速使用起来的文章,但是当时只是搭建了框架,让整个KUNIT跑起来了。使用到的关于KUNIT中的东西还是比较的少。现在这次我们去测试一些复杂的场景,使用到一些复杂的断言。继续我们的二点点KUNIT,学习…