并发编程:安全问题

最近开始学习并发编程,每天抽空学一点,晚上坚持做一下笔记。

180918-------------------------------------------------------------------------------------------------------------------------

一、什么是线程安全

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

二、多个线程多个锁

        多个线程,每个线程都可以拿到自己指定的锁,分别获得锁之后,执行synchronized方法体的内容。

三、synchronized关键字

        1、关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁,所以先执行带有synchronized关键字的代码段(方法)的线程就持有该代码段(方法)所属对象的锁(Lock),如果存在两个对象,则线程持就有两个不同的锁,他们互不影响,可以并行运行。

代码示例:

	/*no static  from:txd2016_5_11 180918*/
	public synchronized void printNumber_void(String tag) {
		System.err.println("这里是:printNumber_void "+tag);
			if("a".equals(tag)) {
				number = 100;
				System.out.println("tag : a,set number over");
			}else {
				number = 200;
				System.out.println("tag is not a:set number over");
			}
			try {
				Thread.sleep(3*1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("执行结果:"+tag+", number:"+number);
	}

       2、静态方法上存在synchronized关键字,则表示锁定该对象对应的.Class类,是为类一级的锁(独占锁),导致其串行运行。

代码示例:

	/* static   from:txd2016_5_11 180918*/
	public static synchronized void printNumber_static(String tag) {
		System.err.println("这里是:printNumber_static "+tag);
			if("a".equals(tag)) {
				number_static = 100;
				System.out.println("tag : a,set number over");
			}else {
				number_static = 200;
				System.out.println("tag is not a:set number over");
			}
			try {
				Thread.sleep(3*1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("执行结果:"+tag+", number:"+number_static);
	}

两段代码执行结果:

			System.out.println("两个不同的对象访问static锁");
			t1.start();
			t2.start();
			System.err.println("执行结果:t1与t2按顺序执行,即先到者先将类锁住了,"
					+ "后来者只有等前面的执行结束之后才能执行");

			System.out.println("两个不同的对象访问非static锁");
			t3.start();
			t4.start();
			System.err.println("执行结果:t3与t4并发执行,之间互不影响,所操作的都是自己的对象内内容");

-------------------------------------------------------------------------------------------------- 180918 end

源码?目前还不到那个层次,至少目前状态还是不存在的,至于以后,那就是一定得好好抠一下。

180919  ---------------------------------------关于锁的几个问题-----------------------------------------

1、锁讲解:

测试代码:

private int count = 3;
	//synchronized 加锁
	//synchronized
	public synchronized void run() {
		count -- ;
		float result = 100/count;
		try {
			Thread.sleep(1*1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(this.currentThread().getName()+" count = "+count+"  \t result = "+result);
	}

main执行测试:

public static void main(String[] args) {
		MyThread01 myThread = new MyThread01();
		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");
		Thread[] threads = {t1,t2,t3,t4,t5};
		for(Thread t:threads) {
			t.start();
		}
	}

执行结果分析:

t1到t2不定顺序串行执行,其中某一个线程会报除零错误,其余线程都正常执行,count数也由3减少为-2;

2、同步与异步

        同步(synchronized):类似于MySQL的主从同步(主服务器:读写操作,从服务器:readOnly)N个线程可以放心大胆地并发去从服务器上进行DML操作,N个线程前往主服务器进行写操作,如insert,则先到达的线程会先将表锁住,等待insert执行完毕后下一个线程才有可能被允许进来进行insert操作(主服务器与从服务器将会进行数据同步,线程在主服务执行完insert操作后将产生日志文件,主服务器将此日志文件传给从服务器,此处涉及到同步刷盘/异步刷盘);区分MySQL的分库分表(后者主要是为了减轻数据库压力)。

        同步的概念就是共享,如果不是共享的资源就没有必要进行同步。同步的目的就是为了线程安全,而对于线程安全来说,则需要满足两个特性:原子性(不可拆分?)、可见性。

        异步(asynchronized):异步的概念就是*独*-*立*,相互之间不收任何制约。

代码示例(原子性):MyObject类

	public synchronized void method01() {
		System.err.println(Thread.currentThread().getName()+"我是被锁住的方法");
		try {
			Thread.sleep(5*1000);			//睡4秒钟
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public void method2() {
		System.err.println(Thread.currentThread().getName()+"我还没有被锁住");
		try {
			Thread.sleep(1*1000);				//睡1秒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

main测试:

	final MyObject myObj = new MyObject();
	Thread thread1 = new Thread(new Runnable() {
				@Override
				public void run() {
					myObj.method01();
				}
			},"thread1");
	Thread thread2 = new Thread(new Runnable() {
				@Override
				public void run() {
					myObj.method2();
				}
			},"thread2");
	Thread thread3 = new Thread(new Runnable() {
				@Override
				public void run() {
					myObj.method1();
				}
			},"thread3");
    thread1.start();
    thread2.start();
    thread3.start();

执行结果:。。。

thread1先获取到method01的锁,后边thread3接着去获取method01则需要等待thread1释放该资源;thread2由于获取的是thread02且该方法没有加锁,自然就能够迅速被thread2获取并执行了。

-------------------------------------------------------------------------------------------------- 180919 end

180920  -------------------------------------关于脏读的几个问题----------------------------------------

脏读:

示例代码:

数据资源(userName、password)及其初始化值:

private String userName="txd";
private String password="123";

操作(get/set),set方法加了锁,get方法则不加锁:

public synchronized void setValue(String username,String password) {
		this.userName = username;
		try {
			Thread.sleep(2*1000);
		}catch(InterruptedException e) {
			System.err.println(Thread.currentThread().getName()+"线程此时被打断了");
		}
		this.password = password;
		System.err.println("setVlaue的最终执行结果为:username="+username+",   
                           password="+password);
	}

public void getValue() {
		System.err.println("getValue的最终执行结果为:username="+this.userName+",       
                           password="+this.password);
	}

main测试:

	public static void main(String[] args) {
		final DirtyRead dr = new DirtyRead();
		Thread t1 = new Thread(
				new Runnable() {
					@Override
					public void run() {
						dr.setValue("晓冬", "凛冬将至");
					}
				}
				);
		Thread t2 = new Thread(
				new Runnable() {
					@Override
					public void run() {
						dr.getValue();
					}
				}
				);
		t1.start();
		t2.start();
	}

运行效果:

getValue的最终执行结果为:username=晓冬,   password=123
setVlaue的最终执行结果为:username=晓冬,   password=凛冬将至

显然:理想状态下的键值对信息应该是:“txd”-"123"、"晓冬"-"凛冬将至";

结果显示:发生脏读现象。

将set/get方法都加锁,测试效果:

//先get再set
getValue的最终执行结果为:username=txd,   password=123
setVlaue的最终执行结果为:username=晓冬,   password=凛冬将至
//先set再get
setVlaue的最终执行结果为:username=晓冬,   password=凛冬将至
getValue的最终执行结果为:username=晓冬,   password=凛冬将至

解决脏读方案:根据业务场景不同而不同,比如同时都加锁、写互斥/读共享锁...加锁时考虑业务的整体性,保证业务(Service)的         原子性,也从侧面保证了业务的一致性。

备注:数据库的ACID:原子性、一致性、隔离性、持久性;

锁重入:synchronized关键字拥有锁重入的功能,即在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象时可以再次得到该对象的锁,作用?还没意识到。。。。

-------------------------------------------------------------------------------------------------- 180920 end

synchronized关键字使用时的注意事项:

1、为了提高执行速度,锁的粒度应该尽量小。

2、不要为字符串常量加锁,会出现死循环。

3、当使用一个对象进行加锁的时候,若对象本身发生了改变,则其持有的锁也就不同了。这里的对象本身的改变不包括对象的属性等的改变。

4、注意避免死锁。

猜你喜欢

转载自blog.csdn.net/txd2016_5_11/article/details/82764292