原子操作:所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切[1] 换到另一个线程)。
关于我对原子操作的理解:原子操作就类似于化学中的原子为不可分割的单位,也就是如果把需要操作的代码块能够顺序执行中间不为被干扰。
这样就不会出现线程不安全情况(案例中的购票系统出现负数的情况),这种原子操作思想还是挺有用的,在这提提自己也不了解=-=。
解决方案:保证打印编号和操作必须同步执行:System.out.println(Thread.currentThread().getName()+”—卖出的票”+tickets–);
也就是上述代码中ticket–与输出同步执行,不能因为某个线程输出后就休眠而不执行减减操作。
方式一、同步代码块:
语法:
synchronize(同步锁){需要同步操作的代码}
案例:
package com.test;//线程安全
public class Main { public static void main(String[] args){ SaleThread saleThread=new SaleThread(); new Thread(saleThread,"线程一").start(); new Thread(saleThread,"线程二").start(); new Thread(saleThread,"线程三").start(); new Thread(saleThread,"线程四").start(); }
}
class SaleThread implements Runnable{ private int tickets=10; public void run(){ //synchronized (this) {while(tickets>0){ try{ Thread.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"---卖出的票"+tickets--); // } } }
}
*输出结果:
线程二—卖出的票10
线程一—卖出的票9
线程四—卖出的票8
线程三—卖出的票7
线程二—卖出的票5
线程一—卖出的票6
线程三—卖出的票4
线程四—卖出的票3
线程一—卖出的票1
线程二—卖出的票2
线程三—卖出的票-1
线程四—卖出的票0*
分析:上述结果中出现负数和0情况(如果数据量大还会出现重复情况)。
方式二、同步方法
使用synchronized修饰的方法就叫同步方法,表示a线程在执行该方法的时候其他线程只能等待。
代码:
synchronized public void run(){ while(tickets>0){ try{ Thread.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"---卖出的票"+tickets--); }
**问题来了上述代码中synchronized中的同步锁是谁?**对于非static方法同步锁就是this对于static方法,我们使用当前方法所在类的字节码对象(当前类名.class)
方式三、同步锁-锁机制lock
为了保证每个线程都能正常执行原子操作,java引入了线程同步机制。
同步监听对象/同步锁/同步监听器/互斥锁(a进去b被排斥,保证只有一个进程执行)
对象的同步锁只是一个概念,可以想象为在对象上标记一个锁。
java程序运行使用任何对象作为同步监听对象,但是一般的,我们试验当前并发访问的共同资源作为同步监听对象。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他线程只能等待。