详解Java多线程编程中的线程同步方法
1、多线程的同步:
1.1、同步机制:
在多线程中,可能有多个线程试图访问一个有限的资源,必须预防这种情况的发生。所以引入了同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直到解锁后才可以访问。
1.2、共享成员变量的例子:
成员变量与局部变量:
成员变量:
如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作,这多个线程是共享一个成员变量的。
局部变量:
如果一个变量是局部变量,那么多个线程对同一个对象进行操作,每个线程都会有一个该局部变量的拷贝。他们之间的局部变量互不影响。
下面举例说明:
实现了runnable的线程类:
class mythread3 implements runnable{ //两个线程操作同一个对象,共享成员变量 //int i; @override public void run() { //两个线程操作同一个对象,各自保存局部变量的拷贝 int i = 0; while(i<100){ system.out.println(i); i++; try { thread.sleep(100); } catch (interruptedexception e) { e.printstacktrace(); } } } }
在main方法中用两个线程操作同一个对象:
public static void main(string[] args) { mythread3 mythread = new mythread3(); //下面两个线程对同一个对象(runnable的实现类对象)进行操作 thread thread = new thread(mythread); thread thread2 = new thread(mythread); //各自保存局部变量的拷贝,互不影响,输出200个数字 thread.start(); thread2.start(); }
这里如果把i变成成员变量,则输出100个数字。
1.3、共享资源导致的读取错误
下面举个例子,两个线程共用一个number对象,通过number类的getnumber方法获取数据,读取数据并改写时,发现了重复读操作:
首先创建一个number类:
class number{ private int number = 10; public string getnumber(int i){ if(number > 0){ try { thread.sleep(100); } catch (interruptedexception e) { e.printstacktrace(); } number -= i; return "取出"+i+"成功,剩余数量:"+number; } return "取出"+i+"失败,剩余数量:"+number; } }
线程类,在线程类中的私有属性包含了number类的引用:
class mythread4 extends thread{ //两个线程操作同一个对象,共享成员变量 number number; public mythread4(number number){ this.number = number; } @override public void run() { system.out.println(number.getnumber(8)); } }
在main函数中创建两个线程类,包含了同一个number类实例的引用:
public static void main(string[] args) { number number = new number(); //两个线程操作同一个对象,共享对象number的成员变量number mythread4 mythread = new mythread4(number); mythread4 mythread2 = new mythread4(number); mythread.start(); mythread2.start(); }
这样,当第一个线程读取number中的number变量时先保存下来再休眠0.1秒,然后第二个线程再读取number变量并保存,此时两个线程保存了同样的数字,在修改时,也就导致修改了同一个数字两次。
2、同步机制的实现:
在多线程并发编程中synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着java se1.6对synchronized进行了各种优化之后,有些情况下它并不那么重了”
java中的每一个对象都可以作为锁。
对于同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前对象的class对象。
对于同步方法块,锁是synchonized括号里配置的对象。
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
2.1、使用synchronized关键字创建synchronized方法:
使用synchronized关键字,该关键字修饰的方法叫做同步方法。
java中每个对象都有一个锁或者称为监视器,当访问某个对象的synchronized方法时,表示将该对象上锁,而不仅仅是为该方法上锁。
这样如果一个对象的synchronized方法被某个线程执行时,其他线程无法访问该对象的任何synchronized方法(但是可以调用其他非synchronized的方法)。直至该synchronized方法执行完。
静态的synchronized方法调用情况:
当调用一个对象的静态synchronized方法时,它锁定的并不是synchronized方法所在的对象,而是synchronized方法所在对象对应的class对象。这样,其他线程就不能调用该类的其他静态synchronized方法了,但是可以调用非静态的synchronized方法。
结论:执行静态synchronized方法锁方法所在对象,执行非静态synchronized方法锁方法所在对象对应的class对象。
下面是多线程调用静态的方法的例子,由于锁定了方法所在对象对应的class对象,其他线程无法调用该方法所在对象其他的静态synchronized方法:
/** * 定义一个类,包含了线程类需要调用的方法 */ class compute1{ //这时如果某个线程调用该方法, //将锁定synchronized方法所在对象对应的class对象, //而不是锁定synchronized方法所在对象 public synchronized static void execute(){ for(int i = 0; i<100; i++){ try { thread.sleep(100); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println("compute1:execute1 " + i++); } } public synchronized static void execute2(){ for(int i = 0; i<100; i++){ try { thread.sleep(100); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println("compute1:execute2 " + i++); } } }
main方法中两个线程分别调用同一个对象的两个static synchronized方法:
public static void main(string[] args) { compute1 com = new compute1(); thread thread1 = new thread1(com); thread thread2 = new thread2(com); thread1.start(); thread2.start(); }
一次只能调用一个静态方法,直到执行完成。
2.2、使用synchronized创建同步代码块:
通过使用synchronized同步代码块,锁定一个对象,该对象作为可执行的标志从而达到同步的效果:
/** * 定义一个类,包含了线程类需要调用的方法 */ class compute1{ //通过同步代码块锁定object1对象进行锁定了其他同样的synchronized代码块 private object object1 = new object(); public void execute(){ synchronized(object1){ for(int i = 0; i<100; i++){ try { thread.sleep(100); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println("compute1:execute1 " + i++); } } } public synchronized void execute2(){ synchronized(object1){ for(int i = 0; i<100; i++){ try { thread.sleep(100); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println("compute1:execute2 " + i++); } } } }
如果想要使用synchronized同步代码块达到和使用synchronized方法同样的效果,可以锁定this引用:
synchronized(this){ … }
2.3、synchronized方法和synchronized同步代码块的区别:
synchronized同步代码块只是锁定了该代码块,代码块外面的代码还是可以被访问的。
synchronized方法是粗粒度的并发控制,某一个时刻只能有一个线程执行该synchronized方法。
synchronized同步代码块是细粒度的并发控制,只会将块中的代码同步,代码块之外的代码可以被其他线程同时访问。
上一篇: java中set接口使用方法详解