【多线程学习笔记4】线程同步

简介: 【多线程学习笔记4】线程同步

当使用多线程来访问同一个数据时,就会很容易出现线程安全问题,最经典的问题就是银行取钱的问题。而如何处理线程安全问题呢,最常用的回答就是加锁,实现线程同步。那么如何加锁呢,拆分之后,无非是下面三种方式:同步代码块、同步方法、同步锁。


1 同步代码块


同步代码块的语法如下:

synchronized(obj){
    //此处的代码就是同步代码块
}


上述语法格式中的synchronized后括号里的obj就是同步监视器,上面代码的含义是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。


注意:阻止多线程对同一个共享资源进行并发访问,通常推荐使用可能被并发访问的共享资源充当同步监视器。


2 同步方法


与同步代码块对应,Java的多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字修饰某个方法,则该方法称为同步方法。对于synchronized修饰的实例方法而言,无须显示指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。


注意:synchronized关键字可以修饰方法,可以修饰代码块,但不能修饰构造器、成员变量等。


可变类的线程安全是以降低程序的运行效率作为代价的,为了减少线程安全所带来的负面影响,程序可以采取如下策略:


(1)不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源的方法进行同步。


(2)如果可变类有两种运行环境:单线程环境和多线程环境,则应该为该可变类提供两种版本,即线程不安全版本和线程安全版本。


JDK所提供的StringBuilder、StringBuffer就是为了照顾单线程环境和多线程环境所提供的类,在单线程环境下应该使用StringBulider来保证较好的性能;当需要保证多线程安全时,就应该使用SringBuffer。


释放同步监视器的锁定:


针对同步代码块、同步方法,程序无法显式释放对同步监视器的锁定,那么线程在哪几种情况下会释放同步监视器的锁定呢?


(1)同步方法、同步代码块执行结束;

(2)同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行;

(3)同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束;

(4)程序执行了同步监视器对象的wait()方法。


下面的情况不会释放锁定:程序调用sleep()、yield()和suspend()方法。


3 同步锁


从Java5开始,Java还提供了一种功能强大的线程同步机制,通过显示定义同步锁对象来实现同步,在这种机制下,同步锁由Lock对象充当。


Lock是控制多线程对共享资源进行访问的工具,通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象。


某些锁可能允许对共享资源并发访问,如ReadWriteLock,另外还为Lock提供了ReentrantLock(可重入锁)实现类。在实现线程安全的控制中,比较常用的是ReentrantLock(可重入锁)。使用该Lock对象可以显式地加锁、释放锁,通常使用ReentrantLock的代码格式如下:

class X {
    //定义锁对象
    private final ReentrantLock lock = new ReentrantLock();
    // ...
    public void m(){
        //加锁
        lock.lock();
        try{
            //需要保证线程安全的代码
            // method body
        }
        //使用finally块来保证释放锁
        finally{
            lock.unlock();
        }
    }
}


注意:


(1)使用ReentrantLock对象来进行同步,加锁和释放锁出现在不同的作用范围内时,通常建议使用finally块来确保在必要时释放锁。


(2)使用Lock与使用同步方法有点相似,只是使用Lock时显式使用Lock对象作为同步锁,而使用同步方法时系统隐式使用当前对象作为同步监视器。


(3)ReentrantLock锁具有可重入性,也就是说,一个线程可以对已被加锁的ReentrantLock锁再次加锁,ReentrantLock对象会维持一个计数器来追踪lock()方法的嵌套调用,线程在每次调用lock()加锁后,必须显式调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。


4 死锁


当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有监测,也没有采取措施处理死锁的情况,所以在多线程编程时,应该采取措施避免死锁出现。而一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态。

相关文章
|
14天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
25天前
|
存储 缓存 NoSQL
Redis单线程已经很快了6.0引入多线程
Redis单线程已经很快了6.0引入多线程
31 3
|
27天前
|
消息中间件 安全 Linux
线程同步与IPC:单进程多线程环境下的选择与权衡
线程同步与IPC:单进程多线程环境下的选择与权衡
57 0
|
1月前
|
Java 调度 C#
C#学习系列相关之多线程(一)----常用多线程方法总结
C#学习系列相关之多线程(一)----常用多线程方法总结
|
1月前
|
安全 编译器 C#
C#学习相关系列之多线程---lock线程锁的用法
C#学习相关系列之多线程---lock线程锁的用法
|
1月前
|
Java C#
C#学习系列相关之多线程(五)----线程池ThreadPool用法
C#学习系列相关之多线程(五)----线程池ThreadPool用法
|
1月前
|
存储 安全 Java
深入理解 Java 多线程、Lambda 表达式及线程安全最佳实践
线程使程序能够通过同时执行多个任务而更有效地运行。 线程可用于在不中断主程序的情况下在后台执行复杂的任务。 创建线程 有两种创建线程的方式。 扩展Thread类 可以通过扩展Thread类并覆盖其run()方法来创建线程:
107 1
深入理解 Java 多线程、Lambda 表达式及线程安全最佳实践
|
1月前
|
缓存 安全 Java
保障线程安全性:构建可靠的多线程应用
保障线程安全性:构建可靠的多线程应用
|
2月前
|
Java
网络 I/O:单 Selector 多线程(单线程模型)
网络 I/O:单 Selector 多线程(单线程模型)
|
1月前
|
数据采集 存储 Java
「多线程大杀器」Python并发编程利器:ThreadPoolExecutor,让你一次性轻松开启多个线程,秒杀大量任务!
「多线程大杀器」Python并发编程利器:ThreadPoolExecutor,让你一次性轻松开启多个线程,秒杀大量任务!

热门文章

最新文章