原子操作:所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 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程序运行使用任何对象作为同步监听对象,但是一般的,我们试验当前并发访问的共同资源作为同步监听对象。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他线程只能等待。