线程同步
为什么要使用线程同步呢?当有多个线程同时操作一个可共享的资源时,如果线程A修改完数据之后,还没来得及把新结果返回,此时线程B又来操作此数据,则此时线程B就获取到了错误的数据,得到的结果就可能出现严重错误。因此我们引入线程同步,当线程A在操作数据时,不允许其他线程操作此数据,待A线程工作完成之后在允许其他线程工作,这样就不会出现数据的混乱的现象了。
一、synchronized关键字的作用及范围
1、synchronized关键字作用在实例方法上:
设置工具类
public class SynchronizeUtil1 {
public synchronized void minus(){
int count = 5;
for (int i = 0; i < 5; i++){
count--;
System.out.println(Thread.currentThread().getName() + " count: " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void minus2(){
int count = 5;
for (int i = 0; i < 5; i++){
count--;
System.out.println(Thread.currentThread().getName() + " count: " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试
public class SynchronizeTest1 {
public static void main(String[] args) {
final SynchronizeUtil1 synchronizeUtil1 = new SynchronizeUtil1();
Thread t1 = new Thread(new Runnable() {
public void run() {
synchronizeUtil1.minus();
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
synchronizeUtil1.minus();
}
});
t1.start();
t2.start();
}
}
结果
Thread-0 count: 4
Thread-0 count: 3
Thread-0 count: 2
Thread-0 count: 1
Thread-0 count: 0
Thread-1 count: 4
Thread-1 count: 3
Thread-1 count: 2
Thread-1 count: 1
Thread-1 count: 0
因为实例方法是被synchronized关键字修饰,所以上面创建的两个线程都需要获得对象锁 一个线程获得后另一个线程需要等待。
现在有一个问题:其他线程是否能够访问此对象的其他同步方法或者非同步方法呢?
经过测试得知,其他线程不能够访问此对象的其他同步方法,但是却可以调用此对象的非同步方法。
2、当synchronized关键字放在静态方法上的时候,看下面实例:
public class SynchronizeUtil1 {
public static synchronized void minus(){
int count = 5;
for (int i = 0; i < 5; i++){
count--;
System.out.println(Thread.currentThread().getName() + " count: " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static synchronized void minus1(){
int count = 5;
for (int i = 0; i < 5; i++){
count--;
System.out.println(Thread.currentThread().getName() + " count: " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void minus2(){
int count = 5;
for (int i = 0; i < 5; i++){
count--;
System.out.println(Thread.currentThread().getName() + " count: " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果
Thread-0 count: 4
Thread-1 count: 4
Thread-0 count: 3
Thread-1 count: 3
Thread-1 count: 2
Thread-0 count: 2
Thread-1 count: 1
Thread-0 count: 1
Thread-0 count: 0
Thread-1 count: 0
Thread-2 count: 4
Thread-2 count: 3
Thread-2 count: 2
Thread-2 count: 1
Thread-2 count: 0
看到结果可以得知,synchronized修饰static方法和实例方法作用及范围是一样的。
注意: synchronized关键字不能修饰抽象类中的抽象方法,也不能是接口中的方法。
3、synchronized关键字修饰同步块
同步是一个开销很大的操作,所以我们应尽量减小synchronized关键字的修饰范围,synchronized关键字还可以修饰代码块,类似下面这种:
public void save(int money){
synchronized (this){
account += money;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
如果一个对象既有同步方法,又有同步块,那么当其中任意一个同步方法或者同步块被某个线程执行时,这个对象就被锁定了,其他线程无法在此时访问这个对象的同步方法,也不能执行同步块。