《五》多线程基础——Java线程间的通信(互斥与协作)

Java并发编程系列文章
《一》多线程基础——Java线程与进程的基本概念
《二》多线程基础——Java线程入门类和接口
《三》多线程基础——Java线程组和线程优先级
《四》多线程基础——Java线程生命周期及转换
《五》多线程基础——Java线程间的通信(互斥与协作)
《六》实际应用——如何优雅的关闭线程
《七》实际应用——生产者与消费者模型

  并发编程(多线程)一直以来都是程序员头疼的难题。曾经听别人总结过并发编程的第一原则,那就是不要写并发程序,哈哈哈。后来发现,这样能够显著提高程序响应和吞吐量的利器,哪还能忍得住不会用呢?
  整理出《Java并发编程系列文章》,共计7篇,与君共勉。



1、锁与同步

  在Java中,锁的概念都是基于对象的,所以我们⼜经常称它为对象锁。线程同步听着很高大上,cao,被吓了一跳,其实就是让线程之间按照⼀定的顺序执⾏。怎么实现呢?我们可以使⽤锁来实现它。

public class ObjectLock {
    private static Object lock = new Object();
    static class ThreadA implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 100; i++) {
                    System.out.println("Thread A " + i);
                }
            }
        }
    }
    static class ThreadB implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 100; i++) {
                    System.out.println("Thread B " + i);
                }
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        new Thread(new ThreadA()).start();
        Thread.sleep(10);
        new Thread(new ThreadB()).start();
    }
}

  这⾥声明了⼀个名字为 lock 的对象锁。我们在 ThreadA 和ThreadB 内需要同步的代码块⾥,都是⽤ synchronized 关键字加上了同⼀个对象锁 lock 。
  根据线程和锁的关系,同⼀时间只有⼀个线程持有⼀个锁,那么线程B就会等线程A执⾏完成后释放 lock ,线程B才能获得锁 lock 。
  这⾥在主线程⾥使⽤sleep⽅法睡眠了10毫秒,是为了防⽌线程B先得到锁。因为如果同时start,线程A和线程B都是出于就绪状态,操作系统可能会先让B运⾏。这样就会先输出B的内容,然后B执⾏完成之后⾃动释放锁,线程A再执⾏。
  更多内容,请查看《八》线程安全·数据共享——Synchronized与锁

2、等待和通知

  上⾯⼀种基于“”的方式,获取CPU时间片的线程需要不断地去尝试获得锁,如果失败了,当再次获取CPU时间片后继续尝试。这可能会耗费服务器资源。⽽等待/通知机制是另⼀种⽅式。
  Java多线程的等待/通知机制是基于 Object 类的 wait() notify(),notifyAll()来实现的。notify()会随机叫醒⼀个正在等待的线程,⽽notifyAll()会叫醒所有正在等待的线程。
  例如:线程A持有了⼀个锁 lock 并开始执⾏,它可以使⽤lock.wait()让⾃⼰进⼊等待状态。这个时候, lock 这个锁是被释放了的。线程B获得了 lock 这个锁并开始执⾏,它可以在某⼀时刻,使⽤ lock.notify() ,叫醒之前释放 lock 锁并进⼊等待状态的线程A,注意,此时锁依然在线程B手中,只有当线程B执行完了,线程A才能竞争lock 锁。

public class WaitAndNotify {
    private static Object lock = new Object();
    static class ThreadA implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    try {
                        System.out.println("ThreadA: " + i);
                        lock.notify();
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();
            }
        }
    }
    static class ThreadB implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    try {
                        System.out.println("ThreadB: " + i);
                        lock.notify();
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        new Thread(new ThreadA()).start();
        Thread.sleep(1000);
        new Thread(new ThreadB()).start();
    }
}
// 输出:
ThreadA: 0
        ThreadB: 0
        ThreadA: 1
        ThreadB: 1
        ThreadA: 2
        ThreadB: 2
        ThreadA: 3
        ThreadB: 3
        ThreadA: 4
        ThreadB: 4

  在这个Demo⾥,线程A和线程B⾸先打印出⾃⼰需要的东⻄,然后使⽤ notify() ⽅法叫醒另⼀个正在等待的线程,然后自己使⽤ wait() ⽅法陷⼊等待并释放 lock 锁。需要注意的是等待/通知机制使⽤的是使⽤同⼀个对象锁,如果你两个线程使 ⽤的是不同的对象锁,那它们之间是不能⽤等待/通知机制通信的。

多线程的休息室WaitSet的详细介绍:
(1)所有对象都可以作为锁,也都有WaitSet这么一个概念,用来存放调用该对象的wait()方法之后进入阻塞状态的线程;
(2)WaitSet中的线程被notify()之后,不会立即执行,因为执行notify()的线程正在持有对象锁。
(3)线程从WaitSet中被唤醒的顺序,不一定是进入的顺序。
(4)线程被唤醒后,必须重新获取锁。

3、信号量

等待后续补充

4、管道

等待后续补充

5、其他互斥与协作方法

5.1、sleep()

  • sleep是Thread的方法,wait是Object的方法。
  • wait释放cpu资源,同时释放锁;sleep释放cpu资源,但是不释放锁,所以易死锁。
  • wait可以指定时间,也可以不指定;⽽sleep必须指定时间。
  • wait必须放在同步块或同步⽅法中,⽽sleep可以再任意位置。

5.2、join()

  • 执行join语句的当前线程,会等被join的线程执行完,再执行。
  • 例如:start httpServer,启动的线程会随着主线程结束后自动挂掉,所以通过。

5.3、interrupt()

  • 线程自己不能interrupt本身,但可以中断别的线程。被调用interrupt方法进行中断的线程如果正处于wait,sleep,join状态,那么会报InterruptedException。
  • 特别注意interrupt正处于join的线程时,interrupt的不是被调用join的线程,而是调用join的线程。
  • interrupt和interrupted的区别:interrupt()用于继承Thread的线程,Thread.interrupted()用于实现Runnable的线程

5.4、ThreadLocal类

  • 如果开发者希望将类的某个静态变量(user ID或者transaction ID)与线程状态关联,则可以考虑使⽤ThreadLocal。
  • 最常⻅的ThreadLocal使⽤场景为⽤来解决数据库连接、Session管理等。数据库连接和Session管理涉及多个复杂对象的初始化和关闭。
  • 如果在每个线程中声明⼀些私有变量来进⾏操作,那这个线程就变得不那么“轻量”了,需要频繁的创建和关闭连接。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值