zoukankan      html  css  js  c++  java
  • (五)多线程:线程同步

    1.线程安全问题

     多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。
     多个线程在操作共享的数据(读写操作),一条线程对共享数据的修改导致其他线程对数据的判断出错(共享数据未修改之前就已出判断)而做出的错误处理,最终产生错误的结果,称之为线程不安全。

    2.同步代码块

     Java的多线程支持引入同步监视器来解决线程安全问题,使用同步监视器的通用方法就是同步代码块。

        synchronized(obj) {
    
            ...
            //此处代码就是代码同步快
        }
    

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

    注意:任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码执行完成后,该线程会释放对该同步监视器的锁定。
    推荐:通常推荐使用可能被并发访问的共享资源充当同步监视器。

    3.同步方法

     同步方法就是使用synchronized关键字来修饰某个方法,则该方法称为同步方法。对于synchronized修饰的实例方法(非static方法)而言,无须显示指定同步监视器,同步方法的同步监视器是this,也就是调用改方法的对象。
    通过使用同步方法可以非常简单的实现线程安全的类,线程安全的类具有如下特征:

    • 该类的对象可以被多个线程安全地访问
    • 每个线程调用该对象地任意方法之后都可以得到正确结果。
    • 每个线程调用该对象地任意方法只有,该对象状态依然保持合理状态。

    为了减少线程安全所带来的负面影响,程序可以采用如下策略:

    • 不要对线程安全类的所有方法都进行同步,只有那些会改变竞争资源(竞争资源也就是共享资源)的方法进行同步。
    • 如果可变类有两种运行环境:单线程环境和多线程环境,则应该为该可变类提供两种版本,即线程不安全和先线程安全版本。在单线程环境中使用线程不安全版本以保证性能,在多线程环境中使用线程安全版本。如:StringBuilder和StringBuffer

    4.释放同步监视器的锁定

     任何线程进入同步代码块,同步方法之前,必须先获得对同步监视器的锁定,程序无法显式释放对同步监视器的锁定,线程会在如下几种情况下释放对同步监视器的锁定:

    • 当前线程的同步方法,同步代码块执行结束,当前线程即释放同步监视器。
    • 当前线程在同步代码块,同步方法中遇到break,return终止了该代码块,该方法的继续执行,当前线程将会释放同步监视器。
    • 当前线程在同步代码块,同步方法中出现了未处理的Error或Exception,导致了该代码块,该同步方法异常结束时,当前线程将会释放同步监视器。
    • 当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器。

    如下情况,线程不会释放同步监视器:

    • 线程执行同步代码块或同步方法时,线程调用了Thread.sleep(),Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器。
    • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放同步监视器。当然,程序应该尽量避免使用suspend()和resume()方法来控制线程。

    5.同步锁(Lock)

     从Java5开始,Java提供了一种功能更强大的线程同步机制——通过显式定义同步锁对象来实现同步,在这种机制下,同步锁由Lock()对象充当。
     Lock提供了比synchronized方法和synchronized代码块更广泛的锁操作,Lock允许实现更灵活的结构,可以具有很大的属性,并且支持多个相关的Condition对象。
     Lock是控制多个线程对共享i元进行访问的工具。通常,锁提供了对共享资源的独立访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
     在实现线程安全的控制中,比较常用的是RenntrantLock(可重入锁)。使用该Lock可以显式加锁,释放锁。

    public class ReentrantLockClass {
    
        private final ReentrantLock lock = new ReentrantLock();
    
        public void m() {
            //加锁
            lock.lock();
            try {
    
                //需要保证线程安全的代码
                // method code
            } finally {
                //释放锁
                lock.unlock();
            }
        }
    }
    

    Lock提供了同步方法和同步代码没有的其他功能,包括用户非块结构的tryLock()方法,以及试图获取可中断锁的lockInterruptibly()方法。

    6.死锁

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

    public class A {
    
        public synchronized void a1(B b) {
            System.out.println("当前线程:" + Thread.currentThread().getName() + ",进入A实例a1()方法");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("当前线程:" + Thread.currentThread().getName() + ",企图调用B实例b2()方法");
            b.b2();
        }
    
        public synchronized void a2() {
            System.out.println("当前线程:" + Thread.currentThread().getName() + ",进入A实例a2()方法");
        }
    
    }
    
    public class B {
        public synchronized void b1(A a) {
            System.out.println("当前线程:" + Thread.currentThread().getName() + ",进入B实例b1()方法");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("当前线程:" + Thread.currentThread().getName() + ",企图调用A实例a2()方法");
            a.a2();
        }
    
        public synchronized void b2() {
            System.out.println("当前线程:" + Thread.currentThread().getName() + ",进入B实例b2()方法");
        }
    
    }
    
    public class DeadLock implements Runnable {
    
        A a = new A();
        B b = new B();
    
        public void init() {
            a.a1(b);
        }
    
        @Override
        public void run() {
            b.b1(a);
        }
    
        public static void main(String[] args) {
            DeadLock deadLock = new DeadLock();
            new Thread(deadLock).start();
            deadLock.init();
        }
    }
    

    程序阻塞,输出结果:

    当前线程:main,进入A实例a1()方法
    当前线程:Thread-0,进入B实例b1()方法
    当前线程:main,企图调用B实例b2()方法
    当前线程:Thread-0,企图调用A实例a2()方法
    

    注意:由于Thread类得suspend()方法也很容易导致死锁,所以Java不在推荐使用该方法来暂停线程的执行。

    文章内容均取自《疯狂Java讲义-李刚》一书中多线程章节。截取重要知识点作为笔记记录,方便自己回顾。

  • 相关阅读:
    .NET正则基础之——平衡组
    正则基础之——贪婪与非贪婪模式
    正则应用之——日期正则表达式
    文件指针/句柄(FILE*)、文件描述符(fd)以及 文件路径(filepath)的相互转换(完整版,收集,整理)
    linux c 发送邮件
    select, poll和epoll的区别(转)
    linux c 中文支持
    修改远程桌面连接端口(PortNumber)
    libhdfs编译,安装,配置,使用
    C语言字节对齐详解
  • 原文地址:https://www.cnblogs.com/everyingo/p/12795664.html
Copyright © 2011-2022 走看看