实例代码:
- public class MyTest {
- public static void main(String[] args) {
- SellTicketRunnable runnable = new SellTicketRunnable();
- //创建了三个线程
- Thread th1 = new Thread(runnable);
- Thread th2 = new Thread(runnable);
- Thread th3 = new Thread(runnable);
- //给三个线程重新命名
- th1.setName("窗口1");
- th2.setName("窗口2");
- th3.setName("窗口3");
- //开启线程
- th1.start();
- th2.start();
- th3.start();
- }
- }
-
-
- public class SellTicketRunnable implements Runnable {
- static int num = 100; //三个线程共享了这个数据(一共有100张票)
- @Override
- public void run() {
- while (true) {
- if (num > 0) {
- try {
- Thread.sleep(20); //模拟以下延迟
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "-正在出售" + (num--) + "张票");
- }
- }
- }
- }
-
现实中售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟.于是在代码中加入了一点延迟!这个不影响什么的!看起来没有什么问题!部分运行结果如下:
看到这运行结果,就发现问题了!现实中的售票情况怎么可能会几个窗口出售同一张票呢?甚至有的时候还会出现出售0票和负票的情况.这就是出现了线程安全问题!
那么重点来啦!为什么会出现线程安全问题呢?为什么会几个窗口出售同一张票,0票甚至负票呢?先来了解一下Java内存模型吧!!!
Java内存模型规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内存,线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来,也就是读)。线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
简单点就是,请看图!!!!!
如上这样毫无规矩,随机性的抢占,当然会坏事啦!试想一下,去医院看病,这般你争我抢,直接毁灭吧!!!说个玩笑话!这样的抢占,就引起了安全问题!怎么解决,相信友友们已经有想法了.
总结一下会出现安全隐患的标准: 得是多线程环境、有共享数据、有多条语句操作共享数据!
我们无法让其没有共享数据更没有办法让其不处于多线程的环境!如果不是多线程,那当然不会出现安全隐患的问题啦!那么只能从多条语句操作共享数据下手了!那在操作共享数据的时候,能不能一次只让一个进入呢?当然可以!所以就出现了锁! 把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可,操作完了,锁立马释放掉! 就不会出现线程安全问题啦!
- synchronized(对象){
- 要被同步的代码 ;
- }
注意: 不能在大括号里直接new 对象, new 了 就没效果了!
这个同步代码块保证数据的安全性的一个主要因素就是这个对象. 注意这个对象要定义为静态成员变量才能被所有线程共享.这个对象其实就是一把锁. 这个对象习惯叫做监视器.
内置锁
每个java对象都可以用做一个实现同步的锁,这些锁便被称为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。java内置锁是一个互斥锁. 同步代码代码块上的锁亦是如此. 这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
对象锁和类锁
java的对象锁和类锁在锁的概念上基本上和内置锁是一致的. 但是两个锁实际是有很大区别的,对象锁是用于对象实例方法或者一个对象实例上的,而类锁是用于类的静态方法或者一个类的class对象上的。类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。还有一点必须注意的哟!也就是类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的.
哈哈哈!不要晕乎乎!这个锁的分类只是想让友友们了解一下而已啦!
我们用锁解决一下上面案例出现的线程安全问题!!!!
- public class MyTest {
- public static void main(String[] args) {
- SellTicketRunnable runnable = new SellTicketRunnable();
- //创建了三个线程
- Thread th1 = new Thread(runnable);
- Thread th2 = new Thread(runnable);
- Thread th3 = new Thread(runnable);
- //给三个线程重新命名
- th1.setName("窗口1");
- th2.setName("窗口2");
- th3.setName("窗口3");
- //开启线程
- th1.start();
- th2.start();
- th3.start();
- }
- }
-
-
- public class SellTicketRunnable implements Runnable {
- static int num = 100; //三个线程共享
- static Object obj = new Object(); //上锁对象,静态成员变量
-
- @Override
- public void run() {
- while (true) {
- //th1 th2 th3中任意一个线程,一旦进入同步代码块,就会被加锁,其他没被加锁的对象便会在此等待
- synchronized (obj) { //这个就是锁
- if (num > 0) {
- try {
- Thread.sleep(20); //模拟延迟
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "-正在出售" + (num--) + "张票");
- }
- } //出了同步代码块,就会释放锁
- }
- }
- }
现在查看结果就不会出现几个窗口出售同一张票以及出售0票和负票的情况了!!!!这可是个好东西啊!!!
同步的出现 解决了多线程的安全问题。 其弊端就是,当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会 降低程序的运行效率。
(小编也在努力学习更多哟!以后再慢慢分享的啦!)
希望对友友们有所帮助!!!!