java多线程编程核心技术(学习笔记二 第一节)

2.1 synchronized同步方法

2.1.1方法内声明的变量是线程安全的

2.1.2实例变量非线程安全

2.1.3 多个对象多个锁

    关键字synchronized取得的锁都是对象锁,而不是一段代码或者方法当作锁,哪个线程先执行待synchronized关键字的方法,哪个线程就持有该方法所属对象的锁lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。

   但如果多个线程访问多个对象,则JVM会创建多个锁。

  synchronized声明的方法一定是排队运行的,只有共享资源的读写访问才需要同步化,如果不是共享资源,就没有必要同步化。

2.1.4 synchronized方法与锁对象

测试线程1在执行synchronized方法的同时,线程2执行非synchronized方法,看是否有影响。

两个自定义线程类分别调用不同的方法。

package p2;

public class ThreadA extends Thread{
   private MyObject1 myObject1;
   public ThreadA(MyObject1 object1) {
	   this.myObject1=object1;
   }
   public void run() {
	   myObject1.methodA();
   }
}
package p2;

public class ThreadB extends Thread{
   private MyObject1 myObject1;
   public ThreadB(MyObject1 object1) {
	   this.myObject1=object1;
   }
   public void run() {
	   myObject1.methodB();
   }
}

package p2;

public class Run1 {
	public static void main(String[] args) {
		 MyObject1 myObject1=new MyObject1();
		 ThreadA threadA=new ThreadA(myObject1);
		 ThreadB threadB=new ThreadB(myObject1);
		 threadA.start();
		 threadB.start();
	}
}

package p2;

public class MyObject1 {
  synchronized public void methodA() {
	 try {
		 System.out.println("begin synchronized methodA");
		 Thread.sleep(5000);
		 System.out.println("end synchronized methodA ");
	 }catch(InterruptedException e) {
		 e.printStackTrace();
	 }
  }
  public void methodB() {
	  try {
			 System.out.println("begin methodB");
			 Thread.sleep(5000);
			 System.out.println("end methodB");
		 }catch(InterruptedException e) {
			 e.printStackTrace();
		 }
  }
}

程序运行结果如下:

将methodB()方法前加上synchronized关键字,运行结果如下


该实验的结论是:

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

(2)A线程先持有object对象的Lock锁,B线程如果这时调用object对象中的synchronized类型的方法则需要等待,也就是同步。

  


2.1.5 synchronized锁重入

 关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时时可以再次得到该对象的锁的。这也证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。

package p2;

public class Run2 {
	  public static void main(String[] args) {
		MyThread2 myThread2=new MyThread2();
		myThread2.start();
		
	}
}

package p2;

public class MyThread2 extends Thread{
    public void run() {
    	Service service=new Service();
    	service.service1();
    }
}

public class Service {
  synchronized public void service1() {
	  System.out.println("service1");
	  service2();
  }
  synchronized public void service2() {
	  System.out.println("service2");
	  service3();
  }
  synchronized public void service3() {
	  System.out.println("service3");
	  
  }
}

运行结果:


“可重入锁”的概念是:自己可以再次获取自己的内部锁。比如有1条线程获得了某个对象的锁,此时这个对象还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。

   可重入锁也支持在父子继承的环境中,当存在父子继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。

2.1.7 出现异常,锁自动释放

 当一个线程执行的代码出现异常时,其所持有的所会自动释放。


2.2synchronized同步语句块

2.2.1

  用关键字synchronized声明方法在某些情况下是存在弊端的,比如A线程调用同步方法执行一个长时间的任务,那么B线程则必须等待比较长时间,在这样的情况下可以使用synchronized同步语句块来解决,synchronized方法是对当前对象进行加锁,而synchronized代码块是对某一个对象进行加锁。

  同步代码块的使用:

  synchronized(this){

     同步的执行语句

  } 

不在synchronized块的就是异步执行,在synchronized块中的就是同步执行。

