接着上一篇,梳理java线程资源冲突的问题,也是我们编程中遇到的,在什么时候考虑资源冲突,该如何解决呢?
当我们处理数据,会有多个线程或多出访问修改数据是,就需要考虑资源共享的问题 。(Brian同步规则:如果我们写的一个变量,他可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,必须使用同步。
)
解决资源冲突的问题就是采用序列化访问共享资源的方案
共享资源:一般以对象的形式存储在内存片段,但也可以是文件,输入/输出端口,要控制对共享资源的方法,一般是把资源封装在一个对象里,然后对其使用Synchronized等关键字标记。
当任务执行时,他将检查锁是否可用,然后获取锁,执行代码,释放锁
主要方法:
1,Synchronized关键字:
1),每个对象都自动含有单一的锁:当在对象上调用任意的synchronized方法时,此对象就会被加上锁,此时该对象的其他Synchronized方法必须等到前一个方法释放锁,才能调用
2)对象里的所有Synchronized方法共享同一个锁,防止对个任务同时被编码为内存对象
3)使用Synchronized时,域必须是private的,否者没有作用
4)在一个synchronized方法的内部调用synchronized方法,锁会递加,释放锁递减,直到标志为零时,完全释放锁。
5)在一个对象内,有多个方法对某个数据进行访问和修改,必须都加锁,否者其他方法将忽略锁,可以在任何情况下对该数据进行读写。
2.Lock关键字
使用:必须被显示的创建,锁定和释放。一般在开始加锁,try语句里进行相关的操作,finally里释放锁。且return语句必须在try里,确保unlock过早发生,造成数据的暴露。
代码:
package resouceHandler;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by acer on 2016/2/28.
*/
public class LockEventGenetor extends IntGenetor {
private int countValue = 0;
private Lock lock = new ReentrantLock();
@Override
public int next() {
lock.lock();
try{
++countValue;
Thread.yield();
++countValue;
return countValue;
} finally {
lock.unlock();
}
}
}
区别:lock可以解决synchronized关键字的所有问题,但lock更加灵活,可以自己控制什么时候加锁和释放锁。但代码比较多,使用比较复杂。同时当在同步的过程中抛出异常,synchronized无法释放锁,无法进行处理工作,但lock可以释放,后续的处理。
Synchronized使用简单,出错的机会小。
一般在特殊问题才会使用lock。例如:synchronized无法获取锁,或获取一段时间后要放弃锁。
3,volatile关键字
确保了应用中的可视性:如果一个域声明为volatile,对它的读写操作,其他读操作都会看到,即使使用了本地缓存。因为被声明volatile的域,会立即写入到主存中。后面的文章会介绍线程的内存模型。
使用规则:
1)如果一个域完全被synchronized方法或synchronized语句块锁定,无须使用volatile。
2)如果一个域的值依赖与他的上一个值或受制于其他域,volatile无法工作。例如Range的lowe和upper
3)线程或任务的任何读写操作对该任务都是可视的,都在工作内存里,如果只须在任务内部可视,不须使用volatile关键字
4)当类中只有一个可变的域时,使用volatile。
如果一个域需要被多个任务同时访问,或至少一个任务有写的操作,就要考虑使用volatile关键字
4,Synchronized的另一种用法:同步块
比同步方法性能更高。我们只是需要锁定某一个对象,或某一块而不是整个方法,此时可以使用同步块
1)自身同步
2)其他对象上同步
5,线程本地存储
根除共享资源上产生冲突的第二种方式是根除对变量的共享,相同的变量对不同的线程都创建不同的存储。即如果有五个线程都对x进行读写操作,那么线程本地存储就要生产五个用于x的存储块。
ThreadLocal通常当做静态域的存储,创建ThreadLocal时,只能通过get和set方法得到域,其中get方法返回与其线程相关的对象的副本,set方法会将参数插入到为其线程存储的对象中,并返回存储中原有的对象
代码:
6原子类:原子类为底层实现,因此只在特殊的情况下才使用,即便使用了也要确保不存在其他问题,通常依赖synchronized更安全些
常见的原子类:AutomInteger AutomLog AutomReference
下面具体演示实例的简单代码:
package resouceHandler;
/**
* Created by acer on 2016/2/28.
*/
public abstract class IntGenetor {
private volatile boolean cancel = false;
public abstract int next();
public void cancel () {
cancel = true;
}
public boolean isCancel() {
return cancel;
}
}
package resouceHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**整数检测器的具体是西安
* Created by acer on 2016/2/28.
*/
public class EventGenetor extends IntGenetor {
private int currentValut = 0;
private int count = 5;
//演示冲突问题方法
// @Override
// public int next() {
//
如果不存在资源冲突,检测器就不会打印相关语句,一直都是偶数
// ++currentValut;
// ++currentValut;
// return currentValut;
//
// }
// 使用锁
@Override
public synchronized int next() {
// 如果不存在资源冲突,检测器就不会打印相关语句,一直都是偶数
++currentValut;
++currentValut;
return currentValut;
}
}
package resouceHandler;
import Util.PrintUtil;
/**
* Created by acer on 2016/2/28.
*/
public class EventChecker implements Runnable {
private IntGenetor intGenetor;
private final int id;
public EventChecker(IntGenetor intGenetor, int id) {
this.intGenetor = intGenetor;
this.id = id;
}
@Override
public void run() {
while (!intGenetor.isCancel()) {
int value = intGenetor.next();
//如果不存在资源冲突,检测器就不会打印相关语句,一直都是偶数
if (value % 2 != 0) {
PrintUtil.printb(value + " not even");
intGenetor.cancel();
}
}
}
}
package resouceHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by acer on 2016/2/28.
*/
public class TestResouce {
public static void main(String args[]) {
test(new LockEventGenetor());
}
public static void test(IntGenetor genetor,int count) {
ExecutorService serv = Executors.newCachedThreadPool();
for (int i=0; i< count; i++) {
serv.execute(new EventChecker(genetor,i));
}
serv.shutdown();
}
public static void test(IntGenetor genetor) {
test(genetor,2);
}
}
写完第三篇以后,我会把代码的github地址贴出,大家感兴趣可以看一下,环境大家code完善。