Java多线程:多线程的Synchronized详解
输出为什么要引入同步机制
在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。
解决方法:在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程便不能再使用那个资源,除非被解锁。
怎样实现同步
对于访问某个关键共享资源的所有方法,都必须把它们设为synchronized
例如:
synchronized void f() { /* ... */ } synchronized void g() { /* ... */ }
如果想保护某些资源不被多个线程同时访问,可以强制通过synchronized方法访问那些资源。 调用synchronized方法时,对象就会被锁定。
1 public class MyStack { 2 int idx = 0; 3 char [] data = new char[ 6]; 4 public synchronized void push( char c) { 5 data[ idx] = c; 6 idx++; 7 } 8 public synchronized char pop() { 9 idx--; 10 return data[ idx]; 11 } 12 }
说明:
•当synchronized方法执行完或发生异常时,会自动释放锁。
•被synchronized保护的数据应该是私有(private)的。
看几个例子
1 public class ThreadTest3 2 { 3 public static void main(String[] args) 4 { 5 Runnable r = new HelloThread(); 6 7 Thread t1 = new Thread(r); 8 9 //r = new HelloThread(); 10 11 Thread t2 = new Thread(r); 12 13 t1.start(); 14 t2.start(); 15 } 16 } 17 18 class HelloThread implements Runnable 19 { 20 int i; 21 22 @Override 23 public void run() 24 { 25 // int i = 0; 26 27 while(true) 28 { 29 System.out.println("number: " + i++); 30 31 try 32 { 33 Thread.sleep((long)(Math.random() * 1000)); 34 } 35 catch (InterruptedException e) 36 { 37 e.printStackTrace(); 38 } 39 40 if(5 == i) 41 { 42 break; 43 } 44 } 45 } 46 }
输出顺序执行:
程序执行时出现了两种输出,现在分别来分析一下:
为什么打印两个0:如文章开头所讲,由于两个线程同时访问一个资源,而且此资源未加锁就会导致访问冲突。
线程1打印语句还未执行++操作,线程2就进来了,所以导致打印两个0
为什么"只有一个线程输出":
只打印了0-4,给人感觉只打印了一条线程,其实这是两条线程共同作用的结果。
因为 i 这个变量是一个成员变量,而在同一个对象 r 中成员变量是共享的,当线程对 i 进行++操作,线程2中的 i 也会更改,所以打印出这样的结果
如果将 i 放在方法内即变成局部变量,则会打印两条线程的执行结果
总结:
关于成员变量与局部变量:如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,他们对该成员变量是彼此影响的(也就是说一个线程对成员变量的改变会影响到另一个线程)。
如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。
停止线程的方式:不能使用Thread类的stop方法来终止线程的执行。一般要设定一个变量,在run方法中是一个循环,循环每次检查该变量,如果满足条件则继续执行,否则跳出循环,线程结束。
synchronized关键字
synchronized关键字:当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法。
1 public class ThreadTest4 2 { 3 public static void main(String[] args) 4 { 5 Example example = new Example(); 6 7 Thread t1 = new TheThread(example); 8 9 example = new Example(); 10 11 Thread t2 = new TheThread2(example); 12 13 t1.start(); 14 t2.start(); 15 } 16 } 17 18 class Example 19 { 20 public synchronized void execute() 21 { 22 for(int i = 0; i < 5; i++) 23 { 24 try 25 { 26 Thread.sleep((long)(Math.random() * 1000)); 27 } 28 catch (InterruptedException e) 29 { 30 e.printStackTrace(); 31 } 32 33 System.out.println("hello: " + i); 34 } 35 } 36 37 public synchronized void execute2() 38 { 39 for(int i = 0; i < 5; i++) 40 { 41 try 42 { 43 Thread.sleep((long)(Math.random() * 1000)); 44 } 45 catch (InterruptedException e) 46 { 47 e.printStackTrace(); 48 } 49 50 System.out.println("world: " + i); 51 } 52 } 53 } 54 55 class TheThread extends Thread 56 { 57 private Example example; 58 59 public TheThread(Example example) 60 { 61 this.example = example; 62 } 63 64 @Override 65 public void run() 66 { 67 this.example.execute(); 68 } 69 } 70 71 class TheThread2 extends Thread 72 { 73 private Example example; 74 75 public TheThread2(Example example) 76 { 77 this.example = example; 78 } 79 80 @Override 81 public void run() 82 { 83 this.example.execute2(); 84 } 85 }
结果为
结果可以看出两条线程都同时执行了且互不影响。
当我们修改main方法
1 public class ThreadTest4 2 { 3 public static void main(String[] args) 4 { 5 Example example = new Example(); 6 7 Thread t1 = new TheThread(example); 8 9 Thread t2 = new TheThread2(example); 10 11 t1.start(); 12 t2.start(); 13 } 14 }
使用同一个 example,结果为
这个结果是必然的,不管你执行多少次。
很显然,线程在执行时是一个线程一个线程的执行的。
原因就如上面红字所讲的,当访问某个对象的synchronized方法时表示对该对象上锁,我们这里使用的是同一个example对象,所以只能等上一个线程执行完后才能执行下一个线程,而不能同时执行。
如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。
再来更改Example对象,在其方法上都加上 static 修饰
1 public synchronized static void execute() 2 { 3 for(int i = 0; i < 5; i++) 4 { 5 try 6 { 7 Thread.sleep((long)(Math.random() * 1000)); 8 } 9 catch (InterruptedException e) 10 { 11 e.printStackTrace(); 12 } 13 14 System.out.println("hello: " + i); 15 } 16 } 17 18 public synchronized static void execute2() 19 { 20 for(int i = 0; i < 5; i++) 21 { 22 try 23 { 24 Thread.sleep((long)(Math.random() * 1000)); 25 } 26 catch (InterruptedException e) 27 { 28 e.printStackTrace(); 29 } 30 31 System.out.println("world: " + i); 32 } 33 }
这时,不管你是否使用同一个example,执行结果都是先执行线程1在执行线程2.
原因就在于:
如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象,因为Java中无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,他们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行。
再对Example方法进行修改,改成一个static一个非static
1 public synchronized void execute() 2 { 3 for(int i = 0; i < 5; i++) 4 { 5 try 6 { 7 Thread.sleep((long)(Math.random() * 1000)); 8 } 9 catch (InterruptedException e) 10 { 11 e.printStackTrace(); 12 } 13 14 System.out.println("hello: " + i); 15 } 16 } 17 18 public synchronized static void execute2() 19 { 20 for(int i = 0; i < 5; i++) 21 { 22 try 23 { 24 Thread.sleep((long)(Math.random() * 1000)); 25 } 26 catch (InterruptedException e) 27 { 28 e.printStackTrace(); 29 } 30 31 System.out.println("world: " + i); 32 } 33 }
这时不管你是否使用同一个example,结果都是乱序,即两个线程同时执行,原因还是在于锁对象问题。
static锁的是class对象,非static锁的就是对象,两个锁的对象不同,结果自然不同。
synchronized代码块
synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;
synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内、synchronized块之外的代码是可以被多个线程同时访问到的。
所以更多情况下,我们选择把需要同步的逻辑写在synchronized块中。
1 synchronized(object) 2 { 3 。。。 4 }
表示线程在执行的时候会对object对象上锁
所以上面的程序就可以改为
1 public void execute() 2 { 3 synchronized (this) 4 { 5 for (int i = 0; i < 20; i++) 6 { 7 try 8 { 9 Thread.sleep((long) (Math.random() * 1000)); 10 } 11 catch (InterruptedException e) 12 { 13 e.printStackTrace(); 14 } 15 16 System.out.println("hello: " + i); 17 } 18 } 19 20 } 21 22 public void execute2() 23 { 24 synchronized(Example.class) 25 { 26 for (int i = 0; i < 20; i++) 27 { 28 try 29 { 30 Thread.sleep((long) (Math.random() * 1000)); 31 } 32 catch (InterruptedException e) 33 { 34 e.printStackTrace(); 35 } 36 37 System.out.println("world: " + i); 38 } 39 } 40 }
posted on 2016-04-19 11:47 Traveling_Light_CC 阅读(335) 评论(0) 编辑 收藏 举报