在多线程环境中,有可能会出现多个线程同时访问同一个资源的情况,资源可以是:一个文件、变量、对象、数据库表等。而单个线程的执行过程是不可控的,可能导致结果与逾期不同。这个资源被称为“临界资源”(也称共享资源)。
1.什么情况出现线程安全问题
情况重现:
public class Main {
public static void main(String[] args) {
// SyncThread sync1 = new SyncThread("sync1");
// SyncThread sync2 = new SyncThread("sync2");
// Thread thread1 = new Thread(sync1);
// Thread thread2 = new Thread(sync2);
// thread1.start();
// thread2.start();
for (int i = 0; i < 5; i++) {
Thread thread1 = new Thread(new SyncThread("sync1"));
thread1.start();
}
}
static class SyncThread implements Runnable {
private String name;
private static int count = 0;
public SyncThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
count++;
System.out.println(this.name+" : " + count);
}
}
}
}
根据代码来看,最终输出的结果应该是5*10=50,实际输出(省略):
sync1 : 1
sync1 : 3
sync1 : 4
sync1 : 5
sync1 : 6
sync1 : 7
sync1 : 8
sync1 : 9
sync1 : 48
sync1 : 49
sync1 : 44
sync1 : 50
其输出结果不会递增出现无规律;这就是典型的线程安全问题。当前一个线程拿到count值时,其他的某些线程也同时拿到count值,同时进行某操作导致写入的值一致。
2.Synchronized解决线程安全问题
Synchronized关键字的使用场景以及锁的对象归纳如下图:(静态方法属于类本身不属于对象)
- 修饰一个代码块
public class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
@Override
public synchronized void run() {
for (int i = 0; i < 10; i++) {
count++;
System.out.println(Thread.currentThread().getName() + " : " + count);
}
}
}
使用:
SyncThread run = new SyncThread();
Thread thread1 = new Thread(run, "sync1");
Thread thread2 = new Thread(run, "sync2");
thread1.start();
thread2.start();
输出:
sync1 : 1
sync1 : 2
sync1 : 3
sync1 : 4
sync1 : 5
sync1 : 6
sync1 : 7
sync1 : 8
sync1 : 9
sync1 : 10
sync2 : 11
sync2 : 12
sync2 : 13
sync2 : 14
sync2 : 15
sync2 : 16
sync2 : 17
sync2 : 18
sync2 : 19
sync2 : 20
一个线程访问一个对象中的synchronized(this)(锁住的该对象)同步代码块时,其他试图访问该对象的线程将被阻塞,直到该对象锁释放
修改上面的代码:
SyncThread run1 = new SyncThread();
SyncThread run2 = new SyncThread();
Thread thread1 = new Thread(run1, "sync1");
Thread thread2 = new Thread(run2, "sync2");
thread1.start();
thread2.start();
得到的输出完全不一致:
sync1 : 2
sync2 : 2
sync2 : 4
sync2 : 5
sync2 : 6
sync2 : 7
sync2 : 8
sync2 : 9
sync2 : 10
sync2 : 11
sync2 : 12
sync1 : 12
sync1 : 13
sync1 : 14
sync1 : 15
sync1 : 16
sync1 : 17
sync1 : 18
sync1 : 19
sync1 : 20
其余的使用类似,不一一列出。
总结:
修饰一个代码块:
- 一个线程访问一个对象中的
synchronized(this)
(锁住的该对象)同步代码块时,其他试图访问该对象的线程将被阻塞 - 一个对象只有
一个锁
- 当一个线程访问一个对象的synchronized(this)同步代码块时,另一个线程仍然可以访问该对象的
非synchronized(this)
同步代码块 - 当没有明确对象作为锁时,只想让一段代码同步时,可以创建
一个特殊的对象来充当锁
;零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
byte[] lock = new byte[0];
public void method(){
synchronized(lock){
}
}
修饰一个方法:
- public synchronized void method(){}
- synchronized 关键字不能
继承
定义接口方法
时不能使用synchronized关键字- 构造方法不能使用synchronized关键字,构造方法中可以使用synchronized代码块来进行同步
修饰一个静态方法:(静态方法属于类不属于对象)
- synchronized锁静态方法即锁
类的所有对象
修饰一个类:
- synchronized作用于一个类T时,是给这个类T加锁,T的
所有对象
用的是同一把锁。
参考自:
https://blog.csdn.net/xiao__gui/article/details/8188833
https://www.cnblogs.com/dolphin0520/p/3923737.html
http://www.importnew.com/21866.html