java多线程编程核心技术2-Synchronized

一。什么是非线程安全:
1. 非线程安全:多个线程对同一个对象中的实例变量进行了并发访问,产生的后果就是“脏读”,也就是取到的数据其实是被更改过的。
2. 线程安全:获得实例变量的值是经过同步处理的,不会出现脏读的现象。(如按顺序读取)
3. “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全问题”。
4. 实例变量的非线程安全:
    1.如果对象中有多个实例变量,运行的结果可能出现交叉的情况,如仅有一个实例变量,则可能出现覆盖的情况。

    解决方法一: 使用synchronized同步方法,多线程访问同一个对象中的同步方法时一定是线程安全的。(同步:可理解为线程里更改的值同步到主内存中,

                             使其他线程能够看到)

   
二。线程占用的锁是对象锁。
1.两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,jvm会加两个锁,互不影响。
  synchronized 取得的锁都是对象锁,而不是把一段代码或方法(函数)当做锁,线程会获得实例对象中同步方法所属的实例对象的锁,所以上面互不影响。
  
结论:
  1. A线程通过调用methodA先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法methodC。
  2. A线程通过调用methodA先持有object对象的Lock锁,B线程如果在这时调用object对象中的所有synchronized类型的方法都需要等待锁(methodA或methodB)。
	public class MyObject {
		synchronized public void methodA() {
		 ...
		 }
		synchronized public void methodB() {
		 ...
		 }

		public void methodC() {
		    ...
		 }
	}

三。脏读
1.线程在读取实例变量时,此值已经被其他线程更改过了。
  场景:线程A执行对象的某个同步方法设置值时,如果过程比较慢只设置了一部分的变量的值,此时调用此对象的另一个未同步的取值方法时取到的值
             是只设置了一半值。
public class PublicVar {
		public String username = "A";
		public String password = "AA";

