转自http://blog.csdn.net/u012961566/article/details/69342135

在我们的实际应用当中可能经常会遇到这样一个场景:多个线程读或者、写相同的数据,访问相同的文件等等。对于这种情况如果我们不加以控制,是非常容易导致错误的。在Java中,为了解决这个问题,引入临界区概念。所谓临界区是指一个访问共用资源的程序片段,而这些共用资源又无法同时被多个线程访问。

在java中**为了实现临界区提供了同步机制。**当一个线程试图访问一个临界区时,他将使用一种同步机制来查看是不是已经有其他线程进入临界区。如果没有则他就可以进入临界区,否则他就会被同步机制挂起,指定进入的线程离开这个临界区。

临界区规定:每次只准许一个进程进入临界区,进入后不允许其他进程进入。调度法则为:

1、如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入

2、任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待。

3、进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。

4、如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”现象
#synchronized关键字
##synchronized简介
synchronized,我们谓之锁,主要用来给方法、代码块加锁。当某个方法或者代码块使用synchronized时,那么在同一时刻至多仅有有一个线程在执行该段代码。当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段。但是,其余线程是可以访问该对象中的非加锁代码块的。
##synchronized方法
通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:

public synchronized void getResult();

synchronized方法控制对类成员变量的访问。它是如何来避免类成员变量的访问控制呢?我们知道方法使用了synchronized关键字表明该方法已加锁,在任一线程在访问改方法时都必须要判断该方法是否有其他线程在“独占”。每个类实例对应一个把锁,每个synchronized方法都必须调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,被阻塞的线程方能获得该锁。

其实synchronized方法是存在缺陷的,如果我们将一个很大的方法声明为synchronized将会大大影响效率的。如果多个线程在访问一个synchronized方法,那么同一时刻只有一个线程在执行该方法,而其他线程都必须等待,但是如果该方法没有使用synchronized,则所有线程可以在同一时刻执行它,减少了执行的总时间。所以如果我们知道一个方法不会被多个线程执行到或者说不存在资源共享的问题,则不需要使用synchronized关键字。但是如果一定要使用synchronized关键字,那么我们可以synchronized代码块来替换synchronized方法。
##synchronized代码块
synchronized代码块所起到的作用和synchronized方法一样,只不过它使临界区变的尽可能短了,换句话说:**它只把需要的共享数据保护起来,其余的长代码块留出此操作。**语法如下:

synchronized(object) {  
    //允许访问控制的代码  
}

如果我们需要以这种方式来使用synchronized关键字,那么必须要通过一个对象引用来作为参数,通常这个参数我们常使用为this.

synchronized (this) {
    //允许访问控制的代码 
}

对于synchronized(this)有如下理解:

1、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

2、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问object中的非synchronized(this)同步代码块。

3、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其他synchronized(this)同步代码块得访问将被阻塞。

4、第三个例子同样适用其他同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其他线程对该object对象所有同步代码部分的访问都将被暂时阻塞。

5、以上规则对其他对象锁同样适用
#锁的是什么
##锁的概念
么是锁?在日常生活中,它就是一个加在门、箱子、抽屉等物体上的封缄器,防止别人偷窥或者偷盗,起到一个保护的作用。在java中同样如此,锁对对象起到一个保护的作用,一个线程如果独占了某个资源,那么其他的线程别想用,想用?等我用完再说吧!

在java程序运行环境中,JVM需要对两类线程共享的数据进行协调:
1、保存在堆中的实例变量
2、保存在方法区中的类变量。

为了实现监视器的排他性监视能力,java虚拟机为每一个对象和类都关联一个锁。代表任何时候只允许一个线程拥有的特权。线程访问实例变量或者类变量不需锁。 如果某个线程获取了锁,那么在它释放该锁之前其他线程是不可能获取同样锁的。一个线程可以多次对同一个对象上锁。对于每一个对象,java虚拟机维护一个加锁计数器,线程每获得一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被完全释放了。

java编程人员不需要自己动手加锁,对象锁是java虚拟机内部使用的。在java程序中,只需要使用synchronized块或者synchronized方法就可以标志一个监视区域。当每次进入一个监视区域时,java 虚拟机都会自动锁上对象或者类。
##锁的是什么
在这个问题之前我们必须要明确一点:无论synchronized关键字加在方法上还是对象上,锁都是对象。在java中每一个对象都可以作为锁,它主要体现在下面三个方面:

  • 对于同步方法,锁是当前实例对象。

  • 对于同步方法块,锁是Synchonized括号里配置的对象。

  • 对于静态同步方法,锁是当前对象的Class对象。
    ###对于同步方法,锁是当前实例对象。
    首先我们先看下面例子:

/**
 * 
 */
package test;

/**
 * <p>Title:Sycntest </p>
 * <p>Description: </p>
 * @author 田茂林
 * @data 2017年9月4日 下午3:24:45
 */
public class Sycntest implements  Runnable{
	 @Override
	    public synchronized void run() {
	        for(int i = 0 ; i < 3 ; i++){
	            System.out.println(Thread.currentThread().getName() + "run......");
	        }
	    }
	    
