线程安全类笔记

线程安全的定义:当多个线程访问某个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是                              线程安全的。

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

简单synchronized例子:

public class FirstThread extends Thread {

    private int count = 5;

    @Override
    public synchronized void run() {
        count--;
        System.out.println(this.currentThread().getName()+"count = "+count);
    }

    public static void main(String[] args) {
        FirstThread ft = new FirstThread();
        Thread t1 = new Thread(ft,"t1");
        Thread t2 = new Thread(ft,"t2");
        Thread t3 = new Thread(ft,"t3");
        Thread t4 = new Thread(ft,"t4");
        Thread t5 = new Thread(ft,"t5");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

 运行结果:

t3count = 4
t5count = 3
t1count = 2
t2count = 1
t4count = 0

总结:当多个线程访问run方法时,以排队的方式进行处理(这里排队指的是按照CPU分配的先后顺序而定的),一个线程要执行synchronized修饰的方法时,首先去尝试获得锁,若拿到锁,则执行synchronized代码体内容;拿不到锁,这个线程就会不断地尝试获得这把锁,直至拿到为止,而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)。

多个线程多个锁:多个线程,每个线程都可以拿到自己指定的锁,分别获得锁之后,执行synchronized方法体的内容。

代码例子:

public class SecondThread {

    private static int count = 0;

    public static synchronized void printCount(String st) {
        try{
            if (st.equals("a")){
                count = 100;
                System.out.println(" a  ,set count end !!!");
                Thread.sleep(1000);
            }else{
                count = 200;
                System.out.println(" b  ,set count end !!!");
                Thread.sleep(1000);
            }
            System.out.println("st = "+st+",count = "+count);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        //两个对象都要在线程的run方法中调用,所以用final修饰
        final SecondThread st1 = new SecondThread();
        final SecondThread st2 = new SecondThread();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                st1.printCount("a");
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                st2.printCount("b");
            }
        });
        t1.start();
        t2.start();
    }
}

运行结果:

 a  ,set count end !!!
st = a,count = 100
 b  ,set count end !!!
st = b,count = 200

总结:synchronized关键字取得的锁都是对象锁,而不是把一段代码(方法)当作锁,所以代码中只加synchronized关键字时,两个不同的对象会同时去访问synchronized修饰的方法,即两个对象获得的是两个不同的锁,并且互不影响。而在前面加上static关键字后,获得的锁是类级别的锁,即两个对象可以获得相同的锁。

同步:synchronized。同步的概念就是共享。如果不是共享的资源,就没有必要进行同步。

异步:asynchronized。异步的概念就是独立,相互之间不受到任何制约。有点类似于ajax异步请求。

同步的目的是为了线程安全。对于线程安全,需要满足两个特性:

原子性(同步)和可见性

public class ThirdThread {

    public synchronized void method1() {
        try{
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(2000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public synchronized void method2() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        final ThirdThread tt = new ThirdThread();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                tt.method1();
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                tt.method2();
            }
        },"t2");
        t1.start();
        t2.start();
    }
}

总结:两个线程取的是同一个对象,及tt的锁,当t1获得锁之后,t2会处于等待状态,即method1执行完成之后method2才执行。

脏读:对于对象的同步和异步的方法,在设计程序的时候,一定要考虑问题的整体,不然就会出现数据不一致的错误,很经典的错误就是脏读(dirtyread)

例子:

public class FourthThread {

    private String userName = "name";
    private String passWord = "pw";
    public synchronized void setValue(String userName,String passWord){
        this.userName = userName;

        try{
            Thread.sleep(2000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        this.passWord = passWord;
        System.out.println("setValue结果:userName = "+userName+",passWord = "+passWord);
    }

    public synchronized void getValue(){
        System.out.println("getValue结果:userName = "+this.userName+",passWord = "+this.passWord);
    }

    public static void main(String[] args) throws InterruptedException {
        final FourthThread ft = new FourthThread();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                ft.setValue("username2","password2");
            }
        });
        t1.start();
        Thread.sleep(1000);
        ft.getValue();
    }
}

运行结果:

setValue结果:userName = username2,passWord = password2
getValue结果:userName = username2,passWord = password2

总结:在对一个对象的方法加锁的时候,需要考虑业务的整体性,即为setValue/getValue方法同时加锁synchronized关键字,保证业务(service)的原子性,不然会出现业务错误(也从侧面保证业务的一致性)。

synchronized锁重入:关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到了一个对象的锁后,再次请求此对象时是可以再次得到该对象的锁。

例子:

public class FifthThread {

    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) throws InterruptedException {
        final FifthThread ft = new FifthThread();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                ft.method1();
            }
        });
        t1.start();
    }
}

运行结果:

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

例子:

public class SixthThread {

    static class Main{
        public int i = 10;
        public synchronized void operationSup(){
            try {
                i--;
                System.out.println("Main print i = "+i);
                Thread.sleep(100);
            }catch (InterruptedException 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 (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                Sub sub = new Sub();
                sub.operationSub();
            }
        });
        t1.start();
    }
}

运行结果:

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

出现异常,锁自动释放。

例子:

public class SeventhThread {

    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 == 10){
                    Integer.parseInt("a");
                }
            }catch (Exception e){
                e.printStackTrace();
                System.out.println(" log info i = "+i);
            }
        }
    }

    public static void main(String[] args) {
        final SeventhThread st = new SeventhThread();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                st.operation();
            }
        },"t1");
        t1.start();
    }
}

运行结果:

t1 ,  i = 1
t1 ,  i = 2
t1 ,  i = 3
t1 ,  i = 4
t1 ,  i = 5
t1 ,  i = 6
t1 ,  i = 7
t1 ,  i = 8
t1 ,  i = 9
t1 ,  i = 10
java.lang.NumberFormatException: For input string: "a"
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:580)
	at java.lang.Integer.parseInt(Integer.java:615)
 log info i = 10
	at com.SeventhThread.operation(SeventhThread.java:13)
	at com.SeventhThread$1.run(SeventhThread.java:27)
	at java.lang.Thread.run(Thread.java:745)
t1 ,  i = 11
t1 ,  i = 12

总结:对于web应用程序,异常释放锁的情况,如果不及时处理,很可能对你的应用程序业务逻辑产生严重的错误,比如你现在执行一个队列任务,很多对象都去在等待第一个对象正确执行完毕再去释放锁,但是第一个对象由于异常的出现,导致业务逻辑没有正常执行完毕,就释放了锁,那么可想而知后续的对象执行的都是错误的逻辑。所以这一点一定要引起注意,在编写代码的时候,一定要考虑周全。

synchronized代码块:

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值