java并发编程之synchronized关键字(五)

原博文地址:http://www.cnblogs.com/dolphin0520/p/3923737.html

1.什么是线程安全问题:

1)多个线程同时访问一个资源时,会导致程序的运行结果与预测的并不一致。这个资源在这里成为临界资源or共享资源【一个对象,对象中的属性,一个文件,一个数据库等】。

eg:用户注册的时候,不能往数据库里面插入相同的名称M,如果Thread-1和Thread-2同时读到Yes【即可以插入】,那么这个线程就会将数据插入到数据库。

这里就产生了一个隐患了,假若Thread-1和Thread-2的数据M不一样就没问题。要是刚好一样,这样数据库数据就出现问题了。这时候就产生了线程安全问题。

2)当多个线程执行一个方法时,方法内部的局部变量并不是临界资源,因为方法是在栈上执行的,而Java栈是线程私有的,因此不会产生线程安全问题。

2.解决线程安全问题:

1)通常来说,都是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。即在同一个时刻只有一个线程能够访问临界资源,术语上就是:同步互斥访问。

2)java中使用synchronized【同步】和Lock【锁】来实现同步互斥访问。

3.synchronized关键字:

1)在Java中,每一个对象都拥有一个标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问

2)在java中,可以用synchronized来标记一个方法或者代码块,

3)当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。【synchronized作用原理】

4.synchronized方法块:

1)加入synchronized前:

 

public class Test {
	 
    public static void main(String[] args)  {
        final InsertData insertData = new InsertData();
        new Thread() {
            public void run() {
                insertData.insert(Thread.currentThread());
            };
        }.start();
        new Thread() {
            public void run() {
                insertData.insert(Thread.currentThread());
            };
        }.start();
    }  
}
 
class InsertData {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    public void insert(Thread thread){
        for(int i=0;i<5;i++){
            System.out.println(thread.getName()+"在插入数据"+i);
            arrayList.add(i);
        }
    }
}

结果:

 

结论:两个线程在同时执行insert方法。

2)加入synchronized关键字后:

 

class InsertData {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    public synchronized void insert(Thread thread){
        for(int i=0;i<5;i++){
            System.out.println(thread.getName()+"在插入数据"+i);
            arrayList.add(i);
        }
    }
}

结果:

 

结论:

1)Thread-1插入数据是等Thread-0插入完数据之后才进行的。说明Thread-0和Thread-1是顺序执行insert方法的。

注意点:

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

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

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

5.synchronized代码块:

1)公式如下:

 

synchronized(synObject) {
         
    }

2)当在某个线程中执行这段代码块,该线程会获取对象synObject的锁,从而使得其他线程无法同时访问该代码块。  

 

3)synObject可以是this,代表获取当前对象的锁,也可以是类中的一个属性,代表获取该属性的锁。

4)synchronized代码块可以进行如下改造:

 

class InsertData {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
     
    public void insert(Thread thread){
        synchronized (this) {
            for(int i=0;i<100;i++){
                System.out.println(thread.getName()+"在插入数据"+i);
                arrayList.add(i);
            }
        }
    }
}

class InsertData {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Object object = new Object();
     
    public void insert(Thread thread){
        synchronized (object) {
            for(int i=0;i<100;i++){
                System.out.println(thread.getName()+"在插入数据"+i);
                arrayList.add(i);
            }
        }
    }
}


好处:

 

1)synchronized代码块使用起来比synchronized方法要灵活得多。因为也许一个方法中只有一部分代码只需要同步,如果此时对整个方法用synchronized进行同步,会影响程序执行效率。而使用synchronized代码块就可以避免这个问题,synchronized代码块可以实现只对需要同步的地方进行同步。

2)每个类也会有一个锁,它可以用来控制对static数据成员的并发访问。

3)如果一个线程执行一个对象的非static synchronized方法,另外一个线程需要执行这个对象所属类的static synchronized方法,此时不会发生互斥现象,因为访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁,所以不存在互斥现象。

例子如下:

 

public class Test {
 
    public static void main(String[] args)  {
        final InsertData insertData = new InsertData();
        new Thread(){
            @Override
            public void run() {
                insertData.insert();
            }
        }.start(); 
        new Thread(){
            @Override
            public void run() {
                insertData.insert1();
            }
        }.start();
    }  
}
 
class InsertData { 
    public synchronized void insert(){
        System.out.println("执行insert");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行insert完毕");
    }
     
    public synchronized static void insert1() {
        System.out.println("执行insert1");
        System.out.println("执行insert1完毕");
    }
}

结果:

 

结论:

1)第一个线程里面执行的是insert方法,不会导致第二个线程执行insert1方法发生阻塞现象。

注意点:

1)对于synchronized方法或者synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值