Java多线程学习篇(二)synchronized
synchronized 有二种修饰方法:
-
修饰一个方法
synchronized public void runTest{ /**/ }
-
修饰一个代码块
public void runTest{ synchronized( /*某一对象或某一类*/ ){ /* 代码块 */ } }
synchronized 的作用范围分为修饰一个类和修饰一共对象
当修饰一个对象时,不同线程的同一对象调用相同代码会发生堵塞
当修饰一个类时,不同线程的同一类调用相同代码会发生堵塞
修饰静态方法相当于修饰类
定义一个类(用于验证 synchronized 的作用范围)
public class Test implements Runnable { public static int Count = 0; @Override public void run() { runTest(); } public void runTest() { for (int i = 0; i < 5; ++i) { Count++; System.out.println(Thread.currentThread().getName() + " " + Count); } } }
当 synchronized 修饰一个方法时
-
若方法为非静态方法,作用的范围是一个对象
不同线程的同一对象调用该方法时会发生堵塞
//调用相同的对象
synchronized public void runTest() { // 修饰非静态的方法 for (int i = 0; i < 5; ++i) { Count++; System.out.println(Thread.currentThread().getName() + " " + Count); } }通过以下代码调用
Test test = new Test(); Thread thread_one = new Thread( test, "Thread_ONE" ); Thread thread_two = new Thread( test, "Thread_Two" ); thread_one.start(); thread_two.start();
结果是
Thread_ONE 1 Thread_ONE 2 Thread_ONE 3 Thread_ONE 4 Thread_ONE 5 Thread_Two 6 Thread_Two 7 Thread_Two 8 Thread_Two 9 Thread_Two 10
由于该调用是二个thread任务中的对象是同一个test,第一个thread任务运行时,会将第二个任务堵塞
若对象不是同一个,则不会发生堵塞
//调用二个不同的对象 Thread thread_one = new Thread(new Test(), "Thread_ONE"); Thread thread_two = new Thread(new Test(), "Thread_Two"); thread_one.start(); thread_two.start();
结果是
Thread_Two 2 Thread_ONE 1 Thread_Two 3 Thread_ONE 4 Thread_Two 5 Thread_ONE 6 Thread_Two 7 Thread_ONE 8 Thread_Two 9 Thread_ONE 10
-
当修饰的方法为静态的方法时,作用的范围是一个类,而非一个对象。
不同线程的相同类调用该方法时都会发生堵塞
//调用相同的对象 Test test = new Test(); Thread thread_one = new Thread( test, "Thread_ONE" ); Thread thread_two = new Thread( test, "Thread_Two" ); thread_one.start(); thread_two.start(); //调用二个不同的对象 Thread thread_one = new Thread(new Test(), "Thread_ONE"); Thread thread_two = new Thread(new Test(), "Thread_Two"); thread_one.start(); thread_two.start();
结果都为
Thread_ONE 1 Thread_ONE 2 Thread_ONE 3 Thread_ONE 4 Thread_ONE 5 Thread_Two 6 Thread_Two 7 Thread_Two 8 Thread_Two 9 Thread_Two 10
当 synchronized 修饰一个代码块时,作用的范围看括号内的内容。若括号内为对象,则范围是一个对象;若括号内为类,则范围是一个类
-
若括号内为对象,则范围是一个对象,效果和 synchronized 修饰非静态方法一样
public void runTest() { synchronized (this) { // 括号内为一个对象 for (int i = 0; i < 5; ++i) { Count++; System.out.println(Thread.currentThread().getName() + " " + Count); } } }
效果
//调用相同的对象 Test test = new Test(); Thread thread_one = new Thread( test, "Thread_ONE" ); Thread thread_two = new Thread( test, "Thread_Two" ); thread_one.start(); thread_two.start(); //发生类堵塞 //Thread_ONE 1 //Thread_ONE 2 //Thread_ONE 3 //Thread_ONE 4 //Thread_ONE 5 //Thread_Two 6 //Thread_Two 7 //Thread_Two 8 //Thread_Two 9 //Thread_Two 10 //调用二个不同的对象 Thread thread_one = new Thread(new Test(), "Thread_ONE"); Thread thread_two = new Thread(new Test(), "Thread_Two"); thread_one.start(); thread_two.start(); // 没有发生堵塞 //Thread_Two 2 //Thread_ONE 1 //Thread_Two 3 //Thread_ONE 4 //Thread_Two 5 //Thread_ONE 6 //Thread_Two 7 //Thread_ONE 8 //Thread_Two 9 //Thread_ONE 10
-
若括号内为类,则范围是一个对象,效果和 synchronized 修饰静态方法一样
public [static] void runTest() { synchronized (Test.class) { // 括号内为一个类 for (int i = 0; i < 5; ++i) { Count++; System.out.println(Thread.currentThread().getName() + " " + Count); } } }
二种调用都发生堵塞
Thread_ONE 1 Thread_ONE 2 Thread_ONE 3 Thread_ONE 4 Thread_ONE 5 Thread_Two 6 Thread_Two 7 Thread_Two 8 Thread_Two 9 Thread_Two 10
要注意的一点是,以上的例子不同的线程都是调用同一段的代码,有可能会出现误点,认为一段代码块对应一把锁。
synchronized的锁锁住的是对象,或者某个类。一个对象或者一个类对应一把锁。
class Example { public synchronized void printfA() { //synchronized (this) { for(int i = 1; i <= 100; ++i) System.out.println("A "+i); //} } public synchronized void printfB() { //synchronized (this) { for(int i = 1; i <= 100; ++i) System.out.println("B "+i); //} } } public class test{ Example ex = new Example(); RunA runa = new RunA(); RunB runb = new RunB(); class RunA implements Runnable { @Override public void run() { ex.printfA(); } } class RunB implements Runnable { @Override public void run() { ex.printfB(); } } public static void main(String[] args) { test t = new test(); Thread A = new Thread( t.runa ); Thread B = new Thread( t.runb ); A.start(); B.start(); } }
注意这个例子,当线程 A 调用了 printfA() 方法时,获得了对象 ex 的锁,而线程 B 调用了 printfB()方法,这里虽然和线程 A 调用的方法不一样,但是锁是同一个,都是 ex 的锁,所以锁已经被线程 A 获得,线程 B 将被堵塞,只是等线程 A 释放锁(运行完printfA())线程B才会运行。(有可能线程 B 比线程 A 先获得锁),所以 A 和 B 的输出不会出现交叉。
小结:多线程中 锁 的最主要的目的就是为了确保多线程间的安全性,同一时间只用一个线程对相同一处内存进行操作,而程序运行的本质之一也就是对内存的操作。静态类在编译的时候就分配类内存,锁的对象要是是静态的,那操作都是同一个内存,作用对象也是这个类;锁的对象要是非静态的,也就是是一个对象,那在对象实例化的时候分配内存,不同的对象有着不同的内存。
参考:http://tutorials.jenkov.com/java-concurrency/synchronized.html