在Java代码中,多线程的环境下很多时候需要用到synchronized来控制线程同步的。
synchronized是Java中的关键字,是一种同步锁。它的用法主要分为一下几种:
1. 修饰一个普通方法:
被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的this对象;
测试:
public class BaseBean {
/**
* synchronized修饰普通方法
*/
public synchronized void test(String name){
System.out.println(name + ",普通test方法:进入锁");
try {
Thread.currentThread().sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ",普通test方法:离开锁");
}
/**
* synchronized修饰普通方法
*/
public synchronized void putongtest(String name){
System.out.println(name + ",普通putongtest方法:进入");
try {
Thread.currentThread().sleep(3*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ",普通putongtest方法:离开");
}out.println(name + ",普通方法:离开");
}
}
测试方法:
public static void main(String[] args ){
BaseBean baseBean = new BaseBean();
Thread thread1 = new Thread("线程1"){
@Override
public void run() {
baseBean.test("线程1");
}
};
BaseBean baseBean1 = new BaseBean();
Thread thread2 = new Thread("线程2"){
@Override
public void run() {
baseBean1.test("线程2");
//baseBean1.test("线程2");
//baseBean.putongtest("线程2");
}
};
thread1.start();
thread2.start();
}
首先测试不同线程同一个对象调用同一个同步方法;再测试不同线程不同对象调用同一个同步方法;最后再测试不同线程同一个对象调用不同的同步方法。得到测试结果如下:
线程同一个对象调用同一个同步方法。
线程1,普通test方法:进入锁
线程1,普通test方法:离开锁
线程2,普通test方法:进入锁
线程2,普通test方法:离开锁
不同线程不同对象调用同一个同步方法。
线程1,普通test方法:进入锁
线程2,普通test方法:进入锁
线程1,普通test方法:离开锁
线程2,普通test方法:离开锁
不同线程同一个对象调用不同的同步方法
线程1,普通test方法:进入锁
线程1,普通test方法:离开锁
线程2,普通putongtest方法:进入
线程2,普通putongtest方法:离开
可以得到结论:同一个对象的所有synchronized修饰的普通方法都公用一个this对象。这些方法再多线程环境中是线程串行地执行,同一时间只能运行一个线程,其他线程处于等待状态。当我们多次new不同的对象时,每个方法的this对象不一致。可并行运行。
2. 修饰代码块
被修饰的代码块称为同步代码块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象this;synchronized修饰普通方法和synchronized修饰this代码块的处理方式其实是一致的。
/**
* synchronized修饰代码块,对象是this
*/
public void test2(String name){
synchronized(this){
System.out.println(name + ",synchronized修饰test2代码块,对象是this:进入锁");
try {
Thread.currentThread().sleep(3 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ",synchronized修饰test2代码块:离开锁");
}
}
/**
* synchronized修饰代码块,对象是this
*/
public void putongtest2(String name){
synchronized(this){
System.out.println(name + ",synchronized修饰putongtest2代码块,对象是this:进入锁");
try {
Thread.currentThread().sleep(3 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ",synchronized修饰putongtest2代码块:离开锁");
}
}
这里就不展示测试结果了。synchronized修饰普通方法和synchronized修饰this代码块的处理方式其实是一致的。
在某些时候synchronized(obj)修饰的代码块,而且obj自己的方法中包含了 synchronized(this)代码块或者synchronized修饰的普通方法。那么这些代码公用一个obj对象锁,这些代码都是同步的。
测试方法如下;
构建一个类里面有不同的synchronized方法和代码块。
public class TestObject {
synchronized public void test1(){
System.out.println("TestObject-test1方法执行");
try {
Thread.currentThread().sleep(10* 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("TestObject-test1方法结束");
}
synchronized public void test2(){
System.out.println("TestObject-test2方法执行");
try {
Thread.currentThread().sleep(10* 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("TestObject-test2方法结束");
}
public void test3(){
synchronized (this) {
System.out.println("TestObject-test3方法执行");
try {
Thread.currentThread().sleep(10* 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("TestObject-test3方法结束");
}
}
}
在一个线程中分别执行这些方法。
public class TestThread extends Thread {
TestObject testObject;
String msg;
public TestThread(TestObject testObject, String msg){
this.testObject = testObject;
this.msg = msg;
}
@Override
public void run(){
try {
if ("1".equals(msg)) {
testObject.test1();
} else if ("2".equals(msg)) {
testObject.test2();
} else if ("3".equals(msg)) {
testObject.test3();
} else {
synchronized (testObject) {
System.out.println("TestThread方法执行");
try {
Thread.currentThread().sleep(10* 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("TestThread方法结束");
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
多线程进行测试:
public static void main(String[] args){
TestObject testObject = new TestObject();
TestThread testThread = new TestThread(testObject, "1");
TestThread testThread1 = new TestThread(testObject, "2");
TestThread testThread2 = new TestThread(testObject, "3");
TestThread testThread3 = new TestThread(testObject, "4");
testThread.start();
testThread1.start();
testThread2.start();
testThread3.start();
}
测试结果如下:
TestThread方法执行
TestThread方法结束
TestObject-test1方法执行
TestObject-test1方法结束
TestObject-test2方法执行
TestObject-test2方法结束
TestObject-test3方法执行
TestObject-test3方法结束
可以看到这些方法和代码块都是同步的。
3. 修改一个静态的方法:
其作用的范围是整个静态方法,作用的对象是这个类的所有对象,也就是class对象。
/**
* synchronized修饰静态方法
*/
public synchronized static void staticTest(String name){
System.out.println(name + ",静态方法staticTest:进入锁");
try {
Thread.currentThread().sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ",普通方法staticTest:离开锁");
}
/**
* synchronized修饰静态方法
*/
public synchronized static void staticTest2(String name){
System.out.println(name + ",静态方法staticTest2:进入锁");
try {
Thread.currentThread().sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ",普通方法staticTest2:离开锁");
}
测试方式:
public static void main(String[] args ){
BaseBean baseBean = new BaseBean();
Thread thread1 = new Thread("线程1"){
@Override
public void run() {
baseBean.staticTest("线程1");
}
};
BaseBean baseBean1 = new BaseBean();
Thread thread2 = new Thread("线程2"){
@Override
public void run() {
//baseBean.staticTest2("线程2");
//baseBean1.staticTest2("线程2");
baseBean.staticTest2("线程2");
}
};
thread1.start();
thread2.start();
}
首先测试不同线程同一个对象调用同一个同步方法;再测试不同线程不同对象调用同一个同步方法;最后再测试不同线程同一个对象调用不同的同步方法。得到测试结果如下:
测试 不同线程 同一个对象 调用 同一个同步代码块:
线程2,静态方法staticTest:进入锁
线程2,普通方法staticTest:离开锁
线程1,静态方法staticTest:进入锁
线程1,普通方法staticTest:离开锁
测试 不同线程 同一个对象 调用 不同的同步代码块:
线程2,静态方法staticTest:进入锁
线程2,普通方法staticTest:离开锁
线程1,静态方法staticTest:进入锁
线程1,普通方法staticTest:离开锁
测试 不同线程 不同对象 调用 不同的同步代码块:
线程2,静态方法staticTest2:进入锁
线程2,普通方法staticTest2:离开锁
线程1,静态方法staticTest:进入锁
线程1,普通方法staticTest:离开锁
测试 不同线程 同一个对象调用 不同的同步代码块:
线程1,静态方法staticTest:进入锁
线程1,普通方法staticTest:离开锁
线程2,静态方法staticTest2:进入锁
线程2,普通方法staticTest2:离开锁
可以得到结论:同一个bean的所有对象的所有synchronized修饰的静态方法都公用一个class对象。这些方法再多线程环境中是线程串行地执行,同一时间只能运行一个线程,其他线程处于等待状态。当我们多次new不同的对象时,每个方法的class对象不变。同一时间只能运行一个线程,其他线程处于等待状态。
4. 修改一个类(class)/静态成员变量:
作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象,即class对象。这个结果和synchronized修饰静态方法是同样的结果,这里就不再重复测试了。
synchronized修饰的代码中内层抛出异常,跳出synchronized代码块,会释放锁。所以其他的线程可以继续执行synchronized修饰的代码。
synchronized虽然是用来来定义方法,但synchronized并不属于方法定义的一部分。因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中如果重写方法,在子类中的这个方法如果需要同步就必须加上synchronized关键字才可以。