	    public static void main(String[] args) {
	    	Sycntest sys = new Sycntest();
	        for(int i = 0 ; i < 5 ; i++){
	            new Thread(new Sycntest(),"Thread_" + i).start();
	        }
	    }

}

运行结果

Thread_0run......
Thread_4run......
Thread_4run......
Thread_4run......
Thread_1run......
Thread_1run......
Thread_1run......
Thread_3run......
Thread_3run......
Thread_3run......
Thread_0run......
Thread_0run......
Thread_2run......
Thread_2run......
Thread_2run......

这个结果与我们预期的结果有点不同(这些线程在这里乱跑),照理来说,run方法加上synchronized关键字后,会产生同步效果,这些线程应该是一个接着一个执行run方法的。在上面LZ提到,一个成员方法加上synchronized关键字后,实际上就是给这个成员方法加上锁,具体点就是以这个成员方法所在的对象本身作为对象锁。但是在这个实例当中我们一共new了10个ThreadTest对象,那个每个线程都会持有自己线程对象的对象锁,这必定不能产生同步的效果。所以:如果要对这些线程进行同步,那么这些线程所持有的对象锁应当是共享且唯一的!

/**
 * 
 */
package test;

/**
 * <p>Title:Sycntest </p>
 * <p>Description: </p>
 * @author 田茂林
 * @data 2017年9月4日 下午3:24:45
 */
public class Sycntest implements  Runnable{
	 @Override
	    public synchronized void run() {
	        for(int i = 0 ; i < 3 ; i++){
	            System.out.println(Thread.currentThread().getName() + "run......");
	        }
	    }
	    
	    public static void main(String[] args) {
	    	Sycntest sys = new Sycntest();
	        for(int i = 0 ; i < 5 ; i++){
	            new Thread(sys,"Thread_" + i).start();//唯一的对象实例
	        }
	    }

}

运行结果

Thread_1run......
Thread_1run......
Thread_1run......
Thread_2run......
Thread_2run......
Thread_2run......
Thread_4run......
Thread_4run......
Thread_4run......
Thread_3run......
Thread_3run......
Thread_3run......
Thread_0run......
Thread_0run......
Thread_0run......

###对于同步方法块,锁是Synchonized括号里配置的对象。
面实例是使用synchronized方法,我们在看看synchronized代码块

public class ThreadTest_02 extends Thread{

    private String lock ;
    private String name;
    
    public ThreadTest_02(String name,String lock){
        this.name = name;
        this.lock = lock;
    }
    
    @Override
    public void run() {
        synchronized (lock) {
            for(int i = 0 ; i < 3 ; i++){
                System.out.println(name + " run......");
            }
        }
    }
    
    public static void main(String[] args) {
        String lock  = new String("test");
        for(int i = 0 ; i < 5 ; i++){
            new ThreadTest_02("ThreadTest_" + i,lock).start();
        }
    }
}
ThreadTest_0 run......
ThreadTest_0 run......
ThreadTest_0 run......
ThreadTest_1 run......
ThreadTest_1 run......
ThreadTest_1 run......
ThreadTest_4 run......
ThreadTest_4 run......
ThreadTest_4 run......
ThreadTest_3 run......
ThreadTest_3 run......
ThreadTest_3 run......
ThreadTest_2 run......
ThreadTest_2 run......
ThreadTest_2 run......

在main方法中我们创建了一个String对象lock,并将这个对象赋予每一个ThreadTest2线程对象的私有变量lock。我们知道java中存在一个字符串池,那么这些线程的lock私有变量实际上指向的是堆内存中的同一个区域,即存放main函数中的lock变量的区域,所以对象锁是唯一且共享的。线程同步!

在这里synchronized锁住的就是lock这个String对象。

对于静态同步方法,锁是当前对象的Class对象。

public class ThreadTest_03 extends Thread{

    public synchronized static void test(){
        for(int i = 0 ; i < 3 ; i++){
            System.out.println(Thread.currentThread().getName() + " run......");
        }
    }
    
    @Override
    public void run() {
        test();
    }

    public static void main(String[] args) {
        for(int i = 0 ; i < 5 ; i++){
            new ThreadTest_03().start();
        }
    }
}
Thread-0 run......
Thread-0 run......
Thread-0 run......
Thread-4 run......
Thread-4 run......
Thread-4 run......
Thread-1 run......
Thread-1 run......
Thread-1 run......
Thread-2 run......
Thread-2 run......
Thread-2 run......
Thread-3 run......
Thread-3 run......
Thread-3 run......

在这个实例中,run方法使用的是一个同步方法,而且是static的同步方法,那么这里synchronized锁的又是什么呢?我们知道static超脱于对象之外,它属于类级别的。所以,对象锁就是该静态放发所在的类的Class实例。由于在JVM中,所有被加载的类都有唯一的类对象,在该实例当中就是唯一的 ThreadTest_03.class对象。不管我们创建了该类的多少实例,但是它的类实例仍然是一个!所以对象锁是唯一且共享的。线程同步!!