我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Java学习之线程同步与线程间通信详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Java学习之线程同步与线程间通信详解

线程同步的概念

由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也会带来访问冲突的问题:

举例:

public class Runnable_test implements Runnable {//实现Runnable接口
private  int ticknumbers=10;

    @Override
    public void run() {
        while(true){
            if(ticknumbers<=0){
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticknumbers--+"票");//currentThread()监测线程的状态
        }
    }

    public static void main(String[] args) {
        Runnable_test runnable_test=new Runnable_test();
        new Thread(runnable_test,"小明").start();
        new Thread(runnable_test,"小黄").start();
        new Thread(runnable_test,"小红").start();
    }
}

在输出的数据中,显然出现了,一张票同时被大于1人拿到的情况,这与我们的现实显然不相符合。

为了解决此问题,Java 语言提供专门的机制来避免同一个对象被多个线程同时访问,这个机制就是线程同步。

当两个或多个线程同时访问同一个变量,并且有线程需要修改这个变量时,就必须采用同步的机制对其进行控制,否则就会出现逻辑错误的运行结果

造成上述这种错误逻辑结果的原因是:可能有多个线程取得的是同一个值,各自修改并存入,从而造成修改慢的后执行的线程把执行快的线程的修改结果覆盖掉了

因为线程在执行过程中不同步,多个线程在访问同一资源时,需要进行同步操作,被访问的资源称为共享资源。

同步的本质是加锁,Java 中的任何一个对象都有一把锁以及和这个锁对应的等待队列,当线程要访问共享资源时,首先要对相关的对象进行加锁

如果加锁成功,线程对象才能访问共享资源并且在访问结束后,要释放锁:如果加锁不成功,那么线程进入被加锁对象对应的是等待队列。

Java用synchronized关键字给针对共享资源进行操作的方法加锁。每个锁只有一把钥匙,只有得到这把钥匙之后才可以对被保护的资源进行操作,而其他线程只能等待,直到拿到这把钥匙。

实现同步的具体方式有同步代码块和同步方法两种

同步代码块

使用 synchronized 关键字声明的代码块称为同步代码块。

在任意时刻,只能有一个线程访问同步代码块中的代码,所以同步代码块也称为互斥代码块

同步代码块格式如下所示:

synchronized(同步对象){
//需要同步的代码,对共享资源的访问
}

synchronized关键字后面括号内的对象就是被加载的对象,同步代码块要实现对共享资源的访问

对上述实例进行修改:

package Runnable;

public class Runnable_test implements Runnable {//实现Runnable接口
    private int ticknumbers = 20;
    private Object obj = new Object();//被加锁的对象,同步对象

    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                if (ticknumbers > 0) {
                    System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticknumbers-- + "票");//currentThread()监测线程的状态
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else
                    break;
            }
        }
    }
}


class test{
    public static void main(String[] args) {
        Runnable_test runnable_test=new Runnable_test();
        new Thread(runnable_test,"小明").start();
        new Thread(runnable_test,"小黄").start();
        new Thread(runnable_test,"小红").start();
    }
}

将票数产生变化的代码块修改为同步代码块:

修改过后输出,我们发现,并未出现同一张票,被第二个甚至第三个人拿到的情况:

小明-->拿到了第20票
小明-->拿到了第19票
小明-->拿到了第18票
小明-->拿到了第17票
小明-->拿到了第16票
小明-->拿到了第15票
小红-->拿到了第14票
小红-->拿到了第13票
小红-->拿到了第12票
小黄-->拿到了第11票
小黄-->拿到了第10票
小黄-->拿到了第9票
小黄-->拿到了第8票
小红-->拿到了第7票
小红-->拿到了第6票
小红-->拿到了第5票
小红-->拿到了第4票
小红-->拿到了第3票
小红-->拿到了第2票
小红-->拿到了第1票

在上面的修改中,仅仅是将需要互斥的代码放人了同步块中。此时,在抽票的过程中通过给同一个 obj对象加锁来实现互斥,从而保证线程的同步执行。

同步方法

synchronized关键字也可以出现在方法的声明部分,该方法称为同步方法

当多个线程对象同时访问共享资源时,只有获得锁对象的线程才能进入同步方法执行,其他访问共享资源的线程将会进入锁对象的等待队列,执行完同步方法的线程会释放锁。

[权限访问限定]    synchronized 方法返回值 方法名称(参数列表){
//.............需要同步的代码,对共享资源的访问
}

