一、原子操作
原子操作:不可中断的一个或者一系列操作, 也就是不会被线程调度机制打断的操作, 运行期间不会有任何的上下文切换。
可以定义为原子操作的有:对于非long和double基本数据类型的"简单操作"都可以看作是原子的. 例如: 赋值和返回, 大多数体系中long和double都占据8个字节, 操作系统或者JVM很可能会将写入和读取操作分离为两个单独的32位的操作来执行, 这就产生了在一个读取和写入过程中一个上下文切换(context switch), 从而导致了不同任务线程看到不正确结果的的可能性。
二、线程同步问题
举一个例子,我们写一个取票程序,在我们没有添加任何操作时,使用多个线程对同一个票数的数据进行修改。票数总数为100.
public void run(){
for(int i=0;i<50;i++){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 买到"+ tk.tick +"票...");
tk.tick--;
}
}
public static void main(String[] args) {
Ticket tk = new Ticket();
UserThread ut = new UserThread(tk);
Thread t1 = new Thread(ut);
t1.setName("USER1");
t1.start();
Thread t2 = new Thread(ut);
t2.setName("USER2");
t2.start();
}
我们对其输出,可以看到结果为
可以发现出现了两个人买了同一张票的情况,还有按序票有没有购买的情况。
这时我们就需要使用synchronized关键字或者自己上锁来避免线程同步问题。
synchronized (监视器(锁)){ }
任何一个对象都是作为监视使用,但所有的线程必须使用同一个监视器。
public void run(){
for(int i=0;i<50;i++){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//继承类Thread时,使用tk,不能使用this,在使用接口时可以使用this
synchronized (tk){
System.out.println(Thread.currentThread().getName()+" 买到"+ tk.tick +"票...");
tk.tick--;
}
}
}
//使用synchronized方法也可以
public synchronized void mp(){
System.out.println(Thread.currentThread().getName()+" 买到"+ tk.tick +"票...");
tk.tick--;
}
//或者对其进行主动上锁
try {
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName()+" 买到"+ tk.tick +"票...");
tk.tick--;
}finally {
//释放锁
lock.unlock();
}