Java之使用synchronized解决多线程安全性问题
什么是线程安全呢?当多个线程并发访问某个Java对象(Object)时,无论系统如何调度这些线程,也无论这些线程将如何交替操作,这个对象都能表现出一致的、正确的行为,那么对这个对象的操作是线程安全的。如果这个对象表现出不一致的、错误的行为,那么对这个对象的操作不是线程安全的,发生了线程的安全问题。
平时生活中的买票场景就是一个典型的线程安全的场景;车票作为多个线程争抢的稀缺资源,如果我们不对车票资源的占用顺序进行限制,那么可能会出现重票或者错票的问题;
public void sellTicketTest() throws InterruptedException {
class TicketOperator implements Runnable{
int count = 100;
@Override
public void run() {
while(count > 0){
System.out.println(Thread.currentThread().getName() + ":" + count);
count--;
}
}
}
var operator = new TicketOperator();
var t1 = new Thread(operator);
t1.setName("t1");
var t2 = new Thread(operator);
t2.setName("t2");
var t3 = new Thread(operator);
t3.setName("t3");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
// t2:100
// t2:99
// t2:98
// t2:97
// t2:96
// t2:95
// t2:94
// t1:100
// t3:100
// t3:91
// t3:90
// t3:89
// t2:93
// t1:92
// t1:86
// t3:88
// t2:87
// t1:85
// t3:84
// t2:83
// t1:82
// t3:81
// t2:80
// t1:79
// t1:76
// t1:75
// t1:74
// t1:73
// t1:72
// t1:71
// t1:70
// t3:78
// t2:77
// t1:69
// t1:66
// t1:65
// t1:64
// t1:63
// t3:68
// t2:67
// t1:62
// t1:59
// t1:58
// t1:57
// t1:56
// t1:55
// t1:54
// t1:53
// t3:61
// t2:60
// t2:50
// t2:49
// t2:48
// t1:52
// t3:51
// t2:47
// t1:46
// t3:45
// t3:42
// t3:41
// t3:40
// t3:39
// t2:44
// t1:43
// t3:38
// t3:35
// t3:34
// t3:33
// t3:32
// t2:37
// t1:36
// t1:29
// t1:28
// t1:27
// t1:26
// t1:25
// t1:24
// t3:31
// t3:22
// t3:21
// t2:30
// t2:19
// t2:18
// t2:17
// t2:16
// t2:15
// t2:14
// t2:13
// t2:12
// t1:23
// t3:20
// t3:9
// t2:11
// t1:10
// t1:6
// t1:5
// t1:4
// t1:3
// t3:8
// t3:1
// t2:7
// t1:2
}
Java提供了synchronized关键来实现同步锁的功能,synchronized添加到操作共享资源的代码块外边,来实现对共享资源的同步访问;
当一个线程进入到共享代码区,则其他的线程必须等待当前线程释放同步锁;
线程的同步在于线程获得了synchronized后边对象的锁,故这个对象对于所有的线程来说都必须是同一个对象;
public void lockSellTicketTest() throws InterruptedException {
class TicketOperator implements Runnable{
int count = 100;
@Override
public void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (this){
if(count > 0){
System.out.println(Thread.currentThread().getName() + ":" + count);
count--;
}
else{
break;
}
}
}
}
}
var operator = new TicketOperator();
var t1 = new Thread(operator);
t1.setName("t1");
var t2 = new Thread(operator);
t2.setName("t2");
var t3 = new Thread(operator);
t3.setName("t3");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
// t3:100
// t1:99
// t2:98
// t3:97
// t1:96
// t2:95
// t1:94
// t2:93
// t3:92
// t2:91
// t3:90
// t1:89
// t3:88
// t1:87
// t2:86
// t2:85
// t3:84
// t1:83
// t2:82
// t1:81
// t3:80
// t3:79
// t2:78
// t1:77
// t3:76
// t2:75
// t1:74
// t3:73
// t1:72
// t2:71
// t2:70
// t3:69
// t1:68
// t2:67
// t1:66
// t3:65
// t1:64
// t2:63
// t3:62
// t2:61
// t1:60
// t3:59
// t3:58
// t2:57
// t1:56
// t2:55
// t3:54
// t1:53
// t1:52
// t2:51
// t3:50
// t1:49
// t2:48
// t3:47
// t1:46
// t3:45
// t2:44
// t3:43
// t2:42
// t1:41
// t2:40
// t1:39
// t3:38
// t1:37
// t2:36
// t3:35
// t3:34
// t2:33
// t1:32
// t3:31
// t1:30
// t2:29
// t2:28
// t3:27
// t1:26
// t3:25
// t2:24
// t1:23
// t1:22
// t2:21
// t3:20
// t1:19
// t2:18
// t3:17
// t1:16
// t2:15
// t3:14
// t2:13
// t1:12
// t3:11
// t1:10
// t2:9
// t3:8
// t1:7
// t3:6
// t2:5
// t2:4
// t1:3
// t3:2
// t2:1
}
synchronized可以直接使用在方法前边,从而实现方法体的同步访问;添加在方法前边时,其默认获得的锁对象跟具体的方法有关系,如果是实例方法则是this,如果是静态方法则是对应类的class对象;
@Test
public void lockMethodSellTicketTest() throws InterruptedException {
class TicketOperator implements Runnable{
int count = 100;
boolean saleOut = false;
@Override
public void run() {
while(!saleOut){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
sale();
}
}
synchronized void sale(){
if(count > 0){
System.out.println(Thread.currentThread().getName() + ":" + count);
count--;
}
else{
saleOut = true;
}
}
}
var operator = new TicketOperator();
var t1 = new Thread(operator);
t1.setName("t1");
var t2 = new Thread(operator);
t2.setName("t2");
var t3 = new Thread(operator);
t3.setName("t3");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
// t3:100
// t1:99
// t2:98
// t3:97
// t1:96
// t2:95
// t1:94
// t2:93
// t3:92
// t2:91
// t3:90
// t1:89
// t3:88
// t1:87
// t2:86
// t2:85
// t3:84
// t1:83
// t2:82
// t1:81
// t3:80
// t3:79
// t2:78
// t1:77
// t3:76
// t2:75
// t1:74
// t3:73
// t1:72
// t2:71
// t2:70
// t3:69
// t1:68
// t2:67
// t1:66
// t3:65
// t1:64
// t2:63
// t3:62
// t2:61
// t1:60
// t3:59
// t3:58
// t2:57
// t1:56
// t2:55
// t3:54
// t1:53
// t1:52
// t2:51
// t3:50
// t1:49
// t2:48
// t3:47
// t1:46
// t3:45
// t2:44
// t3:43
// t2:42
// t1:41
// t2:40
// t1:39
// t3:38
// t1:37
// t2:36
// t3:35
// t3:34
// t2:33
// t1:32
// t3:31
// t1:30
// t2:29
// t2:28
// t3:27
// t1:26
// t3:25
// t2:24
// t1:23
// t1:22
// t2:21
// t3:20
// t1:19
// t2:18
// t3:17
// t1:16
// t2:15
// t3:14
// t2:13
// t1:12
// t3:11
// t1:10
// t2:9
// t3:8
// t1:7
// t3:6
// t2:5
// t2:4
// t1:3
// t3:2
// t2:1
}
synchronized在实现多线程同步共享资源的同时,必然也限制了多线程的并发执行,增长了程序的执行时间;