package Runnable;

public class Runnable_test implements Runnable {//实现Runnable接口
    private int ticknumbers = 20;
    @Override
    public  void run() {
        while (true) {
            if (ticknumbers > 0) {
                ticks();//调用同步方法
            }
        else
            break;
        }
    }
    
    //同步方法
    public synchronized void ticks(){
        if (ticknumbers > 0) {
            System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticknumbers-- + "票");
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//测试类
class test{
    public static void main(String[] args) {
        Runnable_test runnable_test=new Runnable_test();
        new Thread(runnable_test,"小明").start();
        new Thread(runnable_test,"小黄").start();
        new Thread(runnable_test,"小红").start();
    }
}

输出:

小明-->拿到了第20票
小明-->拿到了第19票
小明-->拿到了第18票
小明-->拿到了第17票
小明-->拿到了第16票
小黄-->拿到了第15票
小黄-->拿到了第14票
小黄-->拿到了第13票
小黄-->拿到了第12票
小黄-->拿到了第11票
小黄-->拿到了第10票
小黄-->拿到了第9票
小红-->拿到了第8票
小红-->拿到了第7票
小红-->拿到了第6票
小红-->拿到了第5票
小红-->拿到了第4票
小红-->拿到了第3票
小红-->拿到了第2票
小红-->拿到了第1票

同步方法的本质也是给对象加锁,但是是给同步方法所在类的 this 对象加锁,所以在上述实例中,我们就删除了obj对象的定义。

package Runnable;

public class Runnable_test implements Runnable {//实现Runnable接口
    private int ticknumbers = 20;
    boolean tag = false;//设置此变量的作用是为了让一个线程进入同步块,另一个线程进入同步方法

    @Override
    public void run() {
        if(tag){
            while(true)
                ticks();
        }
        else{
            while (true) {
                synchronized (this) {
                    if (ticknumbers > 0) {
                        System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticknumbers-- + "票");//currentThread()监测线程的状态
                        try {
                            Thread.sleep(300);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else
                        break;
                }
            }
        }
    }

    //同步方法
    public synchronized void ticks() {
        if (ticknumbers > 0) {
            System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticknumbers-- + "票");
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        else
            return;
    }
}


    //测试类
    class test {
        public static void main(String[] args) throws InterruptedException {
            Runnable_test runnable_test = new Runnable_test();
            Thread thread1=new Thread(runnable_test, "小明");
            thread1.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            runnable_test.tag=true;
            Thread thread2=new Thread(runnable_test, "小黄");
            thread2.start();
        }
    }

输出:

小明-->拿到了第20票
小明-->拿到了第19票
小明-->拿到了第18票
小明-->拿到了第17票
小明-->拿到了第16票
小明-->拿到了第15票
小明-->拿到了第14票
小明-->拿到了第13票
小明-->拿到了第12票
小明-->拿到了第11票
小明-->拿到了第10票
小明-->拿到了第9票
小明-->拿到了第8票
小明-->拿到了第7票
小黄-->拿到了第6票
小黄-->拿到了第5票
小黄-->拿到了第4票
小黄-->拿到了第3票
小黄-->拿到了第2票
小黄-->拿到了第1票

通过程序运行结果可以看出:线程thread1执行同步代码块,线程thread2执行同步方法,两个线程之间形成了同步。

由于同步代码块是给 this对象加锁,所以表明同步方法也是给 this对象加锁,否则,两者之间不能形成同步。

注意:多线程的同步程序中,不同的线程对象必须给同一个对象加锁,否则这些线程对象之间无法实现同步

线程组

线程组可以看作是包含了许多线程的对象集,它拥有一个名字以及一些相关的属性,可以当作一个组来管理其中的线程。

每个线程都是线程组的一个成员,线程组把多个线程集成一个对象,通过线程组可以同时对其中的多个线程进行操作。在生成线程时必须将线程放到指定的线程组,也可以放在缺省的线程组中,缺省的就是生成该线程的线程所在的线程组。一旦一个线程加入了某个线程组,就不能被移出这个组。

java,lang包的ThreadGroup类表示线程组,在创建线程之前,可以创建一个ThreadGroup对象。

下面代码是创建线程组并在其中加人两个线程

ThreadGroup myThreadGroup = new ThreadGroup("a");    //创建线程组

//将下述两个线程加入其中
Thread myThread1 = new Thread(myThreadGroup,"worker1");
Thread myThread2 = new Thread(myThreadGroup,"worker2");
myThread1.start();
myThread2.start();

线程组的相关方法

String getName();    //返回线程组的名字
ThreadGoup getParent();    //返回父线程
int tactiveCount();    //返回线程组中当前激活的线程的数目,包括子线程组中的活动线程
int enumerate(Thread list[])    //将所有线程组中激活的线程复制到一个线程数组中
void setMaxPriority(int pri)    //设置线程的最高优先级,pri是该线程组的新优先级
void interrupt()    //向线程组及其子组中的线程发送一个中断信息
boolean isDaemon()    //判断是否为Daemon线程组
boolean parentOf(ThreadGoup g)    //判断线程组是否是线程g或g的子线程
toString()    //返回一个表示本线程组的字符串 

线程组对象的基本应用

举例:

package Runnable;

public class MyThreadgroup {
    public void test(){
        ThreadGroup threadGroup=new ThreadGroup("test");    //创建名为test的线程组
        Thread A=new Thread(threadGroup,"线程A");
        Thread B=new Thread(threadGroup,"线程B");
        Thread C=new Thread(threadGroup,"线程C");

        //为线程设置优先级
        A.setPriority(6);
        C.setPriority(4);
        A.start();
        B.start();
        C.start();
        System.out.println("threadGroup正在进行活动的个数:"+threadGroup.activeCount());
        System.out.println("线程A的优先级:"+A.getPriority());
        System.out.println("线程B的优先级:"+B.getPriority());
        System.out.println("线程C的优先级:"+C.getPriority());
    }
}
class MyThreadgroup_test{
    public static void main(String[] args) {
       MyThreadgroup myThreadgroup=new MyThreadgroup();
       myThreadgroup.test();
    }
}

输出:

threadGroup正在进行活动的个数:3
线程A的优先级:6
线程B的优先级:5
线程C的优先级:4

线程间的通信

某些情况下,多个线程之间需要相互配合来完成一件事情,这些线程之间就需要进行通信”,把一方线程的执行情况告诉给另一方线程。

“通信”的方法在 java.lang.Object类中定义了,我们可以通过“生产者-消费者”模型来理解线程间的通信。

有两个线程对象,其中一个是生产者,另一个是消费者。生产者线程负责生产产品并放入产品缓冲区,消费者线程负责从产品缓冲区取出产品并消费。

当生产者线程获得 CPU 使用权后:

先判断产品缓冲区是否有产品,如果有产品就调用 wait()方法进入产品缓冲区对象的等待队列并释放产品缓冲区对象的锁;如果发现产品缓冲区中没有产品,就生产产品并放入缓冲区并调用notify()方法发送通知给消费者线程。

当消费者线程获得CPU使用权后:

先判断产品缓冲区是否有产品,如果有产品就拿出来消费并调用 notify()方法发送通知给生产者线程;如果发现产品缓冲区中没有产品,调用 wait()方法进入产品缓冲区对象的等待队列并释放产品缓冲区对象的锁。

注意:线程间通信是建立在线程同步基础上的,所以wait()notify()和notifyAll()方法的调用要出现在同步代码块或同步方法中

线程通信简单应用

package Runnable;


 class Box {//产品缓冲区
    public String name="苹果";//表示产品的名称
    public boolean isFull=true;//表示当前缓冲区中是否有产品
}



//定义消费者类
class Cossumer implements Runnable {
    Box box;

    Cossumer(Box box) {
        this.box = box;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (box) {//对产品缓冲区对象加锁
                if (box.isFull == true) //缓冲区中有产品
                {
                    System.out.println("消费者拿出----:" + box.name);
                    box.isFull = false;//设置缓冲区中产品为空
                    box.notify();//发送通知给生产者线程对象
                } else {
                    try {
                        //消费者线程进入产品缓冲区的等待队列并释放锁
                        box.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}



    //生产者类
    class product implements Runnable{
        Box box;
        int Count=0;

        public product(Box box) {
            this.box=box;
        }

        @Override
        public void run() {
            while(true){
                synchronized (box)//对产品缓冲区对象加锁
                {
                    if(box.isFull==true)//缓冲区中有产品
                    {
                        try {
                            box.wait();//生产者线程进入等待队列并释放锁
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    else {
                        if (Count == 0) {
                            box.name = "香蕉";
                            System.out.println("生产者放入+++++:" + box.name);
                        } else {
                            box.name = "苹果";
                            System.out.println("生产者放入+++++:" + box.name);
                        }
                        Count=(Count+1)%2;
                        box.isFull=true;//设置缓冲区中有产品
                        box.notify();//发送通知给消费者线程对象
                    }
                }
            }
        }
    }


class box_test{
    public static void main(String[] args) {
        Box box=new Box();//创建产品缓冲区对象
        product product=new product(box);

        Cossumer cossumer=new Cossumer(box);//生产者和消费者对象要共享同一个产品缓冲区
        Thread thread1=new Thread(product);//创建生产者线程对象
        Thread thread2=new Thread(cossumer);//创建消费者线程对象
        thread1.start();//启动生产者线程对象
        thread2.start();//启动消费者线程对象
    }
}

输出:

消费者拿出----:香蕉
生产者放入+++++:苹果
消费者拿出----:苹果
生产者放入+++++:香蕉
消费者拿出----:香蕉
生产者放入+++++:苹果
消费者拿出----:苹果
生产者放入+++++:香蕉
消费者拿出----:香蕉
生产者放入+++++:苹果
消费者拿出----:苹果
生产者放入+++++:香蕉

从运行结果可以看出:生产者线程向缓冲区放入什么产品,消费者就从缓冲区中取出什么产品,生产者生产一个产品,消费者就消费一个产品,两者之间实现了通信.

以上就是Java学习之线程同步与线程间通信详解的详细内容,更多关于Java线程的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Java学习之线程同步与线程间通信详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Java学习之线程同步与线程间通信详解

这篇文章主要为大家详细介绍了线程同步和线程之间的通信的相关知识,文中的示例代码讲解详细,对我们学习Java有一定的帮助,感兴趣的可以了解一下
2022-12-27

在Java中,如何实现多线程之间的同步与通信?(Java多线程编程时,应如何确保线程间的同步与有效通信?)

本文详细介绍了Java多线程编程中的同步和通信机制。为了实现同步,可以使用synchronized关键字、Lock接口或Semaphore。同步确保多个线程在访问共享资源时保持一致性。对于通信,共享内存、消息队列和管道等机制允许线程共享数据和协调任务。文中还提供了代码示例、最佳实践和问题的预防策略。通过这些机制,开发者可以创建同步且有效的Java多线程应用程序。
在Java中,如何实现多线程之间的同步与通信?(Java多线程编程时,应如何确保线程间的同步与有效通信?)
2024-04-02

Java线程之线程同步synchronized和volatile详解

上篇通过一个简单的例子说明了线程安全与不安全,在例子中不安全的情况下输出的结果恰好是逐个递增的(其实是巧合,多运行几次,会产生不同的输出结果),为什么会产生这样的结果呢,因为建立的Count对象是线程共享的,一个线程改变了其成员变量num值
2023-05-30

详谈java线程与线程、进程与进程间通信

线程与线程间通信一、基本概念以及线程与进程之间的区别联系:关于进程和线程,首先从定义上理解就有所不同1、进程是什么?是具有一定独立功能的程序、它是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说进程是可以独 立运行
2023-05-31

java多线程编程之管道通信详解

上一章节讲了wait/notify通信,这一节我们来探讨使用管道进行通信。 java中提供了IO流使我们很方便的对数据进行操作,pipeStream是一种特殊的流,用于不同线程间直接传送数据。一个线程将数据发送到输出管道,另一个线程从输入管
2023-05-30

ReentrantLock从源码解析Java多线程同步学习

这篇文章主要为大家介绍了ReentrantLock从源码解析Java多线程同步学习,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-16

Java编程之多线程死锁与线程间通信简单实现代码

死锁定义 死锁是指两个或者多个线程被永久阻塞的一种局面,产生的前提是要有两个或两个以上的线程,并且来操作两个或者多个以上的共同资源;我的理解是用两个线程来举例,现有线程A和B同时操作两个共同资源a和b,A操作a的时候上锁LockA,继续执行
2023-05-30

浅谈Java线程间通信之wait/notify

Java中的wait/notify/notifyAll可用来实现线程间通信,是Object类的方法,这三个方法都是native方法,是平台相关的,常用来实现生产者/消费者模式。先来我们来看下相关定义:wait() :调用该方法的线程进入WA
2023-05-31

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录