		synchronized public void setValue(String username, String password) {
			try {
				this.username = username;
				Thread.sleep(5000);
				this.password = password;
				System.out.println("setValue method thread name="
						+ Thread.currentThread().getName() + " username="
						+ username + " password=" + password);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		synchronized public void getValue() {
			System.out.println("getValue method thread name="
					+ Thread.currentThread().getName() + " username="
					+ username + " password=" + password);
		}
	}

	public class Test {
		public static void main(String[] args) {
			try {
				PublicVar publicVarRef = new PublicVar();
				ThreadA thread = new ThreadA(publicVarRef);
				thread.start();

				Thread.sleep(200);// 打印结果受此值大小影响

				publicVarRef.getValue();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}


四。synchronized锁重入:

1。当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到改对象的锁的,也就是在一个synchronized方法内部调用此对象的其他
   synchronized 方法时是永远可以得到锁的(不能就坏事了,到处是死锁)
2. 当存在父子继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。
   
	public class Sub extends Main {
		synchronized public void operateISubMethod() {
			try {
				while (i > 0) {
					i--;
					System.out.println("sub print i=" + i);
					Thread.sleep(100);
					this.operateIMainMethod();
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public class Main {
		public int i = 10;

		synchronized public void operateIMainMethod() {
			try {
				i--;
				System.out.println("main print i=" + i);
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public class MyThread extends Thread {
		@Override
		public void run() {
			Sub sub = new Sub();
			sub.operateISubMethod();
		}
	}

	public class Run {
		public static void main(String[] args) {
			MyThread t = new MyThread();
			t.start();
		}
	}


五。锁的其他知识汇总
1.出现异常,锁自动释放,当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
2.同步不具有继承性。
  1.如果父类的某一方法是同步的,但是其子类重写的同名方法并不直接具备同步性,必须给子类方法也加上synchronized才是同步方法。
3.使用synchronized同步语句块,减少同步范围,尽量减少其他线程的等待时间。
4.当线程访问object的一个synchronized(this)同步代码块时,其他线程对同一object中其他的synchronized(this)同步代码块的访问将被阻塞。
5.synchronized(this)代码块也是锁定当前对象的。
6.synchronized(非this对象),如使用实例对象中的一个字符串变量作为同步对象也是可以的。
7.synchronized(非this对象) 与 synchronized(this)/synchronized 之间的锁互不影响,他们之间不会阻塞。
  如下示例:
  如果Service里的判断条件之前不加synchronized (list),则当MyThread1和MyThread2会同时进入判断条件且满足,然后顺序执行list的同步add方法,导致size=2
  注意:此处必须用list作为同步锁对象,因为两个线程的Service是不同的,所以用synchronized (this)并不能阻塞另一个线程,只有list是两个线程公用的,
        所以它上面的锁会阻塞另一个线程获取此锁,必须等待前一个处理完毕才能去判断是否满足if条件
 
	public class Service {
		public MyOneList addServiceMethod(MyOneList list, String data) {
			try {
				synchronized (list) {
					if (list.getSize() < 1) {
						Thread.sleep(2000);
						list.add(data);
					}
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return list;
		}
	}

	public class MyOneList {
		private List list = new ArrayList();

		synchronized public void add(String data) {
			list.add(data);
		};

		synchronized public int getSize() {
			return list.size();
		};
	}

	public class MyThread1 extends Thread {
		private MyOneList list;

		public MyThread1(MyOneList list) {
			super();
			this.list = list;
		}

		@Override
		public void run() {
			Service msRef = new Service();
			msRef.addServiceMethod(list, "A");
		}
	}

	public class MyThread2 extends Thread {
		private MyOneList list;

		public MyThread2(MyOneList list) {
			super();
			this.list = list;
		}

		@Override
		public void run() {
			Service msRef = new Service();
			msRef.addServiceMethod(list, "B");
		}
	}

8.synchronized(非this对象X)与X对象上的synchronized方法和synchronized(this)方法是同步阻塞的,也就是线    程A通过前面三种任意一种获取了此对象
   X的锁后未释放前,X上的synchronized方法和synchronized(this)对其他线程也是阻塞的。

  
9.synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是   给对象上锁。
  Class锁可以对类的所有对象实例起作用(注意是对所有实例再次调用synchronized static方法时,而不是不带static的对象锁)。
  synchronized(class)代码块的作用和synchronized static方法的作用一样。
  如下列:
	public class ThreadA extends Thread {
		private Service service;

		public ThreadA(Service service) {
			super();
			this.service = service;
		}

		@Override
		public void run() {
			service.printA();
		}
	}

	public class ThreadB extends Thread {
		private Service service;

		public ThreadB(Service service) {
			super();
			this.service = service;
		}

		@Override
		public void run() {
			service.printB();
		}
	}

	public class ThreadC extends Thread {
		private Service service;

		public ThreadC(Service service) {
			super();
			this.service = service;
		}

		@Override
		public void run() {
			service.printC();
		}
	}

	public class Service {
		synchronized public static void printA() {
			try {
				System.out.println("线程名称为:" + Thread.currentThread().getName()
						+ "在" + System.currentTimeMillis() + "进入printA");
				Thread.sleep(3000);
				System.out.println("线程名称为:" + Thread.currentThread().getName()
						+ "在" + System.currentTimeMillis() + "离开printA");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		synchronized public static void printB() {
			System.out.println("线程名称为:" + Thread.currentThread().getName()
					+ "在" + System.currentTimeMillis() + "进入printB");
			System.out.println("线程名称为:" + Thread.currentThread().getName()
					+ "在" + System.currentTimeMillis() + "离开printB");
		}

		synchronized public void printC() {
			System.out.println("线程名称为:" + Thread.currentThread().getName()
					+ "在" + System.currentTimeMillis() + "进入printC");
			System.out.println("线程名称为:" + Thread.currentThread().getName()
					+ "在" + System.currentTimeMillis() + "离开printC");
		}

	}

	public class Run {
		public static void main(String[] args) {
			Service service = new Service();
			ThreadA a = new ThreadA(service);
			a.setName("A");
			a.start();
			ThreadB b = new ThreadB(service);
			b.setName("B");
			b.start();
			ThreadC c = new ThreadC(service);
			c.setName("C");
			c.start();
		}
	}

	// 测试结果:
	// A先启动占用Service的class锁,B线程也申请Class锁被阻塞,C线程申请的是Service的对象锁而不是Class锁所以能进入。
	public class Run {
		public static void main(String[] args) {
			Service service1 = new Service();
			Service service2 = new Service();
			ThreadA a = new ThreadA(service1);
			a.setName("A");
			a.start();
			ThreadB b = new ThreadB(service2);
			b.setName("B");
			b.start();
		}
	}
	// 测试结果:A先启动占用Service的class锁,B线程也申请Class锁,但是Class锁是对所有对象实例起作用的,所以B被阻塞等待A线程执行完毕再执行。
 

10.synchronized代码块都不要用String作为锁对象

    比如new Object()实例化一个Object对象,因为jvm对String常量有缓存,两个值相同的不同String对象实际上是一个对象。


11.如果要为一个Service中两个方法分别控制锁,可以对两个方法内用synchronized同步代码块控制,分别用不同     的对象锁,这样才能相互不影响,不要直接在方法上synchronized,这样会获得对象锁。

   实例1:B永远没有机会运行
   
public class Service {
		synchronized public void methodA() {
			System.out.println("methodA begin");
			boolean isContinueRun = true;
			while (isContinueRun) {
			}
			System.out.println("methodA end");
		}

		synchronized public void methodB() {
			System.out.println("methodB begin");
			System.out.println("methodB end");
		}
	}

	// 实例2:methodA和methodB分别上锁:
	public class Service {
		Object object1 = new Object();

		public void methodA() {
			synchronized (object1) {
				System.out.println("methodA begin");
				boolean isContinueRun = true;
				while (isContinueRun) {
				}
				System.out.println("methodA end");
			}
		}

		Object object2 = new Object();

		public void methodB() {
			synchronized (object2) {
				System.out.println("methodB begin");
				System.out.println("methodB end");
			}
		}
	}


12.死锁的产生和检测

     public class DealThread implements Runnable {

		public String username;
		public Object lock1 = new Object();
		public Object lock2 = new Object();

		public void setFlag(String username) {
			this.username = username;
		}

		@Override
		public void run() {
			if (username.equals("a")) {
				synchronized (lock1) {
					try {
						System.out.println("username = " + username);
						Thread.sleep(3000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					synchronized (lock2) {
						System.out.println("按lock1->lock2代码顺序执行了");
					}
				}
			}
			if (username.equals("b")) {
				synchronized (lock2) {
					try {
						System.out.println("username = " + username);
						Thread.sleep(3000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					synchronized (lock1) {
						System.out.println("按lock2->lock1代码顺序执行了");
					}
				}
			}
		}
	}

	public class Run {
		public static void main(String[] args) {
			try {
				DealThread t1 = new DealThread();
				t1.setFlag("a");
				Thread thread1 = new Thread(t1);
				thread1.start();
				Thread.sleep(100);
				t1.setFlag("b");
				Thread thread2 = new Thread(t1);
				thread2.start();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
查看死锁:
jps:找出run程序的pid
jstack -l pid:显示线程堆栈信息,从中可以看到两个线程各自locked了对方waiting to lock的锁,从而导致死锁。

13.如果以对象作为锁,当对象的属性发生变化时,锁是不变的。
   字符串对象是个特殊,当他的值发生改变后相当于新建了一个对象,所以锁会变。
   

五。volatile关键字
1。作用:使变量在多个线程间可见,相当于直接在主内存而不是线程内存中操作带volatile关键字的变量。
2. synchronized和volatile的比较:
   1.volatile只能修饰于变量,而synchronized可以修饰方法、以及代码块。
   2.volatile不会阻塞,而synchronized会出现阻塞。
   3.volatile能保证数据的可见性,但不能保证原子性,而synchronized可以保证原子性。
   4.volatile解决的是变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性。
3.使用场景:在多个线程中可以感知实例变量被更改了,并且可以获得最新的值使用。
4.原子类型:时一个原子操作可用的类型,它可以在没有锁的情况下做到线程安全(如AtomicInteger)

  注意:但是多个原子类型组合操作时,虽然能保证最终结果正确,但是顺序有可能被打乱,解决它的方法也很简单,

             把多个原子操作变成一个原子操作即给方法或多个原子操作的

            代码块加上synchronized关键字即可。 
	public class MyService {
		public static AtomicLong aiRef = new AtomicLong();

		public void addNum() {
			// 下面两个原子操作在多线程的情况下是不能保证一块执行的,解决它需要给addNum加上synchronized关键字
			aiRef.addAndGet(100);
			aiRef.addAndGet(1);
		}

	}

5.JVM设置为-server时就出现死循环的问题:

  原因:非-server环境时,线程的变量存在于公共堆栈及线程的私有堆栈中,线程在私有和公共之间交互数据(read,load,assign,write等)。
             -server环境时,为了线程的运行效率,线程一直在其私有堆栈中取得线程的变量值,从而导致其他线程更新此变量的值不可见(更新到公共堆栈中)。
    
	public class ThreadA extends Thread {
		private Service service;

		public ThreadA(Service service) {
			super();
			this.service = service;
		}

		@Override
		public void run() {
			service.runMethod();
		}
	}

	public class ThreadB extends Thread {
		private Service service;

		public ThreadB(Service service) {
			super();
			this.service = service;
		}

		@Override
		public void run() {
			service.stopMethod();
		}
	}

	public class Service {
		private boolean isContinueRun = true;

		public void runMethod() {
			// String anyString = new String();
			while (isContinueRun == true) {
				// synchronized (anyString) {
				// }
			}
			System.out.println("停下来了!");
		}

		public void stopMethod() {
			isContinueRun = false;
		}
	}

	public class Run {
		public static void main(String[] args) throws InterruptedException {
			try {
				Service service = new Service();
				ThreadA a = new ThreadA(service);
				a.start();
				Thread.sleep(1000);
				ThreadB b = new ThreadB(service);
				b.start();
				System.out.println("已经发起停止的命令了!");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

6. synchronized 也具有同步数据的功能

    如上例中Service中去掉注释,就具有了同步数据的功能,即使设置为-server模式,线程也会从主内存中

   同步线程相关的变量数据,从而在下一次循环中得到被其他线程更改的isContinueRun的值true,线程正常停止。

7. 线程工作内存与主内存的交互

      其中在线程的工作内存中load,use,assign三个步骤是出现线程不安全的主要因素,考虑一个累加操作,当A线程执行到load是i的值

      是5,此时切换线程,B,C线程同样读取到主内存中的i的值5,然后三个线程继续执行,A,B,C回写主内存值都为6,导致少加了2,解决方法也很简单,给相应增加i值得方         法     或代码块加上synchronized关键字,使A线程执行完一次从read-write过程后其他线程才能继续执行他们自己的过程。
     read:从主内存读取
     load:加载到工作内存
     use:使用
     assign:赋值
     store:存储到工作内存
     write:写入主内存


   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值