public class Task {
   public void doLongTimeTask() {
	   for(int i=0;i<100;i++) {
		   System.out.println("no synchronized threadName"+Thread.currentThread().getName()+"  i="+i);
	
	   }
	   System.out.println("----------------");
	   synchronized (this) {
		 for(int i=0;i<100;i++) {
			 System.out.println("synchronized threadName"+Thread.currentThread().getName()+" i="+i);
		 }
	}
   }
}
package p2;

public class Run3 {
	public static void main(String[] args) {
		Task task=new Task();
		MyThread3 myThread3=new MyThread3(task);
		myThread3.start();
		MyThread3 myThread32=new MyThread3(task);
		myThread32.start();
	}
}

运行结果




  在使用同步synchronized(this)代码块时需要注意,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他的synchronized(this)同步代码块的访问将被阻塞。

  Java还支持对“任意对象”作为“对象监视器”来实现同步的功能,这个“任意对象”大多数是实例变量及方法的参数,使用格式为synchronized(非this对象),在多个线程持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。

  

package p2;

public class Run4 {
public static void main(String[] args) {
  MyService1 myService1=new MyService1();
  MyThread4 myThread4=new MyThread4(myService1);
  myThread4.start();
  MyThread4 myThread42=new MyThread4(myService1);
  myThread42.start();
}
}

package p2;

public class MyThread4 extends Thread{
   private MyService1 service;
   public MyThread4(MyService1 service) {
	   this.service=service;
   }
   public void run() {
	   service.setUsernamePassword("a", "aa");
   }
}

package p2;

public class MyService1 {
  private String username;
  private String passowrd;
  private String anyString=new String();
   public void setUsernamePassword(String username,String password) {
		try {
			synchronized (anyString) {
				System.out.println(
						"线程名称" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入同步块");
				this.username = username;
				Thread.sleep(3000);
				this.passowrd = password;
				System.out.println(
						"线程名称" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开同步块");
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
   }
}

锁非this对象具有一定的优点:如果一个类中有很多synchronized方法,这是虽然能实现同步,但会受到阻塞,影响运行效率,但如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,则可大大提高运行效率。

2.2.2静态同步synchronized方法与synchronized(class)代码块

  关键字synchronized还可以应用在static静态方法上,如果是这样写,那是对当前*.java文件对应的Class类进行持锁,与对象加锁不同,两者之间是异步的,且Class锁可以对类的所有对象实例起作用。使用synchronized(class)和 static方法加锁效果一样。

2.2.3 String的常量池特征

 在JVM中具有String常量池缓存的特征,因此在大多数情况下synchronized代码块都不使用String作为锁对象,而改用其他,比如new Object()实例化一个Object对象,但他并不放在缓存中。

2.2.4 锁对象的改变

  在将任何数据类型作为同步锁时,需要注意的是,是否有多个线程同时持有锁对象,如果同时持有锁对象,则这些线程之间是同步的,如果分别获得锁对象,这些线程之间是异步的。

package p2;

public class MyThread5 extends Thread{
   private MyService2 service;
   public MyThread5(MyService2 service) {
	   this.service=service;
   }
   public void run() {
	   service.testMethod();
   }
}

package p2;

public class MyService2 {
    private String lock="123";
    public void testMethod() {
		try {
			synchronized (lock) {
				System.out.println(
						"线程名称" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入同步块");
				lock="456";
				Thread.sleep(2000);
				System.out.println(
						"线程名称" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开同步块");
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
   }
}

package p2;

public class Run5 {
public static void main(String[] args) throws InterruptedException {
  MyService2 myService2=new MyService2();
  MyThread5 myThread5=new MyThread5(myService2);
  myThread5.start();
  MyThread5 myThread52=new MyThread5(myService2);
  Thread.sleep(50);
  myThread52.start();
}
}

50毫秒后,线程B取得的锁是“456”,锁对象发生了改变,因此变成了异步。

 将Thread.sleep(50)去掉,线程A和B持有的锁都是“123”,虽然将锁改成了“456”,但结果还是同步的,因为A和B共同争抢的锁是“123”。

 只要对象不变,即使对象的属性被改变,运行的结果还是同步的。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值