zoukankan      html  css  js  c++  java
  • 线程同步

    简介

    概念

    线程不安全:不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
    线程安全:多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

    线程安全问题都是由全局变量静态变量引起的。
    若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

    作用

    好处:保证了多线程并发的同步操作,避免了线程的安全性问题。

    缺点:性能比不用的低(平时StringBuilder相对比StringBuffer用到多)。

    源码

        @Override
        public StringBuilder append(String str) {
            super.append(str);
            return this;
        }
    
        @Override
        public synchronized StringBuffer append(String str) {
            toStringCache = null;
            super.append(str);
            return this;
        }

    例子

    卖票:

    public class MyThread implements Runnable {
        private int n = 20;//每个线程共享20张票
    
        public MyThread() {
        }
    
        public void run() {
            while (n > 0) {
                if (n > 0) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " sales NO." + n);
                        Thread.sleep(10);//模拟网络延迟
                        n--;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    public class MainThread {
        public static void main(String[] args) {
            MyThread w = new MyThread();
            Thread t1=new Thread(w,"Window1");
            t1.start();
            Thread t2=new Thread(w,"Window2");
            t2.start();
        }
    }

    结果:一张票怎么可能卖两次?

    Window1 sales NO.20
    Window2 sales NO.20
    Window1 sales NO.19
    Window2 sales NO.19
    Window2 sales NO.18
    Window1 sales NO.17
    Window2 sales NO.15
    Window1 sales NO.15
    Window1 sales NO.13
    Window2 sales NO.13
    Window1 sales NO.11
    Window2 sales NO.11
    Window1 sales NO.10
    Window2 sales NO.9
    Window2 sales NO.8
    Window1 sales NO.8
    Window1 sales NO.7
    Window2 sales NO.7
    Window2 sales NO.6
    Window1 sales NO.5
    Window1 sales NO.3
    Window2 sales NO.3
    Window2 sales NO.2
    Window1 sales NO.2
    Window2 sales NO.1

    同步锁

    为了保证每个线程都能正常执行原子操作,java引入了线程同步机制。

    同步监听对象/同步锁/同步监听器/互斥锁:

    对象的同步锁只是一个概念,可以想象在对象上标记了一个锁,

    java程序运行使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为同步监听对象。

    注意:在任何时候,最多允许一个线程用过同步锁(内置锁),谁拿到锁谁进入代码块,其他线程只能等待(堵塞)。

     正确结果:(1和2顺序看抢,票递减)

    Window1 sales NO.20
    Window1 sales NO.19
    Window1 sales NO.18
    Window1 sales NO.17
    Window1 sales NO.16
    Window1 sales NO.15
    Window1 sales NO.14
    Window2 sales NO.13
    Window2 sales NO.12
    Window2 sales NO.11
    Window2 sales NO.10
    Window2 sales NO.9
    Window2 sales NO.8
    Window2 sales NO.7
    Window2 sales NO.6
    Window2 sales NO.5
    Window2 sales NO.4
    Window2 sales NO.3
    Window2 sales NO.2
    Window2 sales NO.1

    线程安全常用方法

    1.同步代码块

    synchronized(同步锁){

         同步逻辑代码

    }

    注:同步是一种高开销的操作,因此应该尽量减少同步的内容/范围
    通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

    public class MyThread implements Runnable {
        private int n = 20;//每个线程共享20张票
    
        public MyThread() {
        }
    
        public void run() {
            while (n > 0) {
                synchronized (this) {//this代表MyThread对象,该对象属于多线程共享的资源
                    if (n > 0) {
                        try {
                            System.out.println(Thread.currentThread().getName() + " sales NO." + n);
                            Thread.sleep(10);//模拟网络延迟
                            n--;
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    2.同步方法

    synchronized  方法{

         同步逻辑代码

    }

    public class MyThread implements Runnable {
        private int n = 20;//每个线程共享20张票
    
        public MyThread() {
        }
    
        public void run() {
            while (n > 0) {
                sales();
            }
        }
    
        synchronized public void sales() {//同步的监听器是this表示MyThread对象
            if (n > 0) {
                try {
                    System.out.println(Thread.currentThread().getName() + " sales NO." + n);
                    Thread.sleep(10);//模拟网络延迟
                    n--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

      注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

    synchronized修饰的同步锁:

    对于非static方法,同步锁就是this

    对于static方法,同步锁就是当前方法所在类的字节码对象(类.class)

    3.重入锁(Lock)机制

    创建一个锁实例lockinstance

    方法{

         实例lockinstance.lock();

         同步逻辑代码

         实例lockinstance.unlock();

    }

    import java.util.concurrent.locks.ReentrantLock;
    
    public class MyThread implements Runnable {
        private int n = 20;//每个线程共享20张票
        private ReentrantLock lock = new ReentrantLock();//创建一个ReentrantLock实例
    
        public MyThread() {
        }
    
        public void run() {
            while (n > 0) {
                sales();
            }
        }
    
        public void sales() {
            lock.lock();//得到锁
            if (n > 0) {
                try {
                    System.out.println(Thread.currentThread().getName() + " sales NO." + n);
                    Thread.sleep(10);//模拟网络延迟
                    n--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();//释放锁
                }
            } else {
                if (lock.isLocked()) {
                    lock.unlock();
                    System.out.println("n=0也解锁了");
                }
            }
        }
    }

    在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。 

    synchronized修饰的功能,lock机制都有,除此之外lock更强大,更面向对象。

     注:关于Lock对象和synchronized关键字的选择: 
            a.最好两个都不用,使用一种java.util.concurrent包提供的机制, 
                能够帮助用户处理所有与锁相关的代码。 
            b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 
            c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 

  • 相关阅读:
    40. Combination Sum II
    39. Combination Sum
    找一找
    37. Sudoku Solver
    Activiti 多个并发子流程的应用
    BPMN2新规范与Activiti5
    BPMN这点事-BPMN扩展元素
    JAVA规则引擎 -- Drools
    工作流Activiti5流程变量 任务变量 setVariables 跟 setVariablesLocal区别
    activiti 学习( 三 ) 之 流程启动者
  • 原文地址:https://www.cnblogs.com/manusas/p/7141680.html
Copyright © 2011-2022 走看看