首页 > 编程语言 >java多线程(超详细讲解)下篇

java多线程(超详细讲解)下篇

时间:2024-03-23 18:33:20浏览次数:24  
标签:count 水果 下篇 Thread storage 线程 new java 多线程

本章继续讲多线程

目录

一、线程同步

当多个线程共享数据时,由于CPU负责线程的调度,所以程序无法精确地控制多线程的交替次序。如果没有特殊控制,则多线程对共享数据的修改和访问将导致数据的不一致。

1、为什么需要线程同步

上一章学习的线程都是独立且异步运行的,也就是说每个线程都包含了运行时所需要的数据或方法,不必关心其他线程的状态和行为。但是经常会有一些同时运行的线程需要操作共同数据,此时就要考虑其他线程的状态和行为;否则,不能保证程序运行结果的正确性。

示例:

public class Storage {
    public final int MAX_COUNT = 3;//最大库存三车

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    private int count = 0;//库存量

    //对外向果商发货
    public void get() {
        if (count > 0) {
            count--;//1.修改数据
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //2.显示数据
            System.out.println(Thread.currentThread().getName() + ":采购了第" + (MAX_COUNT - count) + "车水果");
        }
    }

    //果园向仓库供货
    public void put() {
        if (count == 0) {
            count = MAX_COUNT;//1.修改数据
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + ":已向仓库发送" + count + "车水果");//显示数据
        }
    }
}

class BusiThread implements Runnable {
    Storage storage;

    public BusiThread(Storage storage) {
        this.storage = storage;
    }

