码迷,mamicode.com
首页 > 编程语言 > 详细

并发4-线程安全

时间:2016-05-13 02:25:49      阅读:165      评论:0      收藏:0      [点我收藏+]

标签:

1.线程安全问题:

在多线程中,有可能出现多个线程同时使用同一个资源的情况,这个资源可以是变量,数据表,txt文件等。这个资源称作"临界资源"

举个例子:取钱这个线程分为两个步骤:
1.读取金额
2.取款
3.更新金额

有个典型的线程安全的例子,倘若A,B两人使用同一个账户(1000元)取款,A执行

1.读取金额    2.取款,取出300元,并未更新金额。
此时,
B读取金额,显示为1000(应该为700),B取出1000元。
最后,A执行3.更新金额,B执行3.更新金额。

如前面所说,多个线程访问临界变量,就会产生线程安全问题。不过,当多个线程执行同一个方法时,方法内部的变量不是临界资源,
因为每个线程都有自己独立的内存区域(PC,方法栈,线程栈)   

2.解决线程安全问题:

基本所有的并发方案,都采用“序列化访问资源”,也就是在同一时间,只有一个线程能访问临界资源,也称作同步互斥访问

方案1:synchronized

在Java中,每个对象都有一个锁标记,称为monitor(监视器),多个线程访问这个对象的临界资源时,只有获取了该对象的锁才能访问

在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

public class ThreadLock2{

    public static void main(String[] agrs) {

        final InsertData insertData = new InsertData();
        new Thread("线程1"){
            @Override
            public void run() {                insertData.insert(Thread.currentThread());
            }

        }.start();

        new Thread("线程2"){
            @Override
            public void run() {
            insertData.insert(Thread.currentThread());
            }
        }.start();
    }
}
class InsertData extends Thread {

    private List<Integer> list = new ArrayList<>();
    //在这其实是锁住了InsertData对象的monitor锁
    public synchronized void insert(Thread thread) {
        for (int i = 0; i < 5; i++) {
            System.out.println(thread.getName() + "插入: " + i);
            list.add(i);
        }
    }
}
输出结果:    

线程1插入: 0
线程1插入: 1
线程1插入: 2
线程1插入: 3
线程1插入: 4
线程2插入: 0
线程2插入: 1
线程2插入: 2
线程2插入: 3
线程2插入: 4

试着把  public synchronized void insert(Thread thread)  中的 synchronized去掉,再看看结果

此时,线程1和线程2可能会交叉运行,因为insert这个方法不再是临界资源了(允许多个线程同时进入) 

insert方法还可以改成以下两种方式:


class InsertData extends Thread {

    private List<Integer> list = new ArrayList<>();
    public void insert(Thread thread) {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                System.out.println(thread.getName() + "插入: " + i);
                list.add(i);
            }
        }
    }
}

class InsertData extends Thread {

    private List<Integer> list = new ArrayList<>();
    private Object object = new Object();

    public synchronized void insert(Thread thread) {
        synchronized (object) {
            for (int i = 0; i < 5; i++) {
                System.out.println(thread.getName() + "插入: " + i);
                list.add(i);
            }
        }
    }
}
说明:

    1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,
    因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。

    2)当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,
    访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是
    可以访问这个方法的,

    3)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使
    object1和object2是同一类型的对象,也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。

synchronized的另外一种使用方式:

public class MyThread{

    private Object lock = new Object();
    private ArrayList<Integer> list = new ArrayList<>();

    public static void main(String[] args) {
        MyThread mh = new MyThread();
        ThreadTest tt = mh.new ThreadTest();
        ThreadTest tt2 = mh.new ThreadTest();
        tt.start();
        tt2.start();
    }

    class ThreadTest extends Thread{
        @Override
        public void run() {
            synchronized (lock) {
                for(int i=0;i<5;i++){
                    list.add(i)
                System.out.println(Thread.currentThread().getName()
                            + "insert:" +i);
                }
            }
        }
    }
}
输出结果:

Thread-0insert:0
Thread-0insert:1
Thread-0insert:2
Thread-0insert:3
Thread-0insert:4
Thread-1insert:0
Thread-1insert:1
Thread-1insert:2
Thread-1insert:3
Thread-1insert:4

注意事项:

static方法,使用的是类锁(多个对象使用同一个类的monitor)

和非static方法,使用的是对象锁(每个对象维护一个monitor)

synchronized的缺陷:

1.使用synchronized包住的代码块,只可能有两种状态:顺利执行完毕释放锁,执行发生异常释放锁,不会由于异常导致出现死锁现象

2.如果synchronized包住的代码块中有sleep等操作,比如I/O阻塞,但是其他线程还是需要等待,这样程序的效率就比较低了

3.等待synchronized释放锁的线程会一直等待下去(死心塌地,不到黄河心不死)

并发4-线程安全

标签:

原文地址:http://blog.csdn.net/liuyiling_xm610/article/details/51340928

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!