【Java多线程】多线程之间是如何实现通信的?

你和你的朋友共同完成一个任务,你们随时交流对方的工作进度,因为有些任务是要对等待对方先完成再做,这个过程就是线程通信。


Table of Contents

synchronized实现通信

重入锁(ReentrantLock)实现通信

线程之间的通讯工具类

CountDownLatch(闭锁)

CyclicBarrier(栅栏)

Semaphore(信号量)


synchronized实现通信

进入等待(是Object类的方法):

  1. void wait();
  2. void wait(long timeoutMillis)
  3. void wait(long timeoutMillis, int nanos)
  4. wait方法执行后,会释放监视器,会释放锁!

唤醒(是Object类的方法):

  1. void notify();  随机唤醒一个正在wait的方法;
  2. void notifyAll();  唤醒所有正在wait的方法;

第一个同步程序:用三个线程模拟三个人报数。

/**
 * 功能:三个线程轮流报数
 *
 * @author KYLE
 *
 */
public class Main {

    public static void main(String[] args) {
        CountOff co = new CountOff();
        Thread t1 = new Thread(co);
        Thread t2 = new Thread(co);
        Thread t3 = new Thread(co);

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

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

class CountOff implements Runnable {
    private volatile int initNum = 1;
    private final int endNum = 10;

    @Override
    public void run() {
        synchronized (this) {
            while(initNum <= 10) {
                try {
                    System.out.println(Thread.currentThread().getName() + "报数:" + initNum++);
                    this.notifyAll(); //唤醒其他线程开始执行
                    this.wait(); //进入等待!
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.notifyAll();
        }
    }
}

重入锁(ReentrantLock)实现通信

进入等待(juc.locks.Condition接口):

  1. void await()   将线程放在这个条件的等待集中。
  2. boolean await(long time, TimeUnit unit)   使当前线程等待直到发出信号或中断,或指定的等待时间过去。
  3. long awaitNanos(long nanosTimeout)   使当前线程等待直到发出信号或中断,或指定的等待时间过去。
  4. void awaitUninterruptibly()   使当前线程等待直到发出信号。
  5. boolean awaitUntil(Date deadline)   使当前线程等待直到发出信号或中断,或者指定的最后期限过去。

 

唤醒:

  1. void signal()   随机唤醒一个等待集中的线程,解除其阻塞状态。
  2. void signalAll()   唤醒所有等待线程,解除阻塞状态。 

第一个重入锁实现的线程间通信:

程序功能:A线程通知B线程
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadDemo {
	
	public static void main(String[] args) {
		ReentrantLockCondition rc = new ReentrantLockCondition();
		Thread t1 = new Thread(rc,"线程1");
		
		t1.start();
		
		//通知rc对象的t1线程继续执行!!!
		//如果没有以下三段代码,则在Console只会显示:线程1开始执行。。。
		rc.lock.lock();
		rc.cond.signal(); //通知rc对象的t1线程:可以继续执行了!
		rc.lock.unlock();
	}
}

class ReentrantLockCondition implements Runnable {
	public ReentrantLock lock = new ReentrantLock();
	public Condition cond = lock.newCondition(); //newCondition方法是juc.locks.Lock接口中的方法
	
	@Override
	public void run() {
		try {
			lock.lock();
			System.out.println(Thread.currentThread().getName()+"开始执行。。。");
			cond.await(); //使得本线程进入等待,并且能够释放当前锁
			System.out.println(Thread.currentThread().getName()+"线程继续执行。。。");
		} catch(InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
}

输出:
线程1开始执行。。。
线程1线程继续执行。。。

线程之间的通讯工具类

为了能够更好控制线程之间的通信,Java为我们提供了一些工具类:

  1. CountDownLatch(闭锁)
  2. CyclicBarrier(栅栏)
  3. Semaphore(信号量)

CountDownLatch(闭锁)

CountDownLatch的功能:CountDownLatch是一个同步的辅助类,允许一个或多个线程一直等待,直到其它线程完成它们的操作。

特性:

  1. 来自juc.CountDownLatch类
  2. 它常用的API其实就两个:await()和countDown()

理解:

  1. count初始化CountDownLatch,然后需要等待的线程调用await方法。await方法会一直受阻塞直到count=0。而其它线程完成自己的操作后,调用countDown()使计数器count减1。当count减到0时,所有在等待的线程均会被释放
  2. 说白了就是通过count变量来控制等待,如果count值为0了(其他线程的任务都完成了),那就可以继续执行。
  3. 实例:你是一个实习生,其他的员工还没下班,你也不好意思走,等其他的员工都走光了,你再走。
第一个CountDownLatch程序
import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class Main {
    public static void main(String[] args) {
        YourName aa = new YourName();
        aa.GoOffWork(); //下班了
    }
}

//你是一个实习生,在XxOo公司上班,其他的员工还没下班,你也不好意思走,等其他的员工都走光了,你再走。(改编自:Java3y)
class YourName {
    private int num; //公司人数
    private final CountDownLatch cd;

    public YourName() {
        Random rand = new Random();
        this.num = rand.nextInt(10); //随机获取一个公司人数
        cd = new CountDownLatch(num); //你必须等待公司全部人走了你才能走
    }

    //下班了
    public void GoOffWork() {
        System.out.println("现在6点下班了。。。");

        //启动你自己的下班线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //这里调用的是await()而不是wait()方法
                    cd.await(); //要等待其他人都走了(执行完毕),你才能走(执行)
                } catch(InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("其他员工都走了,我也可以下班了");
            }
        }).start();

        //公司其他员工的下班线程
        for(int i=0; i<num; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("员工xx下班了!");
                    cd.countDown();
                }
            }).start();
        }
    }
}

