解决方法
synchronized方式
这种方式有两种用法,一种是同步代码块,一种是同步方法
同步代码块
这种用法主要考虑的是同步监视器的定义以及共享数据的处理,在同步代码块中需要显式地声明同步监视器
sychronized(同步监视器) {
//同步代码块
}
1>此中多个线程共同操作的变量称为共享数据;
2>同步代码块:共享数据的代码,即为需要被同步的代码,对同步代码块而言,所需要考虑的是共享数据的存在、如果存在又有哪些共享数据,以及操作这些共享数据的代码块有哪些,这里要求尽可能地少包含代码,也就是让共享区域尽可能少;
3>同步监视器:也可以称为锁,只要满足多个线程共用同一把锁,任何一个对象都可以作为同步监视器;
创建线程有两种方式:实现Runnable接口、继承Thread类
在实现Runnable接口创建多线程的方式中,通常可以考虑用this(也就是当前类的对象)来充当同步监视器;
在继承Thread类创建多线程的方式中,可以考虑用当前类(也就是类名.class)来充当同步监视器,一般不使用this,因为对多线程来说,每一个线程都有Thread类的子类对象,不能确保多个线程共用一把锁的条件,用类来充当同步监视器时,类被类加载器加载,并且只加载一次,保证了当前类是唯一的。
同步方法
首先同步方法与同步代码块考虑的同步监视器以及共享数据的思路是一致的;
当操作共享数据的代码完整的声明在一个方法里,这时候可以考虑将此方法声明为同步的;
同步方法依然是涉及到同步监视器的,与同步代码块不同的是不需要显示地声明
public synchronized void dosome() {
}
public static synchronized void dosome() {
}
而对同步方法来说,又分为非静态的同步方法和静态的同步方法
对非静态的同步方法来说,同步监视器是this,因为在非静态方法中当前类的对象是唯一的;
对静态的同步方法来说,同步监视器是当前类本身。
Lock锁(Java 5.0新增)
Lock loc = new ReentrantLock();
loc.lock();
try {
// access the resource protected by this lock
} finally {
loc.unlock();
}}
以上是Lock锁的基础用法,ReentrantLock类实现了java.util.concurrent.locks.Lock接口,这个接口是控制多个线程对共享区域访问的一个工具,这种方式与synchronized的使用环境相同,都是基于Runnable接口的实现或是Thread类的继承。
使用Lock锁同样需要保证多个线程共用一把锁,也就是同一个对象,而以上代码中的loc就是共用的那同一个对象。
synchronized 与 Lock的异同
同
都可以解决线程安全的问题。
异
synchronized在执行相应的同步代码之后,会自动释放同步监视器;
Lock需要手动的启动和关闭。
解决线程安全问题的方法的利弊
利
synchronized和Lock都解决了线程安全的问题。
弊
每次进入共享区域操作共享数据的线程只能有一个,其他的线程均处于等待的状态,也就相当于是执行一个单线程的方式,效率低。
总结
对线程安全的解决实际上就是考虑对共享区域解决,除了synchronized和Lock这种加锁的方式以外,可以考虑怎样去消除共享区域,这才是最优解。