    public void run() {
        while (true) {
            this.storage.get();//仓库供应一车水果
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

class FarmerThread implements Runnable {
    Storage storage;

    public FarmerThread(Storage storage) {
        this.storage = storage;
    }
    public void run(){
        while (true) {
            this.storage.put();//向仓库运送一车水果
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
class Test7{
    public static void main(String[] args) {
        Storage storage=new Storage();//创建仓库类对象
        Thread busi1=new Thread(new BusiThread(storage),"莲花超市");
        Thread busi2=new Thread(new BusiThread(storage),"果果香水果店");
        Thread farmer=new Thread(new FarmerThread(storage),"果农老王");
        farmer.start();
        busi1.start();
        busi2.start();
    }
}

在以上代码中,变量count存储现有仓库库存;get()方法实现当仓库库存大于零时,对外向果商或商超发货,每次发货一车;put()方法实现当仓库库存为零时,由果园的果农向仓库供货,每次供货三车。为了展示更多的数据方便观察效果,在示例代码中,FarmerThread类和BusiThread类中存在死循环。

运行结果:

果农老王:已向仓库发送1车水果
果果香水果店:采购了第2车水果
莲花超市:采购了第2车水果
莲花超市:采购了第0车水果
果农老王:已向仓库发送2车水果
莲花超市:采购了第1车水果
果果香水果店:采购了第1车水果
果果香水果店:采购了第0车水果
莲花超市:采购了第0车水果
果农老王:已向仓库发送1车水果
果果香水果店:采购了第2车水果
莲花超市:采购了第2车水果
莲花超市:采购了第1车水果
果果香水果店:采购了第2车水果
果农老王:已向仓库发送1车水果
莲花超市:采购了第3车水果
果果香水果店:采购了第0车水果

从运行结果可以发现,运行结果中存在如下数据问题:

  1. 不是从第一车水果开始供货。
  2. 出现果果香水果店和莲花超市共同采购同一车水果的情况。
  3. 果农老王应该运输三车水果到仓库,但数据显示只发送两车。

多个线程共同操作同一共享资源会带来数据不安全问题。具体原因是在Storage类中存在以下情况:

  1. 存在多个线程共同操作的变量count。
  2. 在get()方法和put()方法中,存在修改数据和显示数据两步操作。

二、如何实现线程同步

当两个或多个线程需要访问同一资源时,需要以某种顺序来确保资源某一时刻只能被一个线程使用,这被称为线程同步。线程同步相当于为线程中需要一次性完成不允许中断的操作加上一把锁,从而解决冲突。

1、同步代码块

代码块即使用“{}”括起来的一段代码,使用synchronized关键字修饰的代码块被称为同步代码块。

语法结构:

synchronized(obj){
	//需要同步的代码
}

示例:

package d10;

public class Storage2 {
    public final int MAX_COUNT = 3;//最大库存三车

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    private int count = 0;//库存量

    //对外向果商发货
    public void get() {
        synchronized (this) {
            if (count > 0) {
                count--;//1.修改数据
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //2.显示数据
                System.out.println(Thread.currentThread().getName() + ":采购了第" + (MAX_COUNT - count) + "车水果");
            }
        }
    }

    //果园向仓库供货
    public void put() {
        synchronized (this) {
            if (count == 0) {
                count = MAX_COUNT;//1.修改数据
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + ":已向仓库发送" + count + "车水果");//显示数据
            }
        }
    }
}

class BusiThread2 implements Runnable {
    Storage2 storage;

    public BusiThread2(Storage2 storage) {
        this.storage = storage;
    }

    public void run() {
        while (true) {
            this.storage.get();//仓库供应一车水果
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

class FarmerThread2 implements Runnable {
    Storage2 storage;

    public FarmerThread2(Storage2 storage) {
        this.storage = storage;
    }

    public void run() {
        while (true) {
            this.storage.put();//向仓库运送一车水果
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

class Test8 {
    public static void main(String[] args) {
        Storage2 storage = new Storage2();//创建仓库类对象
        Thread busi1 = new Thread(new BusiThread2(storage), "莲花超市");
        Thread busi2 = new Thread(new BusiThread2(storage), "果果香水果店");
        Thread farmer = new Thread(new FarmerThread2(storage), "果农老王");
        farmer.start();
        busi1.start();
        busi2.start();
    }
}

运行结果:

果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果

2、同步方法

如果一个方法的所有代码都属于需同步的代码,那么这个方法定义处可以直接使用synchronized关键字修饰,即同步方法。

语法结构:

package d10;

public class Storage3 {
    public final int MAX_COUNT = 3;//最大库存三车

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    private int count = 0;//库存量

    //对外向果商发货
    public synchronized void get() {
        if (count > 0) {
            count--;//1.修改数据
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //2.显示数据
            System.out.println(Thread.currentThread().getName() + ":采购了第" + (MAX_COUNT - count) + "车水果");
        }
    }

    //果园向仓库供货
    public synchronized void put() {
        if (count == 0) {
            count = MAX_COUNT;//1.修改数据
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + ":已向仓库发送" + count + "车水果");//显示数据
        }
    }
}

class BusiThread3 implements Runnable {
    Storage3 storage;

    public BusiThread3(Storage3 storage) {
        this.storage = storage;
    }

    public void run() {
        while (true) {
            this.storage.get();//仓库供应一车水果
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

class FarmerThread3 implements Runnable {
    Storage3 storage;

    public FarmerThread3(Storage3 storage) {
        this.storage = storage;
    }
    public void run(){
        while (true) {
            this.storage.put();//向仓库运送一车水果
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
class Test9{
    public static void main(String[] args) {
        Storage3 storage=new Storage3();//创建仓库类对象
        Thread busi1=new Thread(new BusiThread3(storage),"莲花超市");
        Thread busi2=new Thread(new BusiThread3(storage),"果果香水果店");
        Thread farmer=new Thread(new FarmerThread3(storage),"果农老王");
        farmer.start();
        busi1.start();
        busi2.start();
    }
}

运行结果:

果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果

3、线程同步特征

所谓线程之间保持同步,是指不同的线程在执行之间以同一个对象作为锁标记的同步代码块或同步方法时,因为要获得这个对象的锁而相互牵制,线程同步具有以下特征:

  1. 当多个并发线程访问同一对象的同步代码块或同步方法时,同一时刻只能有一个线程运行,其他线程必须等待当前线程运行完毕后才能运行。
  2. 如果多个线程访问的不是同一共享资源,则无需同步。
  3. 当一个线程访问Object对象的同步代码块或同步方法时,其他线程仍可以访问该Object对象的非同步代码块及非同步方法。
    综上所述,synchronized关键字就是为当前的代码块声明一把锁,获得这把锁的线程可以执行代码块里的指令,其他线程只能等待取锁,然后才能执行相同的操作。

三、线程安全的类型

若程序所在的进程中有多个线程,而当这些线程同时运行时,每次的运行结果和单线程的运行结果是一样的,而且其他变量的值夜和预期相同,那么当前程序就是线程安全的。
一个类在被读线程访问时,不管运行时对这些线程有怎样的时序安排,它必须是以固定的、一致的顺序执行,这样的类型被称为线程安全的类型。

1、ArrayList是常用的集合类型,它是否线程安全的呢?

答案是ArrayList是非线程安全的类型。
而ArrayList集合添加一个元素主要完成如下两步操作:

  1. 判断列表容量是否足够,是否需要扩容。
  2. 将元素添加到列表的元素数组里。
    以上两步操作并非不可分割,这样也就出现了导致线程不安全的隐患。在多个线程执行add()方法进行添加元素操作时,可能会导致elementData数组越界。

2、对比 Hashtable和HashMap

1、是否线程安全

Hashtable是线程安全的,其方法时同步的,可查看Hashtable类型源码中操作数据的方法为同步方法。
而HashMap中的方法在默认情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable。
如果使用HashMap,就要自行增加同步处理。

2、效率比较

由于Hashtable是线程安全的,其方法是同步的,而HashMap是非线程安全的,重速度,轻安全,所以当只需要单线程时,使用HashMap的执行速度要搞过Hashtable。

3、对比StringBuffer和StringBuilder

StringBuffer和StringBuilder都可用来存储字符串变量,是可变的对象。它们的区别是StringBuffer是线程安全的,而StringBuilder是非线程安全的。因此,在单线程环境下StringBuilder执行效率更高。

四、线程的状态转换

线程的状态转换就是利用了Thread常用方法,
在Java语言使用Thread类及其子类的对象表示线程,新建的线程通常会在五钟状态中转换,即新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡。
这里就不多讲了!!!

总结

多线程允许程序员编写出可最大程度利用CPU的高效程序。

  1. 在Java程序启动时,一个线程立刻运行,该线程通常被成我饿程序的主线程。主线程是产生其他子线程的线程。
  2. 通常,主线程必须最后完成运行,因为它执行各种关闭动作。

可通过两种方式创建线程。

  1. 声明一个继承了Thread类的子类,在此子类中,重写Thread类的run()方法。
  2. 声明一个实现Runnable接口的类,然后实现run()方法。

每一个线程均会处于新建、就绪、运行、阻塞、死亡五钟状态之一。
在Java实现的多线程应用程序中,可以通过调用Thread类中的方法实现对线程类对象的操作。

  1. 调整线程的优先级:在同等情况下,优先级高的线程会获得较多的运行机会,优先级低的线程则相反。Java线程优先级用1~10的整数表示。
  2. 线程休眠:sleep(long millis)方法使线程转到阻塞状态。
  3. 线程的强制运行:join()方法可以让某一线程强制运行。
  4. 线程礼让:yield()方法,暂停当前正在执行的线程类对象,把执行机会让给相同或更高优先级的线程。

当多个线程类对象操作同一共享资源时,要使用synchronized关键字进行资源的同步处理,可以使用同步代码块或同步方法实现线程同步。

标签:count,水果,下篇,Thread,storage,线程,new,java,多线程
From: https://blog.csdn.net/AE_BD/article/details/136886117

相关文章

  • Java 沉淀-2
    一维数组初始化:动态初始化:数组声明且为数组元素分配空间与赋值操作分开进行静态初始化:在定义数组的同时就为数组元素分配空间并赋值数组元素类型二维数组数组中的数组初始化注意特殊学法情况:int[]x,y[]:x是一维数组,y是二维数组多维数组不必都是规则矩阵形式十......
  • Java应用中的JDBC数据库连接完全指南
    1、简介1.1介绍JDBC连接数据库的重要性是Java平台中用于连接和操作数据库的标准API。它的重要性体现在以下几个方面跨平台性: JDBC允许Java应用程序与各种数据库进行通信,而无需关心底层数据库系统的具体细节实时连接: 通过JDBC,Java应用程序可以实时连接到数据库,从而实现动......
  • JAVA高级面向对象二:多态下的类型转换问题
     packagecom.itheima.多态;publicclassTest{//多态好处publicstaticvoidmain(String[]args){////好处1:实现解耦合,右边对象可以随时切换,后续业务随即改变//Peoplep1=newTeacher();//p1.run();////好处2:可以使用父......
  • JAVA非阻塞IO、异步IO(NIO、AIO)-摘自《netty权威指南》
    一、JAVANIO在介绍NIO编程之前,我们首先需要澄清一个概念:NIO到底是什么的简称?有人称之为NewI/O,因为它相对于之前的I/O类库是新增的,所以被称为NewI/O,这是它的官方叫法。但是,由于之前老的I/O类库是阻塞I/O,NewI/O类库的目标就是要让Java支持非阻塞I/O,所以,更多的人喜欢称之为非阻......
  • JAVA高级面向对象二:认识多态 多态的好处
    1.多态:在继承/实现情况下的一种现象,表现为对象多态(儿子,学生),行为多态(跑的快慢)方法编译看左边,运行看右边  变量编译看左边,运行看左边 packagecom.itheima.多态;publicclassTest{//对象多态,行为多态publicstaticvoidmain(String[]args){//1.对象......
  • 第十三届蓝桥杯省赛真题 Java C 组【原卷】
    文章目录发现宝藏【考生须知】试题A:排列字母试题B:特殊时间试题C:纸张尺寸试题D:求和试题E:\mathbf{E}:......
  • https多线程下载代码
    这里使用了curl网络库和使用多线程来下载对应https链接的文件对应的.h头文件:#pragmaonce#include<iostream>#include<fstream>#include<curl/curl.h>#include<pthread.h>#include<sys/mman.h>#include<sys/stat.h>#include<fcntl.h>#......
  • 【附源码】java数字家谱管理系统(ssm毕业设计+maven+vue+计算机专业)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义标题:数字家谱管理系统的选题背景及其意义随着信息技术的快速发展,数字化已经成为现代社会的一种趋势。在传统文化的传承与保护方面,数字技术的应用尤为重要。家谱作......
  • 【附源码】java双端的在线学习考试平台(ssm毕业设计+maven+vue+计算机专业)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义选题背景:在信息技术日益发展的今天,教育行业也在经历着前所未有的变革。传统的面对面教学模式逐渐向线上教育模式转变,这一趋势在全球范围内愈发明显。尤其是在全球......
  • Error: Could not find or load main class org.apache.hadoop.hbase.util.GetJavaPro
    Hbase没有将其自身的依赖包添加到classpath配置路径所以才会导致找不到自身主类的报错vim/usr/local/hbase/bin/hbase 在161行出修改CLASSPATH="${HBASE_CONF_DIR}"CLASSPATH=${CLASSPATH}:$JAVA_HOME/lib/tools.jar:/usr/local/hbase/lib/*修改成功后,不再报错......