我之见--线程同步,锁

来源:互联网 发布:勇敢的心 知乎 编辑:程序博客网 时间:2024/06/07 01:06

为什么要进行同步?

线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。

线程同步有以下几点要注意:

多线程之间必须使用同一个对象,如果不是同一个,不存在同步问题。

对象必须是共享可变对象,共享对象意味着可以多个线程同时访问,而且必须是可变的对象,如果是不可变的对象也不存在同步问题。

说了这么多,下面我们来看一个简单的例子:

package javaThread;public class ThreadNoSyc {    public static void main(String[] args) {        ThreadData data = new ThreadData();        ThreadTest t1 = new ThreadTest("Thread 1" ,data );        ThreadTest t2 = new ThreadTest("Thread 2" ,data);                t1.start();        t2.start();    }    private static class ThreadTest extends Thread {        ThreadData mData;        String mName;        public ThreadTest(String name,ThreadData data) {            mData = data;            mName = name;        }        @Override        public void run() {            super.run();            for(int i = 0 ;i<4 ;i ++) {                                System.out.println(" name " + mName + " begint " + mData.getX());                try {                    Thread.sleep(3000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println(" name " + mName + " end" + mData.add(10));            }        }    }    private static class ThreadData {        private int num = 100;        public int getX() {            return num;        }        public int add(int i) {            num = num + i;            return num;        }    }}

输出:

name Thread 2 begint 100 name Thread 1 begint 100 name Thread 2 end110 name Thread 1 end120 name Thread 1 begint 120 name Thread 2 begint 120 name Thread 1 end130 name Thread 1 begint 130 name Thread 2 end140 name Thread 2 begint 140 name Thread 1 end150 name Thread 1 begint 150 name Thread 2 end160 name Thread 2 begint 160 name Thread 1 end170 name Thread 2 end180

从结果上面看出是有问题的,Thread 1 begint 100 等到end  的时候已经 120,因为中间Tread 2  对进行的对象进行操作了,

之所以出现上面不合理的情况,是因为没有加同步的原因。这样的程序每次输出的结果都会不一样的。那如何才能避免上面的情况,加同步锁。


进程同步和锁

1、锁的原理
Java中每个对象都有一个内置锁
当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。
当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。
一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。
释放锁是指持锁线程退出了synchronized同步方法或代码块。

关于锁和同步,有一下几个要点:
1)、只能同步方法,而不能同步变量和类;
2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?
3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法,同步还有一个原则:锁的范围是尽可能越小越好,锁的粒度越小越好。
4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
6)、线程睡眠时,它所持的任何锁都不会释放,锁必须手动释放,或者执行完方法才会释放
7)、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁,尽可能不要让一个线程同时持有两个及两个以上的锁,如果一个线程同时需求多个锁,如果其中一个锁不能满足就不会释放其他锁,这样会出现一个相互依赖,出现“死锁”
8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如:
    public int add(int i) {
        synchronized (this) {
            num = num - i;
        }
        return num;
    }
 
当然,同步方法也可以改写为非同步方法,但功能完全一样的,例如:
    public synchronized int getX() {
        return num;
    }
    public int getX() {
        synchronized (this) {
            return num;
        }
    }
效果是完全一样的。

静态方法同步

静态方法的同步和一般方法的同步不一样的,一般方法同步,synchronized 锁的对象(类本身this),但静态方法不一样,静态是通过类名就可以调用的,所以没有this对象,因此静态方法的同步的时候锁的.class 例如:A类的静态synchronized 方法,就是相当于:synchronized(A.class) ;

线程不能不能获得锁会怎么样?

如线程试图进入同步方法,而其锁已经被占用,则线程在该对象上被阻塞。实质上,线程进入该对象的的一种池中,必须在哪里等待,直到其锁被释放,该线程再次变为可运行或运行为止。但以下几点需要注意:
1、调用同一个对象中非静态同步方法的线程将彼此阻塞。但如果是不同对象,则每个线程有自己的对象的锁,线程间彼此互不干预(不同对象不存在资源共享问题,也不存在锁的问题)。
2、调用同一个类中的静态同步方法的线程将彼此阻塞,它们都是锁定在相同的Class对象上(锁相同对象进程才能互斥)。
3、静态同步方法和非静态同步方法将永远不会彼此阻塞,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。
4、对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将彼此阻塞,在不同对象上锁定的线程将永远不会彼此阻塞

线程安全类

当一个类已经很好的同步以保护它的数据时,这个类就称为“线程安全的”,或者还有一种说法:如果一个类能在多个线程中安全的使用,那么它就是一个线程安全类的;线程安全的真正意义:某个类的行为与共规范安全一致。但下面一点还是要注意:即使是线程安全类,也应该特别小心,因为操作的线程是间仍然不一定安全。
举个形象的例子,比如一个集合是线程安全的,有两个线程在操作同一个集合对象,当第一个线程查询集合非空后,删除集合中所有元素的时候。第二个线程也来执行与第一个线程相同的操作,也许在第一个线程查询后,第二个线程也查询出集合非空,但是当第一个执行清除后,第二个再执行删除显然是不对的,因为此时集合已经为空了。

线程死锁


死锁对Java程序来说,是很复杂的,也很难发现问题。当两个线程被阻塞,每个线程在等待另一个线程时就发生死锁。简单的说:有A,B两个线程,同步A,B线程执行的时候都必须C,D两个锁,如果A持有C锁,但要申请D锁,就是这个时候,B线程持有D锁,同时在申请C锁,这个时候就会形成“死锁 ”。

小结:

1.线程同步的目的是为了保护多个线程访问同一个相同资源时对资源的破坏。
2.线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。
3.对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁,尽量不要同步使用静态同步方法和同步方法,这样会造成误解。
4.对于同步,要时刻清醒在哪个对象上同步,这是关键。
5.编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6.当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7.死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。但一旦程序发生死锁,程序将死掉。

后续的文章还会介绍线程使用相关的知识,大家也可以一起学习。


0 0
原创粉丝点击