【多线程】线程安全、锁的同步和异步

1、基本概念java

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

       非线程安全:非线程主要是指多个线程对同一个对象中的同一个实例变量进行操做时会出现值被更改、值不一样步的状况,进而影响程序的执行流程。异步

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

2、线程安全举例ide

       单纯地看线程安全的定义确定会以为很是晦涩。接下来经过下面的程序进行理解。函数

public class MyThread extends Thread{
	
	private int count = 5 ;
	
	//synchronized加锁
	public void run(){
		count--;
		System.out.println(this.currentThread().getName() + " count = "+ count);
	}
	
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		Thread t1 = new Thread(myThread,"t1");
		Thread t2 = new Thread(myThread,"t2");
		Thread t3 = new Thread(myThread,"t3");
		Thread t4 = new Thread(myThread,"t4");
		Thread t5 = new Thread(myThread,"t5");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}
}
        实例变量count的初始值为5,进行i--操做,咱们的预期结果是输出 count = 4 、count = 三、count = 二、count = 一、 count = 0。然而实际输出结果以下:

    

       实际数据结果合咱们预期结果不相符。对比到定义就是这个类没有表现出正确的行为。因此说自定义类MyThread是线程不安全的。学习

       当咱们在方法run()前加上synchronized关键字,输出结果以下:this

      

       此时,咱们的自定义类MyThread就是线程安全的。在这个例子中,咱们实例化了一个对象myThread。开启了5个线程t一、t二、t三、t四、t5。(一个对象对应一把锁spa

        当多个线程访问myThread的run方法时,以排队的方式进行处理(这里排队是按照CPU分配的前后顺序而定的),一个线程想要执行synchronized修饰的方法里的代码,首先是尝试得到锁,若是拿到锁,执行synchronized代码块中的内容:拿不到锁,这个线程就会不断尝试得到这把锁,知道拿到位置,并且是多个线程同时去竞争这把锁。也就是说会有锁竞争的问题。线程

       这个例子中,一旦t2执行完释放锁,其余4个线程就会去抢这把锁。加入开启了1000个线程,一个线程释放锁以后,剩下999个线程去抢一把锁。将会致使CPU飙升,甚至宕机。

3、局部变量、实例变量与线程安全

      方法内部的私有变量,也就是局部变量永远是线程安全的。若是多个线程共同访问1个对象中的实例变量,则有可能出现“非线程安全”问题(上例就是)。run()方法关键字synchronized修饰以后就变成同步方法;在多个线程同时访问同一个对象的同步方法时必定是线程安全的。

4、多个对象多个锁

public class MultiThread {

	private int num = 0;
	
	/** static */
	public synchronized void printNum(String tag){
		try {
			
			if(tag.equals("a")){
				num = 100;
				System.out.println("tag a, set num over!");
				Thread.sleep(1000);
			} else {
				num = 200;
				System.out.println("tag b, set num over!");
			}
			
			System.out.println("tag " + tag + ", num = " + num);
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	//注意观察run方法输出顺序
	public static void main(String[] args) {
		
		//俩个不一样的对象
		final MultiThread m1 = new MultiThread();
		final MultiThread m2 = new MultiThread();
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				m1.printNum("a");
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			@Override 
			public void run() {
				m2.printNum("b");
			}
		});		
		
		t1.start();
		t2.start();
	}

}
代码执行结果输出以下;

     被synchronized修饰的方法printNum()是同步方法。但输出结果给人的感受倒是异步的,咱们最初预想的输出状况是  tab a,set num over!以后紧跟 tag a,num=100。这是为什么呢?

      前面已经说起过一个对象一把锁。N个对象N个锁。上面的示例是两个线程分别访问同一个类的两个不一样对象的同步方法。关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁。因此示例代码中哪一个线程先执行synchronized关键字的方法,哪一个线程就持有该方法所属对象的锁。

      由于有2个对象,因此有2个锁,每一个线程均可以拿到本身指定的锁,分别得到锁以后,执行synchronized同步方法体的内容。也就是时候两个线程t一、t2分别获得的是m1对象的锁和m2对象的锁,彼此之间是没有冲突的。


       有一种状况则是相同的锁,即在static方法上加synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)),咱们接着对上边的代码进行修改,在 synchronized void printNum()前加上static修饰,在int num=0前也加上static关键字,“类锁”输出结果以下:

      

6、对象锁的同步和异步

       同步:synchronized

       同步的概念就是共享,咱们要紧紧记住“共享”两个字,若是不是共享的资源,就没有必要进行同步。同步的目的就是为了线程安全,其实对于线程安全来讲,须要知足两个特性:①原子性(同步)、②可见性。

       异步:asynchronized

       异步的概念就是独立的、相互之间不受到任何约束,就好像咱们学习http的时候,在页面发起Ajax请求,咱们还能够继续浏览或操做页面的内容,二者之间没有任何关系。

7、脏读:

       上面全部的例子,类都是只有一个同步方法。若是类中有1个同步方法,1个异步方法。

       1)A线程先持有object对象的Lock锁,B线程能够以异步的方式调用object对象中的非synchronized方法。

       2)A线程先只有object对象的Lock锁,B线程若是这在这时调用object对象中的synchronized方法则需等待。


public class DirtyRead {

	private String username = "dmsd";
	private String password = "123";
	
	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);
	}
	
	/**
	 * synchronized
	 */
	public  void getValue(){
		System.out.println("getValue方法获得:username = " + this.username + " , password = " + this.password);
	}
	
	
	public static void main(String[] args) throws Exception{
		
		final DirtyRead dr = new DirtyRead();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				dr.setValue("z3", "456");		
			}
		});
		t1.start();
		Thread.sleep(1000);
		
		dr.getValue();
	}
	
}
上面的例子最终的输出结果为: 

      

         虽然在赋值时进行了同步,但在取值时出现了意外,也就是“脏读”。发生脏读的状况是在读取实例变量时,此值已经被其余线程更改了。解决上述脏读的办法就是在getValue()方法前加上synchronized关键字。