博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
线程安全
阅读量:4316 次
发布时间:2019-06-06

本文共 17341 字,大约阅读时间需要 57 分钟。

前言:学习笔记,以供参考

最近学习并发编程,并发编程肯定和多线程、线程安全有关系,那么下面是我总结了自己学习线程安全的笔记!!!

1.线程安全的概念

  多个线程访问某一个类(对象或方法)时,这个类始终都能保持正确的行为,那么这个类(对象或方法)是线程安全的。

2.synchronized

  可以在任意对象和方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。

  a.示例

package com.wp.test;public class MyThread extends Thread {    private int count = 5;    public synchronized void run(){        count--;        System.out.println(this.currentThread().getName()+" count="+count);    }    public static void main(String args[]){        MyThread myThread = new MyThread();        Thread t1 = new Thread(myThread,"t1");        Thread t2 = new Thread(myThread,"t2");        Thread t3 = new Thread(myThread,"t3");        Thread t4 = new Thread(myThread,"t4");        Thread t5 = new Thread(myThread,"t5");        t1.start();        t2.start();        t3.start();        t4.start();        t5.start();    }}
View Code

 

  b.示例总结

  如果run方法前没有加关键字“synchronized”,那么对于多个线程操作count变量,是不安全的。加上“synchronized”时,那么线程是安全的;当多个线程访问run方法时,以排队的方式进行处理(此处的排队是按照CPU分配的先后顺序而定),一个线程想要执行这个synchronized修饰的run方法,首先要获得锁,如果获取不到,便会一直等到获取这把锁为止,此时,如果有多个线程,那么多个线程会竞争这把锁,这时会有锁竞争问题。锁竞争问题很导致应用程序非常慢。

3.对象锁和类锁

  多个线程,每个线程都可以拿到自己指定的锁,分别获得锁之后执行锁定的内容。此处有两个概念:对象锁和类锁,示例总结将做解释。

  a.示例

package com.wp.test; /**  * 关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当作锁  * 所以代码中哪个线程先执行synchronized对应的方法,该线程就持有该方法所属对象的锁(LOCK)  *   * 在静态方法前加关键字synchronized,表示该方法的锁是类级别的锁。 *  */public class MultiThread {    private  static int num = 0;    /* static **/    public static synchronized void printNum(String tag){        try{            if("a".equals(tag)){                num = 100;                System.out.println("tag a!set number over!");                Thread.sleep(1000);            }else{                num = 200;                System.out.println("tag b! set number over!");            }            System.out.println("num="+num);        }catch(Exception e){            e.printStackTrace();        }    }    //注意观察run方法输出顺序    public static void main(String args[]){        //两个不同的对象        final MultiThread m1 = new MultiThread();        final MultiThread m2 = new MultiThread();        Thread t1 = new Thread(new Runnable() {                        @Override            public void run() {                m1.printNum("a");            }        });        Thread t2 = new Thread(new Runnable() {                        @Override            public void run() {                m2.printNum("b");            }        });        t1.start();        t2.start();    }}
View Code

 

  执行结果:

1. 无static:tag a!set number over!tag b! set number over!num=200num=1002.有statictag a!set number over!num=100tag b! set number over!
View Code

 

  b.示例总结

  代码中线程取得的锁都是对象锁,而不是把一段代码(或方法)当作锁,对象锁的特点是不同的对象对应着不同的锁,互不影响。在静态方法上加上关键字synchronized,表示锁定的是.class类,属于类级别的锁。

4.对象锁的同步和异步

  a.同步synchronized

  同步的概念就是是共享资源,如果多个线程共享资源,那么就有必要进行同步了。

  b.异步asynchronized

  异步的概念是独立,多个线程相互之间不受任何制约。

  c.同步的目的就是为了线程安全,对于线程安全,需要满足两个特性:1).原子性(同步)    2).可见性

  d.示例

 

