条件锁
- 锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码。
- 锁可以管理试图进入被保护代码段的线程
- 锁可以拥有一个或多个相关的条件对象
- 每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程。
class Bank {
private final double[] accounts;
private Lock bankLock; //锁
private Condition sufficientFunds; //该锁的条件对象
public Bank(int n, double initialBalance) {
accounts = new double[n];
Arrays.fill(accounts, initialBalance);
bankLock = new ReentrantLock();
sufficientFunds = bankLock.newCondition();
}
//如果该线程资金不足,await()就会将该线程放到条件的等待集中变为阻塞状态等待其它线程激活它,当其
//它线程资金转账成功时,就会调用signalAll()解除条件的等待集中所有线程的阻塞状态,如果该线程抢到
//cpu时间片那么就会执行,检查余额是否足够转账,如果可以继续往下执行,如果不够又会被放到等待集中。
public void transfer(int from, int to, double amount) throws InterruptedException {
bankLock.lock(); //获取这个锁
try {
while (accounts[from] < amount) //如果余额不足
sufficientFunds.await(); //阻塞该线程
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
sufficientFunds.signalAll(); //激活所有阻塞线程
} finally {
bankLock.unlock(); //释放这个锁
}
}
public double getTotalBalance() {
bankLock.lock();
try {
double sum = 0;
for (double a : accounts)
sum += a;
return sum;
} finally {
bankLock.unlock();
}
}
public int size() {
return accounts.length;
}
}
class UnsynchBankTest {
public static final int NACCOUNTS = 100;
public static final double INITIAL_BALANCE = 1000;
public static final double MAX_AMOUNT = 1000;
public static final int DELAY = 10;
public static void main(String[] args) {
Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE);
for (int i = 0; i < NACCOUNTS; i++) {
int fromAccount = i;
Runnable r = () -> {
try {
while (true) {
int toAccount = (int) (bank.size() * Math.random());
double amount = MAX_AMOUNT * Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int) (DELAY * Math.random()));
}
} catch (Exception e) {
}
};
Thread t = new Thread(r);
t.start();
}
}
}
synchronized
如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。也就是说,要调用该方法,线程必须获得内部的对象锁。
wait或notifayAll等价于Condition接口的await和sigalAll方法。
wait,notifyAll以及notify是Object类的fianl方法。所以Condition的方法必须被命名为await,signalAll和signal以便它们不会与那些方法发生冲突。
每一个对象有一个内部锁,并且该锁有一个内部条件。由锁来管理那些试图进入synchronized方法的线程,由条件来管理那些调用wait的线程。
将静态方法声明为synchronized也是合法的。如果调用这种方法,该方法获得相关的类对象的内部锁。例如Bank类有一个静态同步方法,那么当该方法被调用时,Bank.class对象的锁被锁住。因此,没有其他线程可以调用同一个类的这个或任何其他的同步静态方法。
内部锁和条件存在一些局限性。包括
class Bank {
private final double[] accounts;
public Bank(int n, double initialBalance) {
accounts = new double[n];
Arrays.fill(accounts, initialBalance);
}
public synchronized void transfer(int from, int to, double amount) throws InterruptedException {
if (accounts[from] < amount)
wait();
System.out.println(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
notifyAll();
}
private synchronized double getTotalBalance() {
double sum = 0;
for (double a : accounts)
sum += a;
return sum;
}
public int size() {
return accounts.length;
}
}
class UnsynchBankTest {
public static final int NACCOUNTS = 100;
public static final double INITIAL_BALANCE = 1000;
public static final double MAX_AMOUNT = 1000;
public static final int DELAY = 10;
public static void main(String[] args) {
Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE);
for (int i = 0; i < NACCOUNTS; i++) {
int fromAccount = i;
Runnable r = () -> {
try {
while (true) {
int toAccount = (int) (bank.size() * Math.random());
double amount = MAX_AMOUNT * Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int) (DELAY * Math.random()));
}
} catch (Exception e) {
}
};
Thread t = new Thread(r);
t.start();
}
}
}
同步代码块
class SynchronizedBlockThread implements Runnable {
private Object obj = new Object();
private int ticketCount = 100;
@Override
public void run() {
while (ticketCount > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
sale();
}
}
private void sale() {
synchronized (obj) { //this和SynchronizedBlockThread.class都行
if (ticketCount > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (100 - ticketCount + 1) + "张票");
ticketCount--;
}
}
}
public static void main(String[] args) {
SynchronizedBlockThread blockThread = new SynchronizedBlockThread();
Thread t1 = new Thread(blockThread, "窗口1--");
Thread t2 = new Thread(blockThread, "窗口2--");
t1.start();
t2.start();
}
}
显式锁ReentrantLock使用详解之测试锁与超时
如果当前获得锁的线程在做大量耗时的工作,使用lock.lock()方法申请锁的线程会一直阻塞,这样就降低了多线程的效率。而使用tryLock()方法申请锁,如果锁不可用则线程不会阻塞,转而可以去做其他工作。代码实例如下:
public class ReentrantLockTest {
private ReentrantLock lock = new ReentrantLock();
public void tryLockMethod() {
long beginTime = System.currentTimeMillis();
while (System.currentTimeMillis() - beginTime < 100) {
}
try {
if (lock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + " tryLock get lock");
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + " tryLock release lock");
}
} else {
System.out.println(Thread.currentThread().getName() + " tryLock can not get lock");
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void lockTest() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " lock get lock");
long beginTime = System.currentTimeMillis();
while (System.currentTimeMillis() - beginTime < 1000) {}
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + " lock release lock");
}
}
public static void main(String[] args) {
final ReentrantLockTest test = new ReentrantLockTest();
Thread tryLock = new Thread(new Runnable() {
@Override
public void run() {
try {
test.tryLockMethod();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "tryLock_thread");
Thread lock = new Thread(new Runnable() {
@Override
public void run() {
test.lockTest();
}
}, "lock_thread");
tryLock.start();
lock.start();
}
}
/*
结果
lock_thread lock get lock
tryLock_thread tryLock can not get lock //tryLock_thread 获取锁失败去做其他的事情
lock_thread lock release lock
*/
读写锁
java.util.concurrent中有很多的同步工具类,比如ReentrantLock、Semaphore、CountLatch、CyclicBarrier、BlockingQueue、ConcurrentLinkedQueue等等,其中,很多使用的是排他锁的实现,即,同一时间只有一个线程能够访问共享的变量或临界区。因此,在某些场景下,大部分的同步工具类的性能都不尽人意。想想一下这种场景,比如缓存,缓存存在的意义就是减轻数据库的压力,在一段时间内可能只有很少次数的更新缓存,并且会有大量的读取缓存的次数。为了保证缓存与数据库中的一致性,避免脏数据,我们可以在缓存更新时进行加锁,但是,如果该锁是排它锁的话,等于把所有对该缓存的查询和更新操作都串行化了,效率太低,这个时候,ReentrantReadWriteLock就派上用场了。ReentrantReadWriteLock的锁在同一时刻只会有一个线程进行写入操作,而在同一时刻有多个读线程进行读操作,相对于ReentrantLock来说,大大提高了读取的效率。其实只要保证在更新的同时,没有其他线程正在读取就可以了,至于如果没有线程正在执行更新操作,那么不管有多少个线程在读取,都不会造成数据变化,那么读的时候就让那些线程一起读好了。
public class ReadAndWriteTest {
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); //读写锁
Map<String,String> map = new HashMap(); //共享数据
public ReadAndWriteTest() {
map.put("data", "1");
}
public void read() {
rwLock.readLock().lock(); //上读锁
System.out.println(Thread.currentThread().getName()+"读数据为:"+map.get("data") +" 获取锁的时间为: "+
System.currentTimeMillis());
try {
Thread.sleep(100);
}catch (Exception e) {
e.printStackTrace();
}finally {
rwLock.readLock().unlock(); //释放写锁
}
}
public void write() {
rwLock.writeLock().lock(); //上写锁
int data = (int) (Math.random()*100)+1;
map.put("data", data+"");
System.out.println(Thread.currentThread().getName()+"写数据为:"+map.get("data") +" 获取锁的时间为: "+
System.currentTimeMillis());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwLock.writeLock().unlock(); //释放写锁
}
}
}
public class ReadWriteLockTest {
public static void main(String[] args) {
final ReadAndWriteTest raw = new ReadAndWriteTest();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
raw.read();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
raw.read();
}
}
}).start();
}
}
Callable和Future
Callable和Future,它俩很有意思的,一个产生结果,一个拿到结果。
Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到Callable异步执行任务的返回值。
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
method1();
method2();
method3();
method4();
}
/*
* Runnable是个接口,使用很简单: 1. 实现该接口并重写run方法 2. 利用该类的对象创建线程 3. 线程启
* 动时就会自动调用该对象的run方法通常在开发中结合ExecutorService使用,将任务的提交与任务的执行
* 解耦开, 同时也能更好地利用Executor提供的各种特性相对于继承Thread来创建线程方式,使用Runnable
* 可以让你的实现类同时实现多个接口,而相对于Callable及Future,Runnable方法并不返回任务执行 结果
* 且不能抛出异常
*/
public static void method1() {
ExecutorService pool = Executors.newCachedThreadPool();
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("method");
}
});
pool.shutdown();
}
/*
* 与Runnable不同的是,Callable是个泛型参数化接口,并能返回线程的执行结果,且能在无法正常计算时
* 抛出异常allable并不像Runnable那样通过Thread的start方法就能启动实现类的run方法,所以它通常利
* 用ExecutorService的submit方法去启动call方法自执行任务,而ExecutorService的submit又返回一个
* Future类型的结果,因此Callable通常也与Future一起使用
*/
public static void method2() throws InterruptedException, ExecutionException {
ExecutorService pool = Executors.newCachedThreadPool();
Future<String> future = pool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "method2";
}
});
System.out.println(future.get());
pool.shutdown();
}
/*
* 利用FutureTask封装Callable再由Thread去启动(少用)
*/
public static void method3() throws InterruptedException, ExecutionException {
FutureTask<String> task = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
return "method3";
}
});
Thread thread = new Thread(task);
thread.start();
System.out.println(task.get());
}
public static void method4() throws InterruptedException, ExecutionException {
String result = "result";
FutureTask<String> task = new FutureTask<>(new Runnable() {
@Override
public void run() {
System.out.print("method4");
}
}, result);
Thread thread = new Thread(task);
thread.start();
System.out.println(" " + task.get());
}
}
//执行结果:
//method
//method2
//method3
//method4 result
一个扫描文件是否包含关键字的实例
public class FutureTest {
public static void main(String[] args) {
try(Scanner in = new Scanner(System.in)){
System.out.print("请输入文件路径: ");
String directory = in.nextLine();
System.out.print("请输入文件包含的关键字: ");
String keyword = in.nextLine();
MatchCounter counter = new MatchCounter(new File(directory),keyword);
FutureTask<Integer> task = new FutureTask<>(counter);
Thread t = new Thread(task);
t.start();
try {
System.out.println(task.get()+"个文件包含关键字");
}catch (ExecutionException e) {
e.printStackTrace();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MatchCounter implements Callable<Integer> {
private File directory;
private String keyword;
public MatchCounter(File directory, String keyword) {
this.directory = directory;
this.keyword = keyword;
}
@Override
public Integer call() throws Exception {
int count = 0;
try {
File[] files = directory.listFiles();
// list存储了每一个
List<Future<Integer>> results = new ArrayList<>();
for (File file : files) {
if (file.isDirectory()) { // 如果是文件夹就递归调用获取
MatchCounter counter = new MatchCounter(file, keyword);
FutureTask<Integer> task = new FutureTask<>(counter);
results.add(task); // 用list存储FutureTask
Thread t = new Thread(task);
t.start();
} else {
if (search(file))
count++; // 如果是文件就读取文件
}
}
// Future保存了异步计算的结果 把list里面的所有的结果加起来
for (Future<Integer> result : results) {
try {
count += result.get();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
}
return count;
}
public boolean search(File file) {
try {
try (Scanner in = new Scanner(file, "UTF-8")) {
boolean found = false;
while (!found && in.hasNext()) {
String line = in.nextLine();
if (line.contains(keyword))
found = true;
}
return found;
}
} catch (IOException e) {
return false;
}
}
}