保证线程安全
被synchronized修饰的内容,保证了在同一时刻,只能有一个线程访问此内容,从而实现了线程安全。
原理:当多个线程访问同一资源时,每个线程会尝试获得使用该资源的锁,如果拿到锁则执行该内容,执行后释放锁,其它线程继续竞争锁...
例举一个购买火车票的场景,初始化100张票,启动5个线程顺序的来模拟买票,然后打印买票结果:
package cn.zealon.lock;
/**
* synchronized 锁
*/
public class UseSynchronize1 implements Runnable {
//初始化100张火车票
public int tickets = 100;
/**
* synchronized 保证线程安全
* 说明:被synchronized修饰的内容,保证了在同一时刻,只能有一个线程访问此内容,从而实现了线程安全。
* 原理:当多个线程访问时,每个线程会尝试获得锁,如果拿到锁则执行该内容,执行后释放锁,其它线程继续竞争锁...
*/
@Override
public void run() {
tickets--;
System.out.println(Thread.currentThread().getName()+":"+tickets);
}
public static void main(String[] args){
UseSynchronize1 myThread = new UseSynchronize1();
Thread t1 = new Thread(myThread,"t1");
Thread t2 = new Thread(myThread,"t2");
Thread t3 = new Thread(myThread,"t3");
Thread t4 = new Thread(myThread,"t4");
Thread t5 = new Thread(myThread,"t5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
在没有使用synchronized时,打印结果每次都不一样,是因为线程执行先后顺序是有CPU随机分配的,所以结果可能没有按票的顺序输出:
t1:98
t3:97
t2:98
t4:96
t5:95
在run方法上增加synchronized修饰,来保证正确结果输出:
@Override
public synchronized void run() {
tickets--;
System.out.println(Thread.currentThread().getName()+":"+tickets);
}
这是看一下结果,5个线程来模拟购票,每次剩余票量递减,应该输出为,99、98、97、96、95。
如下输出:
t1:99
t3:98
t2:97
t4:96
t5:95
无论运行多少次,无论线程执行顺序的变化是怎么样的,都会以正确的票量结果输出,保证了线程安全。
多个对象多个锁
被synchronized所修饰的内容,虽然保证了线程安全,但是需要在同一个对象上才可以,如果实例化多个对象,那么synchronized的锁在多个对象之间将不起作用。
也就是说,被synchronized修饰的方法,每个实例化的对象都独有一把锁。
看一个例子,一个java类方法,根据到达目的地,设置票价,这里个方法使用synchronized所修饰,那么执行2个线程,分别调用此方法,理论上应该打印目的地1,目的地1的票价,然后打印目的地2,目的地2的票价。
用例代码:
package cn.zealon.lock;
/**
* 多个对象多个锁的特性
*/
public class UseSynchronize2 {
//票价
private static int ticketPrice = 0;
/**
* 根据到达目的地,设置票价
* @param place
* 目的地
*/
public synchronized void arrivePlace(String place){
if(place.equals("拉萨")){
ticketPrice = 500;
System.out.println("place:"+place+",set ticketPrice over!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if(place.equals("漠河")){
ticketPrice = 350;
System.out.println("place:漠河,set ticketPrice over!");
}
System.out.println("place:"+place+",ticketPrice:"+ticketPrice);
}
public static void main(String[] args){
UseSynchronize2 m1 = new UseSynchronize2();
UseSynchronize2 m2 = new UseSynchronize2();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
m1.arrivePlace("拉萨");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
m2.arrivePlace("漠河");
}
});
t1.start();
t2.start();
}
}
执行结果:
place:拉萨,set ticketPrice over!
place:漠河,set ticketPrice over!
place:漠河,ticketPrice:350
place:拉萨,ticketPrice:350
发现结果并没有按照预期的显示,说明synchronized并没有起到锁的作用,因为每个对象都有自己的锁,所以才会有此问题。
把锁定义在类级别
那么如果避免这种问题呢?我们可以在方法上,增加 static 修饰,使synchronized的锁定义在class级上,保证了并发访问时,只能有一个线程能访问。
用例代码:
package cn.zealon.lock;
/**
* 多个对象多个锁的特性
*/
public class UseSynchronize2 {
//票价
private static int ticketPrice = 0;
/**
* 根据到达目的地,设置票价
* @param place
* 目的地
*/
public static synchronized void arrivePlace(String place){
if(place.equals("拉萨")){
ticketPrice = 500;
System.out.println("place:"+place+",set ticketPrice over!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if(place.equals("漠河")){
ticketPrice = 350;
System.out.println("place:漠河,set ticketPrice over!");
}
System.out.println("place:"+place+",ticketPrice:"+ticketPrice);
}
public static void main(String[] args){
UseSynchronize2 m1 = new UseSynchronize2();
UseSynchronize2 m2 = new UseSynchronize2();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
m1.arrivePlace("拉萨");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
m2.arrivePlace("漠河");
}
});
t1.start();
t2.start();
}
}
好了,这时看下运行结果:
place:拉萨,set ticketPrice over!
place:拉萨,ticketPrice:500
place:漠河,set ticketPrice over!
place:漠河,ticketPrice:350
已经起到锁的作用了。
作者: Zealon
崇尚简单,一切简单自然的事物都是美好的。