package com.wp.test; /** * 对象锁的同步和异步问题 */public class MyObject {    public synchronized void method1(){        try {            System.out.println(Thread.currentThread().getName());            Thread.sleep(4000);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }    public void method2(){        System.out.println(Thread.currentThread().getName());    }    public static void main(String args[]){        final MyObject mo = new MyObject();        Thread t1 = new Thread(new Runnable() {                        @Override            public void run() {                mo.method1();            }        },"t1");        Thread t2 = new Thread(new Runnable() {                        @Override            public void run() {                mo.method2();            }        },"t2");        t1.start();        t2.start();    }}
View Code

  示例总结:若t1线程先持有Object对象锁,t2线程如果这个时候要调用对象中的同步(synchronized)方法则需要等待t1释放锁,才可以调用,也就说同步了;若t1线程先持有Object对象锁,t2线程这个时候调用异步(非synchronized修饰)的方法,则会立即调用,t1和t2之间没有影响,也就说是异步了。

 5.脏读

  对于对象的同步和异步方法,我们在设计的时候一定要考虑问题的整体性,不然就会出现数据不一致的错误,很经典的一个错误就是脏读。

  a.示例

package com.wp.test;public class DirtyRead {    private String uname = "sxt";    private String pwd = "123";    public synchronized void setValue(String uname,String pwd){        try {            this.uname = uname;            Thread.sleep(2000);            this.pwd = pwd;            System.out.println("setValue设置的值:uname="+uname+" pwd="+pwd);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }    public void getValue(){        System.out.println("getValue的最终值:uname="+uname+" pwd="+pwd);    }    public static void main(String args[]) throws Exception{        final DirtyRead dr = new DirtyRead();        Thread t = new Thread(new Runnable() {                        @Override            public void run() {                dr.setValue("wangping", "456");            }        });        t.start();        Thread.sleep(1000);        dr.getValue();    }}/**getValue的最终值:uname=wangping pwd=123  setValue设置的值:uname=wangping pwd=456*/
View Code

 

  b.示例分析

  pwd不一致,解决方法:getValue方法加关键字synchronized。加上对象锁之后,等到锁被释放才可以获得锁。保持业务数据一致性。

  c.示例总结

  当我在给对象的方法加锁的时候,一定要考虑业务的整体性,即示例中setValue和getValue方法都加synchronized锁住,就保证了业务的原子性,保证业务不会出错。

  d.oracle关系型数据库,一致性读实现原理

  案例描述:oracle,用户A,9点查询某条数据(100),9:10才可以查到所需数据。而用户B在9:05执行了DML操作,那么A所查的数据在数据库已经变化(200)。那么A在9:10查到的结果是100还是200?答案是:100。

      原因:oracle数据库有一致性读的特性。B在update时,会将之前的数据保存到undo中做记录。当A在9:00这一时刻查时,将这一动作保存到ITL中,当A在9:10查100数据时,会判断ITL中对该数据的动作是否更新(是否在9:00这一刻之后发生变化),如果更新了,那么会从100对应的undo中将之前的数据返回。所以,结果为100。

  Undo的作用:提供一致性读(Consistent Read)、回滚事务(Rollback Transaction)以及实例恢复(Instance Recovery)。

  详细解释:http://www.educity.cn/shujuku/1121393.html

6.synchronized锁重入

  关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个对象得到对象锁之后,不释放锁,还可以再次获得该对象的锁,称为锁重入。

  a.示例1:方法锁重入

package com.wp.test;public class SyncDubbo1 {    public synchronized void method1(){        System.out.println("method1------------------");        method2();    }    public synchronized void method2(){        System.out.println("method2------------------");        method3();    }    public synchronized void method3(){        System.out.println("method3------------------");    }    public static void main(String args[]){        final SyncDubbo1 s = new SyncDubbo1();        Thread t = new Thread(new Runnable() {            @Override            public void run() {                s.method1();            }        });        t.start();    }}/**结果: * method1----------------- * method2------------------* method3------------------*/
View Code

 

  示例2:子类方法锁重入

package com.wp.test;public class SyncDubbo2 {    static class Main{        public int i = 10;        public synchronized void operationSup(){            try{                i--;                System.out.println("Main print i="+i);                Thread.sleep(100);            }catch(Exception e){                e.printStackTrace();            }        }    }    static class Sub extends Main{        public synchronized void operationSub(){            try{                while(i>0){                    i--;                    System.out.println("Sub print i="+i);                    Thread.sleep(100);                    this.operationSup();                }            }catch(Exception e){                e.printStackTrace();            }        }    }    public static void main(String args[]){        Thread t = new Thread(new Runnable() {            @Override            public void run() {                Sub s = new Sub();                s.operationSub();            }        });        t.start();    }}
View Code

 

  结果:

Sub print i=9Main print i=8Sub print i=7Main print i=6Sub print i=5Main print i=4Sub print i=3Main print i=2Sub print i=1Main print i=0
View Code

 

  示例3:锁中的内容出现异常

  对于web应用程序,异常释放锁的情况,如果不特殊处理,那么业务逻辑会出现很严重的错,比如执行一个队列任务,很对任务对象都在等待第一个对象正确执行完之后释放锁,但是执行第一个对象时出现了异常,导致剩余任务没有执行,那么业务逻辑就会出现严重错误,所以在设计代码的时候一定要慎重考虑。

  解决方式:出现异常时,捕获异常后通过记录异常信息到日志文件,然后剩余任务对象继续执行,那么整个任务执行完之后,再对出现异常的那个任务对象进行处理,从而不会影响其他任务对象的执行。

package com.wp.test;public class SyncException {    private int i = 0;    public synchronized void operation(){        while(true){            try{                i++;                Thread.sleep(200);                System.out.println(Thread.currentThread().getName()+",i="+i);                if(i==3){                    Integer.parseInt("a");//throw RuntimeException                }            }catch(Exception e){                e.printStackTrace();                System.out.println("log info i="+i);            }        }    }    public static void main(String args[]){        final SyncException se = new SyncException();        Thread t = new Thread(new Runnable() {            @Override            public void run() {               se.operation();            }        });        t.start();    }}
View Code

 

  结果:执行方法的时候,出现异常,不会释放锁,业务继续执行。执行完之后,对发生的异常进行处理。比如说存储过程:当更新某张表的数据时,出现异常,那么将异常信息保存到日志表中,稍后进行处理,而使得该表的数据继续更新。

Thread-0,i=1Thread-0,i=2Thread-0,i=3java.lang.NumberFormatException: For input string: "a"log info i=3    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)    at java.lang.Integer.parseInt(Integer.java:492)    at java.lang.Integer.parseInt(Integer.java:527)    at com.wp.test.SyncException.operation(SyncException.java:11)    at com.wp.test.SyncException$1.run(SyncException.java:24)    at java.lang.Thread.run(Thread.java:745)Thread-0,i=4Thread-0,i=5Thread-0,i=6
View Code

 

7.synchronized关键字

  使用synchronized声明的方法在某些情况下是有弊端的,比如A线程调用同步的方法执行一个很长时间的任务,那么B线程就必须等待同样长的时间才能执行,这样的情况下可以使用synchronized代码块去优化代码执行时间,通常说,减小了锁的粒度。

  Synchronized可以使用任意的Object进行加锁,用法比较灵活。

  特别注意,就是不要使用String的常量加锁,会出现死循环问题。

  锁对象改变问题:当使用一个对象进行加锁的时候,要注意对象本身是否发生变化(地址),如果发生变化,那么就会释放锁。例如,如果是字符串的锁,当字符串改变后就会释放锁。但是,对象的属性发生变化,不会有影响的。

  a.示例1:synchronized代码块

package com.wp.test;public class ObjectLock {    public void method1(){        synchronized(this){            try {                System.out.println("do method1...");                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    public void method2(){        synchronized(ObjectLock.class){            try {                System.out.println("do method2...");                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }        private Object lock = new Object();    public void method3(){        synchronized(lock){            try {                System.out.println("do method3...");                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    public static void main(String args[]){        final ObjectLock ol = new ObjectLock();        Thread t1 = new Thread(new Runnable() {            @Override            public void run() {                ol.method1();            }        });        Thread t2 = new Thread(new Runnable() {            @Override            public void run() {                ol.method2();            }        });        Thread t3 = new Thread(new Runnable() {            @Override            public void run() {                ol.method3();            }        });        t1.start();        t2.start();        t3.start();    }}
View Code

 

  结果:

do method1...do method3...do method2...

  b.示例2:字符串锁改变

package com.wp.test;public class ChangeLock {    private String lock = "lock";    public void method(){        synchronized(lock){            try {                System.out.println("当前线程:"+Thread.currentThread().getName()+"开始");                lock = "lock1";                Thread.sleep(2000);                System.out.println("当前线程: "+Thread.currentThread().getName()+"结束");            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    public static void main(String args[]){        final ChangeLock cl = new ChangeLock();        Thread t1 = new Thread(new Runnable() {            @Override            public void run() {                cl.method();            }        },"t1");        Thread t2 = new Thread(new Runnable() {            @Override            public void run() {               cl.method();            }        },"t2");        t1.start();        try {            Thread.sleep(100);        } catch (InterruptedException e) {            e.printStackTrace();        }        t2.start();    }
View Code

 

  结果:字符串改变之后,会释放锁。

  示例3:

package com.wp.test;public class ModifyLock {    private String name;    private int age;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public synchronized void changeAttribute(String name,int age){        try {            System.out.println("当前线程:"+Thread.currentThread().getName()+"开始");            this.setName(name);            this.setAge(age);            System.out.println("当前线程:"+Thread.currentThread().getName()+"修改的内容为:"                    +this.getName()+","+this.getAge());            Thread.sleep(2000);            System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");        } catch (InterruptedException e) {            e.printStackTrace();        }    }    public static void main(String args[]){        final ModifyLock ml = new ModifyLock();        Thread t1 = new Thread(new Runnable() {            @Override            public void run() {                ml.changeAttribute("张三", 20);            }        },"t1");        Thread t2 = new Thread(new Runnable() {            @Override            public void run() {               ml.changeAttribute("李四", 21);            }        },"t2");        t1.start();        t2.start();    }}
View Code

 

  结果分析:如果对象内容变化,对象本身引用不发生变化,那么依然同步,属性变化,没有影响,依然同步。

8.volatile关键字

  当多个线程使用同一变量时,为了线程安全,通常我们会在访问变量的地方加一把锁,但是这样效率比较低。但是Volatile的效率相对高点。

  Volatile关键字的主要作用是使用变量在多个线程间可见。原理:强制线程到主内存里去读取变量,而不去线程工作内存区去读,那么当前线程使用的变量是最新的变量(不管其他线程有无修改),从而实现了多个线程间变量可见,也就是满足了线程安全的可见性。如果不明白,下面的示例,运行一下,就明白了。

package com.wp.test;public class RunThread extends Thread{    /**volatile*/    private volatile boolean isRunning = true;    public void setRunning(boolean isRunning){        this.isRunning = isRunning;    }    public void run(){        System.out.println("进入run方法。。");        while(isRunning == true){            //...        }        System.out.println("线程终止。。");    }    public static void main(String args[]) throws InterruptedException{        RunThread rt = new RunThread();        rt.start();        Thread.sleep(3000);        rt.setRunning(false);        System.out.println("isRunning的值已经设置成了false");        Thread.sleep(1000);        System.out.println(rt.isRunning);    }}
View Code

 

  结果分析:isRunning不用volatile修饰时,当主线程修改isRunning的值时,线程rt的内存中的isRunning副本不会变化;isRunning用volatile修饰时,当主线程修改isRunning的值时,会强制线程rt从内存中读取isRunning的值,那么rt内存里的isRunning也就发生了修改。

 

  Volatile关键字虽然拥有多个线程之间的可见性,但是却不具备同步性(也就是原子性),可以算得上一个轻量级的synchronized,性能要比synchronized强很多,不会造成阻塞(在很多开源的框架里,比如netty的底层代码就是大量使用volatile,可见netty性能非常不错。)这里需要注意,一般volatile用于只针对于多个线程可见的变量操作,并不能代替synchronized的同步功能。具体来说,volatile关键字只有可见性,没有原子性。要实现原子性,建议使用atomic类的系列对象,支持原子性操作(注意atomic类只保证本身方法原子性,并不保证多次操作的原子性)。用一个示例说明:

  示例1:volatile关键字只有可见性,没有原子性,建议使用atomic类的系列对象

package com.wp.test;import java.util.concurrent.atomic.AtomicInteger;public class VolatileNoAtomic extends Thread {    //private static volatile int count;    private static AtomicInteger count = new AtomicInteger();    private static void addCount(){        for(int i=0;i<1000;i++){            //count++;            count.incrementAndGet();        }        System.out.println(count);    }    public void run(){        addCount();    }    public static void main(String args[]){        VolatileNoAtomic[] arr = new VolatileNoAtomic[10];        for(int i=0;i<10;i++){            arr[i] = new VolatileNoAtomic();        }        for(int i=0;i<10;i++){            arr[i].start();        }    }}
View Code

  结果分析:当使用volatile修饰时,由于没有原子性,因此,(线程不安全)结果达不到10000;那么使用AtomicInteger时,具有原子性,结果正确为10000。

  示例2:atomic类只保证本身方法原子性,并不保证多次操作的原子性

package com.wp.test;import java.util.ArrayList;import java.util.List;import java.util.concurrent.atomic.AtomicInteger;public class AtomicUse {    private static AtomicInteger count = new AtomicInteger(0);    //多个addAndGet在一个方法内是原子性的,需要加synchronized进行修饰,保证4个addAndGet整体原子性    /**synchronized*/    public synchronized int multiAdd(){        try {            Thread.sleep(100);        } catch (InterruptedException e) {            e.printStackTrace();        }        count.addAndGet(1);        count.addAndGet(2);        count.addAndGet(3);        count.addAndGet(4);        return count.get();    }    public static void main(String args[]){        final AtomicUse au = new AtomicUse();        List
ts = new ArrayList
(); for(int i=0;i<100;i++){ ts.add(new Thread(new Runnable() { @Override public void run() { System.out.println(au.multiAdd()); } })); } for(Thread t : ts){ t.start(); } }}
View Code

  结果分析:多个addAndGet在一个方法内是原子性的,需要加synchronized进行修饰,保证4个addAndGet整体原子性。不加,每次加的值不是整10,加的话是整10。

转载于:https://www.cnblogs.com/zhanxiaoyun/p/6100975.html

你可能感兴趣的文章
[Android]做android蛮有用的一个技巧
查看>>
Swift - defer关键字(推迟执行)
查看>>
LintCode "Coins in a Line"
查看>>
Windows 批处理bat程序设计简明教程
查看>>
Selenium之前世今生
查看>>
High Five Lintcode
查看>>
【linux就该这么学】-03
查看>>
文件资源下载到本地后如何调用
查看>>
K2BPM怎么让金融数据更有意义?
查看>>
AndroidManifest Ambiguity方案原理及代码
查看>>
目前的小幸福。
查看>>
MSSQL2008 常用sql语句
查看>>
图片里的文字转换成word
查看>>
远程多台服务器备份数据方案
查看>>
Linux与Windows协同工作
查看>>
After 66 days of writing
查看>>
OSPF理论
查看>>
史玉柱自述:我是如何带队伍的
查看>>
POJ 3984 迷宫问题(BFS)
查看>>
eclipse快捷键大全
查看>>