因为之前对synchronized不是很了解,所以前面就照搬尚学堂的课程,这里经过一些学习,稍微有点理解了这个synchronized关键字的用法。下面就说一些synchronized注意事项:
- 当对某个对象进行锁定的时候
/**
* 银行账户类
*/
class Account {
String name;
float amount;
public Account(String name, float amount) {
this.name = name;
this.amount = amount;
}
//存钱
public void deposit(float amt) {
amount += amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取钱
public void withdraw(float amt) {
amount -= amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public float getBalance() {
return amount;
}
}
/**
* 账户操作类
*/
class AccountOperator implements Runnable{
private Account account;
public AccountOperator(Account account) {
this.account = account;
}
public void run() {
synchronized (account) {
account.deposit(500);
account.withdraw(500);
System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
}
}
public static void main(String[] args){
Account account = new Account("zhang san", 10000.0f);
AccountOperator accountOperator = new AccountOperator(account);
final int THREAD_NUM = 5;
Thread threads[] = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i ++) {
threads[i] = new Thread(accountOperator, "Thread" + i);
threads[i].start();
}
}
}
输出结果:Thread3:10000.0
Thread2:10000.0
Thread1:10000.0
Thread4:10000.0
Thread0:10000.0
在AccountOperator 类中的run方法里,我们用synchronized 给account对象加了锁。这时,当一个线程访问account对象时,其他试图访问account对象的线程将会阻塞,直到该线程访问account对象结束。也就是说谁拿到那个锁谁就可以运行它所控制的那段代码。
- 当锁定的是静态类或者静态方法
看内存图,对于静态static类和方法是存放在方法去中的,和new出来的类调用方式是不一样的,所以对于static变量和方法其实无论怎么创建,调用的都是同一个地址,静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。
/**
- 同步线程
*/
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public synchronized static void method() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void run() {
method();
}
public static void main(String[] args){
Account account = new Account("zhang san", 10000.0f);
AccountOperator accountOperator = new AccountOperator(account);
final int THREAD_NUM = 5;
Thread threads[] = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i ++) {
threads[i] = new Thread(accountOperator, "Thread" + i);
threads[i].start();
}
}
}
输出结果:
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
- 多个线程访问synchronized和非synchronized代码块
一个线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞。
public class TT implements Runnable{
private int b = 100;
public synchronized void m1(){
b = 1000;
try {
Thread.sleep(1000);
System.out.println("b="+b);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void m2(){
System.out.println(b);
}
@Override
public void run() {
m1();
}
public static void main(String[] args){
TT tt = new TT();
Thread t = new Thread(tt);
t.start(); //在这个线程启动之后,会睡眠3秒钟
try {
Thread.sleep(500); // 这个睡眠时间是让main线程休眠一会,要保证TT线程已经启动,已经执行到方法里面了
tt.m2(); //上面线程是启动锁定的线程,然而这个时候main线程还是可以启动。线程已经锁定了,b=1000了,main线程仍然在运行,而且输出
//为1000,所以线程虽然可以锁定,但是m2()方法没有锁定,仍然可以访问,所以main线程里面输出的不是100而是1000。
//说明一个线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:
1000
b=1000
下面是另外一个实例:
public class CpoyTT implements Runnable{
private int b = 100;
public synchronized void m1(){
b = 1000;
try {
Thread.sleep(2000);
System.out.println("b="+b);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void m2(){
try {
Thread.sleep(1000);
b = 2000;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
m1();
}
public static void main(String[] args){
CpoyTT tt = new CpoyTT();
Thread t = new Thread(tt);
t.start(); //在这个线程启动之后,会睡眠3秒钟
tt.m2(); //同TT,main线程还是可以访问没有被锁定的方法,如果m2()中的睡眠时间少于m1()中的话,会改变b的值
}
}
输出结果:b=2000
- 在用synchronized修饰方法时要注意以下几点:
synchronized关键字不能继承: 虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。这两种方式的例子代码如下:
在子类方法中加上synchronized关键字
class Parent {
public synchronized void method() { }
}
class Child extends Parent {
public synchronized void method() { }
}
在子类方法中调用父类的同步方法
class Parent {
public synchronized void method() { }
}
class Child extends Parent {
public void method() { super.method(); }
}
- 在定义接口方法时不能使用synchronized关键字。
- 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。