java实现线程安全的3种方式
什么时候使用同步
运用在书里面看到的一种说法:如果你正在写一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么你必须使用同步,并且,读写线程都必须用相同的监视器锁同步。
在我们的代码中常常会出现临界资源,如果在类中有超过一个方法在处理临界数据,那么你必须同步所有相关的方法。如果只同步一个方法,那么这个数据就变得不安全了,这值得我们重视:每个访问临界共享资源的方法都必须被同步,否则他们就不会正确地工作。
1.同步代码块
同步代码块使用的是
synchronized(锁对象){
}
这样一种结构,这种方式通过更改输入的锁对象的某个值,来确定当前哪一个线程执行,需要注意的是任何对象都可以成为锁对象。使用同一个锁对象的线程,会根据锁对象的值执行。
下面是一段示例代码:
public static void main(String[] args) {
//线程不安全
//解决办法1 同步代码块
//格式:synchronized(锁对象){}
//任何对象都可以是锁对象
Runnable run = new Ticket2();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket2 implements Runnable{
//总票数
private int count = 10;
private Object o = new Object();
@Override
public void run() {
while(true){
synchronized(o){
if (count > 0){
//卖票
System.out.println("正在卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
}else{
break;
}
}
}
}
}
2.同步方法
通过为需要排队执行的方法加入synchronized修饰符实现线程安全。这种方式的锁对象为synchronized修饰的对象,调用时也就是this,或者在静态修饰的变量中就是 xxxx.class。
如果我们通过同一个对象多次调用synchronized修饰的方法,那么他是线程安全的。
如果通过同一个类的多个对象多次调用synchronized修饰的方法,他就不安全了。
下面是一段示例代码:
public static void main(String[] args) {
//线程不安全
//解决方案2
Runnable run = new Ticket3();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket3 implements Runnable{
//总票数
private int count = 10;
@Override
public void run() {
while(true){
boolean flag = sale();
if(!flag){
break;
}
}
}
//给方法添加synchronized修饰符 即可排队执行这个方法
//这种情况锁对象是 run对象 也就是方法的this 或者 Ticket3.class
//如果再创建一个synchronized代码块 锁对象为this 那么和锁方法共用一把锁
public synchronized boolean sale(){
if (count > 0){
//卖票
System.out.println("正在卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
return true;
}
return false;
}
}
3.显示锁Lock
通过创建ReentrantLock对象,再手动调用该对象的lock()与unlock()方法来实现线程的安全。
下面是一段示例
public static void main(String[] args) {
//线程不安全
//解决办法3 显示锁Lock 子类 ReentrantLock
//自己创建锁对象
Runnable run = new Ticket4();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket4 implements Runnable{
//总票数
private int count = 10;
//显示锁
private Lock l = new ReentrantLock();
@Override
public void run() {
while(true){
l.lock();
if (count > 0){
//卖票
System.out.println("正在卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
}else{
break;
}
l.unlock();
}
}
}
synchronized与显示Lock的对比
显示Lock相比较于synchronized显得更加灵活,更易于控制代码,当因为同步出现异常时,Lock会更加容易确保整个程序的继续执行,但是代码的数量会增加。