多线程-线程间通信

本文围绕线程间通信展开,先介绍了进程和线程的区别,随后重点讲解了synchronized关键字,包括对象锁、实现线程间通信、与volatile和lock的区别,还对比了悲观锁和乐观锁。最后阐述了sleep/wait、wait/notify的区别及使用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程是我们在开发中经常需要用到的,所以了解线程也是很有必要,本章主要围绕线程间的通信。主要的讲解内容:

讲到线程还是先说明下进程和线程的区别:

进程:一个APP的启动前都会创建个进程,然后APP的运行基本就基于这个进程中。除非我们有意去创建新的进程,不然一个APP就只会在这个进程中完成自身所有的操作。

线程:我们开发中经常会用到,new Thread().start()去创建线程。这个线程就会和所处的进程资源共享。

说到线程间通信,那自然就有进程间通信,想了解的朋友可以看下我的另一篇文章

Android进程间通信机制Binder

下进入正题。目录:

一、synchronized关键字

二、sleep/wait、wait/notify

 

一、synchronized关键字

  1. synchronized对象锁
  2. synchronized来实现线程间通信
  3. synchronized/volatile
  4. synchronized/lock

1.synchronized对象锁

sychronized是对象锁,表明synchronized关键字锁住的是当前的对象。如下方代码:

public class synchronizedObject {
    synchronized public void methodA(){
    }
    public void methodB(){
        synchronized (this){
        }
    }
}

在这个类中,methodA、methodB都用了synchronized关键字,两种写法效果是一样的。都是锁住了method这个方法。这就是synchronized的其中一种用法。

 

2.synchronized来实现线程间通信

先上代码:

public class testSynchronized {
    public static void main(String[] args){
        MyObject object = new MyObject();

        ThreadA a = new ThreadA(object);
        ThreadB b = new ThreadB(object);
        a.start();
        b.start();
    }
}

class MyObject{
    synchronized public void methodA(){
        System.out.println("a");
    }
    synchronized public void methodB(){
        System.out.println("b");
    }
}
class ThreadA extends Thread{
    private MyObject object;
    public ThreadA(MyObject object){
        this.object = object;
    }

    @Override
    public void run() {
        super.run();
        object.methodA();
    }
}
class ThreadB extends Thread{
    private MyObject object;
    public ThreadB(MyObject object){
        this.object = object;
    }

    @Override
    public void run() {
        super.run();
        object.methodB();
    }
}

这里我创建了MyObject类写了个带锁方法method。

再建了ThreadA和ThreadB。在main让A和B Thread同时持有MyObject对象去同时调用。

两个线程同时持有一个对象并且同时去调用,但是因为带有synchronized是对象锁,哪怕他们调用的是同一对象的不通方法,但ThreadB也必需等到ThreadA调用完A方法后才能够调用B方法。

这种通过共同拥有一个变量的方式也可以称为共享内存式的通信。

 

3.synchronized/volatile

    int i1;
    int geti1(){
        return i1;
    }

    volatile int i2;
    int geti2(){
        return i2;
    }

    int i3;
    synchronized int getI3(){
        return i3;
    }

上面是3种变量的写法,i1是不加关键字。i2加了volatile。i3加了synchronized。

那么三种有什么区别:

i1:在被多线程持有并且改变的时候,i1数值会不统一。也就是说这样的i1是不具备线程间通信的唯一性。为什么会有这种情况出现?因为java当做有一个主内存的机制,其实就是缓存的机制,可以保存变量当前的值。每个线程又有独立的内存,这个变量值会拷贝到线程独立内存中。所以当A线程和线程B同时更改这个值但没有通知主内存,就会造成和主内存的变量值不同。

i2:用了volatile的值是不允许线程从主内存中拷贝到自己的独立内存中,也就是说线程A或线程B想获取,都是取的主内存的i2。无论在任何线程中改变变量,其他线程都会立刻得到改变的结果。也因为这个高效的同步性,使用volatile对性能上会有所消耗。

i3:synchronized会有个监听器,如果两个线程都要用geti3方法,先获得锁的线程可使用此方法,另一个线程只能等锁解开才能获取锁调用。

获取锁后会与主内存同步数据。然后代码块就会被执行。执行完后会把任何变量的更改返回给主内存。然后释放对象锁。

volatile与synchronized区别:

volatile:只能在线程内存和主内存之间同步一个变量值。只能在变量上使用。

synchronized:在线程内存和主内存之间同步所有变量。并且可以通过锁来管理整个变量。性能上会比volatile更大的消耗。可以在变量、方法、类上。

 

4.synchronized/lock

类别synchronizedlock
存在层次java的关键字,在jvm层是一个类
锁的释放

1.以获取锁的线程执行完同步代码释放锁

2.线程执行异常,jvm让线程释放锁

需要手动代码解锁,最好在finally中释放,不然容易造成死锁
锁获取线程执行带锁对象时判断,如果没被锁将直接获取,如果在锁状态则等待。代码获取锁,线程可以不用一直等待。
锁状态无法判断可判断
锁类型悲观锁乐观锁

lock其实实际运用比较少,需要手动去锁和解锁,运用不当可能会造成死锁。

这里重点说下悲观锁和乐观锁区别:

悲观锁:悲观的概念就是总怕此数据正确性有问题,所以每次操作数据都要上锁,以确保每次数据都是正确且不会被别的线程修改。

乐观锁:总认为别人不会改数据,所以都不会上锁,只有在更新数据的时候会判断此间别人有没去更新这个数据。可以使用版本号等的机制去判断。

乐观锁适用于读取多的数据,悲观锁适用于经常被修改的数据。

二、sleep/wait、wait/notify

类别sleepwait
所属类属于Thread类Object类
是否释放对象锁不会
使用限制任何地方都可以智能在同步方法或者异步块中使用
效果只让出了cpu不让出同步资源锁让出同步资源锁、CPU
什么时候恢复在指定时间之后线程恢复运行notyfy方法被调用后恢复参与竞争同步资源锁,进而继续执行。
是否需要捕捉异常

sleep和wait区别直接看表格。

结合上面synchronized关键字,我们可以了解到,如果我们为了保持此数据还在自身线程中不被其他线程占用,那就可以用sleep方法。如果是此数据可以先别其他线程使用,那我们可以用wait。

wait和notify/notifyAll就很好理解了。
wait:调用后把当前锁释放让出CPU,进入一个等待的状态。

notify/notifyAll:执行该方法后,会唤醒线程,如果获得对象锁则继续执行。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值