CyclicBarrier(栅栏)

理解:

  1. CyclicBarrier允许一组线程互相等待,直到到达某个点。
  2. 叫做cyclic是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用(对比于CountDownLatch是不能重用的)
  3. 区别:CountDownLatch注重的是等待其他线程完成;CyclicBarrier注重的是:当线程到达某个状态后,暂停下来等待其他线程,所有线程均到达以后,继续执行。
  4. 例如:你和你的朋友们(一组线程)约定一起去旅游,要等到所有朋友都到齐了以后(线程间相互等待),才能出发(到达某个点)。
第一个CyclicBarrier程序
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Main {
    public static void main(String[] args) {
        Meet meet = new Meet();

        Thread t0 = new Thread(meet);
        Thread t1 = new Thread(meet);
        Thread t2 = new Thread(meet);
        Thread t3 = new Thread(meet);
        Thread t4 = new Thread(meet);

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

class Meet implements Runnable {
    private final CyclicBarrier cb = new CyclicBarrier(5, ()->{
        System.out.println("所有朋友都到了,可以出发去旅游了!");
    }); //你有5个朋友相约一起去旅游

    @Override
    public void run() {
        try {
            synchronized (this) {
                speedTime();
            }
            cb.await(); //等待所有朋友到齐
        } catch(InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }

    //每个朋友需要到达聚会点的所花时间
    public void speedTime() {
        try {
            Thread.sleep(2000); //模拟这个朋友需要2s才能到达聚会点
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("朋友(线程):"+Thread.currentThread().getName()+"到达了!");
    }
}

Semaphore(信号量)

背景:

  1. 无论是内部锁synchronized还是重入锁ReentrantLock,一次都只允许一个线程访问一个资源;
  2. 那么,有些资源必须允许多个线程访问同时访问,应该怎么实现呢?
  3. 例如,某个小店(资源)一次只能允许5个顾客(多个线程)同时挑选购买,超过5个就需要排队等待了。

理解:

  1. Semaphore(信号量)实际上就是可以指定多个线程同时访问某一资源的一种解决方案。

构造方法:

  1. Semaphore(int permits) 使用给定数量的许可和非公平方式设置创建 Semaphore 。
  2. Semaphore(int permits, boolean fair) 使用给定数量的许可和公平的方式设置创建 Semaphore 。

核心方法:

  1. void acquire()  从此信号量上获取一个许可;如果没有则一直阻塞,直到有一个可用;或者线程为interrupted
  2. void acquire(int permits)  从此信号量获取给定数量的许可;阻塞直到所有可用;或者线程为 interrupted
  3. void acquireUninterruptibly() 从此信号量获取许可,如果没有一直阻塞,直到有一个可用

 

  1. boolean tryAcquire(int permits)  只有在调用时所有许可都可用时,才从此信号量获取给定数量的许可。【尝试获取】
  2. boolean tryAcquire(int permits, long timeout, TimeUnit unit)  如果在给定的等待时间内所有许可都可用,且当前线程不是 interrupted ,则从此信号量获取给定数量的许可。【时间等待获取】
  3. boolean tryAcquire(long timeout, TimeUnit unit)  如果在给定的等待时间内有可用的并且当前线程不是 interrupted ,则从该信号量获取许可。【时间条件获取】
  4. void release()  释放许可。
第一个信号量程序
import java.util.concurrent.Semaphore;

public class Main {
    public static void main(String[] args) {
        SemaphoreExample se = new SemaphoreExample();
        //准备6个线程
        Thread t0 = new Thread(se);
        Thread t1 = new Thread(se);
        Thread t2 = new Thread(se);
        Thread t3 = new Thread(se);
        Thread t4 = new Thread(se);
        Thread t5 = new Thread(se);

        t0.start();
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

class SemaphoreExample implements Runnable {
    private final Semaphore sema = new Semaphore(5); //某一资源同一时刻只能允许5个线程访问

    @Override
    public void run() {
        try {
            sema.acquire(); //申请对该资源的访问,但构造方法规定该资源一次只能允许5个线程同时访问
            System.out.println("线程"+Thread.currentThread().getName()+"正在访问该资源!");
            Thread.sleep(3000); //一旦申请成功,立即休眠3s,模拟该线程执行任务
        } catch(InterruptedException e) {
            e.printStackTrace();
        } finally {
            sema.release();
        }
    }
}

 


 

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值