java对于多线程的安全问题提供了专业的解决方式: 同步机制
什么时候需要使用synchronized :
只有共享资源的读写访问才需要同步化,如果不是共享资源那么根本就没有必要同步
目的:就是使得共享数据变得更加安全。
互斥锁
1)引入对象互斥锁,来保证共享数据操作的完整性。
2每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
关键字synchronized 来与对象的互斥锁联系
1)当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
2)同步的局限性:导致程序的执行效率要降低
3)同步方法(非静态的)的锁为this。
4)同步方法(静态的)的锁为当前类本身。
举一个售票的例子:有三个窗口同时售100张票,可能自己运行一百次都不会出现重票等错误其他情况,程序运行结果没有错并不代表这个程序没有错误。现在我们将程序的错误放大 我让每个线程都睡10毫秒。
package com.ghl.demo;
class Window implements Runnable {
static int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,售票号为:" + ticket--);
} else {
break;
}
}
}
}
public class TestWindow {
public static void main(String[] args) {
Window w1 = new Window();
Thread th = new Thread(w1);
Thread th1 = new Thread(w1);
Thread th2 = new Thread(w1);
th.setName("窗口1");
th1.setName("窗口2");
th2.setName("窗口3");
th.start();
th1.start();
th2.start();
}
}
返回结果:
窗口1售票,售票号为:100
窗口2售票,售票号为:98
窗口3售票,售票号为:99
窗口1售票,售票号为:97
窗口2售票,售票号为:97
窗口3售票,售票号为:96
窗口2售票,售票号为:95
窗口1售票,售票号为:94
窗口3售票,售票号为:95
窗口2售票,售票号为:93
窗口3售票,售票号为:93
窗口1售票,售票号为:93
窗口2售票,售票号为:92
窗口1售票,售票号为:90
窗口3售票,售票号为:91
窗口3售票,售票号为:89
窗口1售票,售票号为:88
窗口2售票,售票号为:87
窗口3售票,售票号为:86
窗口1售票,售票号为:86
窗口2售票,售票号为:85
窗口3售票,售票号为:84
窗口1售票,售票号为:84
窗口2售票,售票号为:83
窗口1售票,售票号为:82
窗口3售票,售票号为:82
窗口2售票,售票号为:82
窗口3售票,售票号为:81
窗口2售票,售票号为:81
窗口1售票,售票号为:80
窗口2售票,售票号为:79
窗口3售票,售票号为:78
窗口1售票,售票号为:79
窗口2售票,售票号为:77
窗口3售票,售票号为:76
窗口1售票,售票号为:75
窗口3售票,售票号为:74
窗口1售票,售票号为:73
窗口2售票,售票号为:72
窗口3售票,售票号为:71
窗口1售票,售票号为:71
窗口2售票,售票号为:71
窗口1售票,售票号为:70
窗口3售票,售票号为:68
窗口2售票,售票号为:69
窗口3售票,售票号为:67
窗口2售票,售票号为:67
窗口1售票,售票号为:67
窗口2售票,售票号为:66
窗口3售票,售票号为:65
窗口1售票,售票号为:64
窗口2售票,售票号为:63
窗口3售票,售票号为:61
窗口1售票,售票号为:62
窗口2售票,售票号为:60
窗口3售票,售票号为:59
窗口1售票,售票号为:58
窗口2售票,售票号为:57
窗口3售票,售票号为:56
窗口1售票,售票号为:55
窗口2售票,售票号为:54
窗口3售票,售票号为:53
窗口1售票,售票号为:52
窗口2售票,售票号为:51
窗口3售票,售票号为:50
窗口1售票,售票号为:49
窗口2售票,售票号为:48
窗口3售票,售票号为:47
窗口1售票,售票号为:46
窗口2售票,售票号为:45
窗口3售票,售票号为:44
窗口1售票,售票号为:43
窗口2售票,售票号为:42
窗口3售票,售票号为:41
窗口1售票,售票号为:40
窗口2售票,售票号为:39
窗口3售票,售票号为:38
窗口1售票,售票号为:37
窗口2售票,售票号为:36
窗口3售票,售票号为:35
窗口1售票,售票号为:34
窗口2售票,售票号为:33
窗口3售票,售票号为:32
窗口1售票,售票号为:31
窗口2售票,售票号为:30
窗口3售票,售票号为:29
窗口2售票,售票号为:28
窗口1售票,售票号为:27
窗口3售票,售票号为:26
窗口2售票,售票号为:25
窗口1售票,售票号为:24
窗口3售票,售票号为:23
窗口1售票,售票号为:22
窗口2售票,售票号为:22
窗口3售票,售票号为:21
窗口1售票,售票号为:20
窗口2售票,售票号为:19
窗口3售票,售票号为:18
窗口2售票,售票号为:17
窗口1售票,售票号为:17
窗口3售票,售票号为:16
窗口2售票,售票号为:15
窗口1售票,售票号为:15
窗口3售票,售票号为:14
窗口2售票,售票号为:13
窗口1售票,售票号为:12
窗口3售票,售票号为:11
窗口2售票,售票号为:9
窗口1售票,售票号为:10
窗口3售票,售票号为:8
窗口2售票,售票号为:7
窗口1售票,售票号为:7
窗口3售票,售票号为:6
窗口2售票,售票号为:4
窗口1售票,售票号为:5
窗口3售票,售票号为:3
窗口1售票,售票号为:2
窗口2售票,售票号为:2
窗口3售票,售票号为:1
窗口2售票,售票号为:0
窗口1售票,售票号为:-1
从上面的结果我们可以看出,即有重复的票也有负数的票。上面的问题存在了线程安全的问题。
线程安全问题出现的原因
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
java如何解决线程安全问题(两种方式)
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。类似于上厕所,一个一个来,排队
格式:
方式一、使用同步代码块(synchronized )来解决
- synchronized (同步监视器){
// 需要被同步的代码;
}
1 共享数据:多个线程共同操作同一个数据。
2)同步监视器:由一个类的对象来充当,哪个类获得此监视器,哪个类就只执行大括号内部的代码,也叫锁。
package com.ghl.demo;
class Window implements Runnable {
static int ticket = 100;
Object obj=new Object();//锁 也就是一个对象
public void run() {
while (true) {
synchronized (this) {//synchronized用在需要共享数据的地方,如果不是继承的可以this代表当前对象,也可以自定义对象来充当锁
//synchronized (obj)
if (ticket > 0) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,售票号为:" + ticket--);
} else {
break;
}
}
}
}
}
public class TestWindow {
public static void main(String[] args) {
Window w1 = new Window();
Thread th = new Thread(w1);
Thread th1 = new Thread(w1);
Thread th2 = new Thread(w1);
th.setName("窗口1");
th1.setName("窗口2");
th2.setName("窗口3");
th.start();
th1.start();
th2.start();
}
}
返回结果
窗口1售票,售票号为:100
窗口2售票,售票号为:99
窗口2售票,售票号为:98
窗口2售票,售票号为:97
窗口2售票,售票号为:96
窗口2售票,售票号为:95
窗口2售票,售票号为:94
窗口2售票,售票号为:93
窗口2售票,售票号为:92
窗口2售票,售票号为:91
窗口2售票,售票号为:90
窗口2售票,售票号为:89
窗口2售票,售票号为:88
窗口2售票,售票号为:87
窗口2售票,售票号为:86
窗口2售票,售票号为:85
窗口2售票,售票号为:84
窗口2售票,售票号为:83
窗口2售票,售票号为:82
窗口2售票,售票号为:81
窗口2售票,售票号为:80
窗口2售票,售票号为:79
窗口2售票,售票号为:78
窗口2售票,售票号为:77
窗口2售票,售票号为:76
窗口2售票,售票号为:75
窗口2售票,售票号为:74
窗口2售票,售票号为:73
窗口2售票,售票号为:72
窗口2售票,售票号为:71
窗口2售票,售票号为:70
窗口2售票,售票号为:69
窗口2售票,售票号为:68
窗口2售票,售票号为:67
窗口2售票,售票号为:66
窗口2售票,售票号为:65
窗口2售票,售票号为:64
窗口2售票,售票号为:63
窗口2售票,售票号为:62
窗口2售票,售票号为:61
窗口2售票,售票号为:60
窗口2售票,售票号为:59
窗口2售票,售票号为:58
窗口2售票,售票号为:57
窗口2售票,售票号为:56
窗口2售票,售票号为:55
窗口2售票,售票号为:54
窗口2售票,售票号为:53
窗口2售票,售票号为:52
窗口2售票,售票号为:51
窗口2售票,售票号为:50
窗口2售票,售票号为:49
窗口2售票,售票号为:48
窗口2售票,售票号为:47
窗口2售票,售票号为:46
窗口2售票,售票号为:45
窗口2售票,售票号为:44
窗口2售票,售票号为:43
窗口2售票,售票号为:42
窗口2售票,售票号为:41
窗口2售票,售票号为:40
窗口2售票,售票号为:39
窗口2售票,售票号为:38
窗口2售票,售票号为:37
窗口2售票,售票号为:36
窗口2售票,售票号为:35
窗口2售票,售票号为:34
窗口2售票,售票号为:33
窗口2售票,售票号为:32
窗口2售票,售票号为:31
窗口2售票,售票号为:30
窗口2售票,售票号为:29
窗口2售票,售票号为:28
窗口2售票,售票号为:27
窗口2售票,售票号为:26
窗口2售票,售票号为:25
窗口2售票,售票号为:24
窗口2售票,售票号为:23
窗口2售票,售票号为:22
窗口2售票,售票号为:21
窗口2售票,售票号为:20
窗口2售票,售票号为:19
窗口2售票,售票号为:18
窗口2售票,售票号为:17
窗口2售票,售票号为:16
窗口2售票,售票号为:15
窗口2售票,售票号为:14
窗口2售票,售票号为:13
窗口2售票,售票号为:12
窗口2售票,售票号为:11
窗口2售票,售票号为:10
窗口2售票,售票号为:9
窗口2售票,售票号为:8
窗口2售票,售票号为:7
窗口2售票,售票号为:6
窗口2售票,售票号为:5
窗口2售票,售票号为:4
窗口2售票,售票号为:3
窗口2售票,售票号为:2
窗口2售票,售票号为:1
方式二、同步方法 跟上面的基本一样 这里就不做重复了
2. synchronized还可以放在方法声明中,表示整个方法为同步方法。
public synchronized void show (String name){
需要同步数据的代码
}
释放锁的操作
1)当前线程的同步方法、同步代码块执行结束
2)当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
3)当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
4)当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。