java之线程同步和线程之间的通信

简介: java之线程同步和线程之间的通信

线程同步的概念:

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

举例:

package Runnable;
public class sync implements Runnable{
    private int ticks=5;
    @Override
    public void run() {
        while(true){
            if(ticks>0){
                System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticks-- + "票");
            }
            else{
                break;
            }
        }
    }
    public static void main(String[] args) {
        //创建一个售票对象
        sync sync=new sync();
        //创建三个线程对象,共享一个售票对象
        new Thread(sync,"小黄").start();
        new Thread(sync,"大军").start();
        new Thread(sync,"小明").start();
    }
}

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


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


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

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

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


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


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


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


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


同步代码块:

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

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

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

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

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

对上述实例进行修改:

package Runnable;
public class sync implements Runnable{
    private int ticks=5;
    private Object object=new Object();
    @Override
    public void run() {
            while (true) {
                synchronized (object) {
                if (ticks > 0) {
                    System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticks-- + "票");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }
    public static void main(String[] args) {
        //创建一个售票对象
        sync sync=new sync();
        //创建三个线程对象,共享一个售票对象
        new Thread(sync,"小黄").start();
        new Thread(sync,"大军").start();
        new Thread(sync,"小明").start();
    }
}

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

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

多次运行,结果可能会不相同

小黄-->拿到了第5票
小黄-->拿到了第4票
大军-->拿到了第3票
大军-->拿到了第2票
大军-->拿到了第1票

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

同步方法:

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

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

[权限访问限定]  synchronized 方法返回值 方法名称(参数列表){
//.............需要同步的代码,对共享资源的访问
}
package Runnable;
public class sync implements Runnable{
    private int ticks=5;
    @Override
    public void run() {
            while (true) {
                if(ticks>0){//若当前票数满足,则调用同步方法
                    show_ticks();
                }
                else{
                    break;
                }
        }
    }
    public synchronized void show_ticks() {
                if (ticks > 0) {
                    System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticks-- + "票");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    public static void main(String[] args) {
        //创建一个售票对象
        sync sync=new sync();
        //创建三个线程对象,共享一个售票对象
        new Thread(sync,"小黄").start();
        new Thread(sync,"大军").start();
        new Thread(sync,"小明").start();
    }
}

输出:

小黄-->拿到了第5票
小黄-->拿到了第4票
大军-->拿到了第3票
大军-->拿到了第2票
小明-->拿到了第1票

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

package Runnable;
public class sync implements Runnable{
    private int ticks=20;
    boolean tag=false;
    @Override
    public void run() {
        if (tag) {
            while (true)
                show_ticks();//进入同步方法
        } else {
            while (true) {
                synchronized (this) {
                    if (ticks > 0) {
                        System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticks-- + "票");
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    else{
                        return;
                    }
                }
            }
        }
    }
    public synchronized void show_ticks() {
        if (ticks > 0) {
            System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticks-- + "票");
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        else{
            return ;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        sync sync=new sync();
        new Thread(sync,"小黄").start();//小黄进入同步代码块
        Thread.sleep(1000);
        sync.tag=true;//将tag变量设置为true---->大军进入同步方法
        new Thread(sync,"大军").start();
    }
}

输出:

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

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


由于同步代码块是给 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();//启动消费者线程对象
    }
}

输出:

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

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

相关文章
|
7天前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
21 2
|
11天前
|
存储 缓存 Java
java线程内存模型底层实现原理
java线程内存模型底层实现原理
java线程内存模型底层实现原理
|
13天前
|
Java 开发者
Java中的多线程基础与应用
【9月更文挑战第22天】在Java的世界中,多线程是一块基石,它支撑着现代并发编程的大厦。本文将深入浅出地介绍Java中多线程的基本概念、创建方法以及常见的应用场景,帮助读者理解并掌握这一核心技术。
|
9天前
|
Java 调度
Java-Thread多线程的使用
这篇文章介绍了Java中Thread类多线程的创建、使用、生命周期、状态以及线程同步和死锁的概念和处理方法。
Java-Thread多线程的使用
|
12天前
|
Java 调度 开发者
Java中的多线程编程:从基础到实践
本文旨在深入探讨Java多线程编程的核心概念和实际应用,通过浅显易懂的语言解释多线程的基本原理,并结合实例展示如何在Java中创建、控制和管理线程。我们将从简单的线程创建开始,逐步深入到线程同步、通信以及死锁问题的解决方案,最终通过具体的代码示例来加深理解。无论您是Java初学者还是希望提升多线程编程技能的开发者,本文都将为您提供有价值的见解和实用的技巧。
15 2
|
14天前
|
Java 数据处理
Java中的多线程编程:从基础到实践
本文旨在深入探讨Java中的多线程编程,涵盖其基本概念、创建方法、同步机制及实际应用。通过对多线程基础知识的介绍和具体示例的演示,希望帮助读者更好地理解和应用Java多线程编程,提高程序的效率和性能。
19 1
|
7天前
|
Java 数据中心 微服务
Java高级知识:线程池隔离与信号量隔离的实战应用
在Java并发编程中,线程池隔离与信号量隔离是两种常用的资源隔离技术,它们在提高系统稳定性、防止系统过载方面发挥着重要作用。
6 0
|
9天前
|
Java 数据处理 调度
Java中的多线程编程:从基础到实践
本文深入探讨了Java中多线程编程的基本概念、实现方式及其在实际项目中的应用。首先,我们将了解什么是线程以及为何需要多线程编程。接着,文章将详细介绍如何在Java中创建和管理线程,包括继承Thread类、实现Runnable接口以及使用Executor框架等方法。此外,我们还将讨论线程同步和通信的问题,如互斥锁、信号量、条件变量等。最后,通过具体的示例展示了如何在实际项目中有效地利用多线程提高程序的性能和响应能力。
|
10天前
|
传感器 网络协议 Java
三大硬核方式揭秘:Java如何与底层硬件和工业设备轻松通信!
大家好,我是V哥。最近与一位从事工业互联网项目的学员交流,启发我分享Java如何与底层硬件和工业设备通信。本文将介绍三种方法:1)使用`jLibModbus`库通过Modbus协议读取设备寄存器数据;2)使用JNI(Java Native Interface)直接访问硬件;3)使用`JSerialComm`库通过串口通信读取数据。每种方法都有详细步骤和示例代码,帮助你轻松实现与硬件设备的通信。无论是工业自动化还是物联网应用,这些方法都能派上用场。欢迎关注和支持!
|
10天前
|
安全 算法 Java
Java中的多线程编程:从基础到高级应用
本文深入探讨了Java中的多线程编程,从最基础的概念入手,逐步引导读者了解并掌握多线程开发的核心技术。无论是初学者还是有一定经验的开发者,都能从中获益。通过实例和代码示例,本文详细讲解了线程的创建与管理、同步与锁机制、线程间通信以及高级并发工具等主题。此外,还讨论了多线程编程中常见的问题及其解决方案,帮助读者编写出高效、安全的多线程应用程序。
下一篇
无影云桌面