赞
踩
synchronized是Java中的关键字,是一种同步锁。
synchronized保证同一时刻有且只有一条线程在操作临界资源,其他线程必须等待该线程处理结束后再对共享数据进行操作。此时便产生了互斥锁,互斥锁特性如下:
synchronized关键字保证同一时刻最多只有1个线程执行被synchronized修饰的方法/代码,其他线程必须等待当前线程执行完该方法/代码块后才能执行该方法/代码块。
{}
之间;作用的对象是调用这个代码块的对象。synchronized (this){
System.out.println("同步代码块 ");
}
{}
之间;作用的对象是这个类的所有对象。class Ticket {
public void sale() {
synchronized (Ticket.class) {
// 操作临界资源
}
}
}
public synchronized void sale() {
// ......
}
public static synchronized void test(){
// ......
}
public class SynchronizedDemo { public static void main(String[] args) { Ticket ticket = new Ticket(); new Thread(() -> { for (int i = 0; i < 40; i++) { ticket.sale(); } }, "A").start(); new Thread(() -> { for (int i = 0; i < 40; i++) { ticket.sale(); } }, "B").start(); new Thread(() -> { for (int i = 0; i < 40; i++) { ticket.sale(); } }, "C").start(); } } class Ticket { // 票数 private int number = 30; // 操作方法:卖票 public synchronized void sale() { // 判断是否有余票 if (number > 0) { System.out.println(Thread.currentThread().getName() + " : " + (number--) + " " + number); } } }
获取锁的线程释放锁的情况:
synchronized的同步效率很低,如果某个代码块被其修饰,当一线程进入synchronized修饰的代码块,那么其余线程只能一直等待,等待持有锁的线程释放锁,才能进入同步代码块。
如果持有锁的线程由于要等待IO或其他原因(如调用sleep方法),被阻塞了,但是没有释放锁,其他线程就只能等待,非常影响程序性能。因此需要一种机制可以不让等待的线程一直无期限的等待下去(如只等待一定时间,或能够响应中断),通过Lock可以解决。如lock可以判断线程是否成功获取到锁,而synchronized无法做到。
public interface Lock { // 获得锁 // 如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到获取锁 // lock()方法不能被中断,一旦陷入死锁,lock()会进入无限等待 void lock(); // 除非当前线程被中断,否则获取锁 // 如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到出现以下两种情况之一: // 1.锁被当前线程获取 // 2.其他线程中断当前线程,支持中断获取锁 // 和lock()方法不同的是在锁的获取中可以中断当前线程 // 如果当前线程在进入此方法时已设置其中断状态 // 那么获取锁时被中断,并且支持获取锁的中断,然后抛出InterruptedException并清除当前线程的中断状态 void lockInterruptibly() throws InterruptedException; // 非阻塞获取锁(如果有),并立即返回true;如果锁不可用,则立即返回false // 该方法会立即返回,即使拿不到锁的时候也不会一直在那等待 // 我们可以根据是否能获取到锁来决定后续程序的行为 boolean tryLock(); // 如果线程在给定的等待时间内获取到锁,且当前线程未中断,则获取锁 // 如果锁可用,则此方法立即返回true // 如果不可用,则出于线程调度目的,当前线程将被挂起,处于休眠状态,直到发生以下3种情况之一: // 1.锁被当前线程获取 // 2.其他线程中断当前线程,支持中断获取锁 // 3.经过指定的等待时间如果获得了锁,则返回true // 如果经过了指定的等待时间,未获得锁,则返回false。如果时间小于等于0,则该方法不需等待 boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 解锁 void unlock(); // 返回绑定到此Lock实例的新Condition实例 Condition newCondition(); }
如果使用了lock,必须主动释放锁,就算发生了异常,也需要手动释放,因为lock不会像synchronized一样自动释放锁。所以使用lock,必须在try{}catch(){}
中进行,并在finally{}
中释放锁,防止死锁。
Lock lock = new ReentrantLock();
try {
lock.lock();
System.out.println("上锁了");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
System.out.println("解锁了");
}
关键字synchronized与wait()/notify()一起使用可以实现等待/通知。Lock锁的newCondition()方法返回Condition对象,Condition类也可以实现等待/通知。使用notify()时,JVM会随机唤醒某个等待的线程,使用Condition类可以选择性通知,Condition常用的两个方法:
注意:
在调用Condition的await()/signal()方法前,也需要线程持有相关的Lock锁,调用await()后线程会释放这个锁,在调用signal()后,会从当前Condition对象的等待队列中,唤醒一个线程,被唤醒的线程开始尝试获取锁,一旦成功获得锁就继续往下执行。
例子:
有两个线程,一个初始值是0的number变量,一个线程当number == 0时,对number值+1,另外一个线程当number == 1时,对number-1:
public class LockDemo { public static void main(String[] args) { Share share = new Share(); new Thread(()->{ for (int i=0;i<=10;i++){ share.incr(); } },"AA").start(); new Thread(()->{ for (int i=0;i<=10;i++){ share.decr(); } },"BB").start(); /** * AA::1 * BB::0 * AA::1 * BB::0 * ..... */ } } class Share { private Integer number = 0; private ReentrantLock lock = new ReentrantLock(); private Condition newCondition = lock.newCondition(); // +1 的方法 public void incr() { try { // 加锁 lock.lock(); while (number != 0) { // 沉睡 newCondition.await(); } number++; System.out.println(Thread.currentThread().getName() + "::" + number); // 唤醒另一个沉睡的线程 newCondition.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } // -1 的方法 public void decr() { try { lock.lock(); while (number != 1) { newCondition.await(); } number--; System.out.println(Thread.currentThread().getName() + "::" + number); newCondition.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
ReentrantLock是唯一实现了Lock接口的类,且提供了更多的方法。
可重入锁:某个线程已经获得某个锁,可以再次获取锁而不会死锁。
public class ReentrantLockDemo { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); new Thread(new Runnable() { @Override public void run() { try { lock.lock(); System.out.println("第1次获取锁,这个锁是:" + lock); for (int i = 2;i<=11;i++){ try { lock.lock(); System.out.println("第" + i + "次获取锁,这个锁是:" + lock); try { Thread.sleep(new Random().nextInt(200)); } catch (InterruptedException e) { e.printStackTrace(); } } finally { // 注意:一定要释放锁。如果把这里注释掉的话,那么程序就会陷入死锁当中 lock.unlock(); } } } finally { lock.unlock(); } } }).start(); } } /** * 第1次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@6b5fde1f[Locked by thread Thread-0] * 第2次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@6b5fde1f[Locked by thread Thread-0] * 第3次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@6b5fde1f[Locked by thread Thread-0] * ... */
public interface ReadWriteLock {
// 获取读锁
Lock readLock();
// 获取写锁
Lock writeLock();
}
读写分离,可以有多个线程进行读操作,提高效率。
ReentrantReadWriteLock实现了ReadWriteLock接口。提供了更丰富的方法,最重要的还是获取读锁和写锁。
案例——多个线程进行读操作
// synchronized加锁 public class SynchronizedDemo { public static void main(String[] args) { final SynchronizedDemo test = new SynchronizedDemo(); new Thread(()->{ test.get(Thread.currentThread()); }).start(); new Thread(()->{ test.get(Thread.currentThread()); }).start(); } public synchronized void get(Thread thread) { long start = System.currentTimeMillis(); while(System.currentTimeMillis() - start <= 1) { System.out.println(thread.getName()+"正在进行读操作"); } System.out.println(thread.getName()+"读操作完毕"); } } /* 结果: Thread-0正在进行读操作 Thread-0读操作完毕 Thread-1正在进行读操作 Thread-1正在进行读操作 ...... ...... Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-1读操作完毕 */
// 读锁 public class ReentrantReadWriteLockDemo { private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public static void main(String[] args) { final ReentrantReadWriteLockDemo test = new ReentrantReadWriteLockDemo(); new Thread(()->{ test.get2(Thread.currentThread()); }).start(); new Thread(()->{ test.get2(Thread.currentThread()); }).start(); } public void get2(Thread thread) { rwl.readLock().lock(); try { long start = System.currentTimeMillis(); while(System.currentTimeMillis() - start <= 1) { System.out.println(thread.getName()+"正在进行读操作"); } System.out.println(thread.getName()+"读操作完毕"); } finally { rwl.readLock().unlock(); } } } /* 结果: Thread-0正在进行读操作 Thread-0正在进行读操作 Thread-0正在进行读操作 ...... Thread-0正在进行读操作 Thread-1正在进行读操作 ...... Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-0正在进行读操作 ...... Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-0读操作完毕 Thread-1读操作完毕 */
结论:
使用读锁,线程1和线程2可以同时读,提高了效率。
注意:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。