synchronized
把代码块声明为 synchronized,有两个重要后果,通常是指该代码具有 原子性和 可见性。原子性意味着一个线程一次只能执行由一把锁保护的代码,从而防止多个线程在更新共享状态时相互冲突。原子操作表示不可被中断的一个或一系列操作。
java 中的每一个对象都可以作为锁。具体表现为一下形式:
1、对于普通同步方法,锁是当前实例对象。
2、对于静态同步方法,锁是当前类的class对象。
3、对于同步方法块,所示synchronized括号里配置的对象。
当一个线程访问同步代码块时,他必须首先得到锁,退出或抛出异常时必须释放锁。
Lock 和synchronized
lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!
public class T{
private int count = 10;
public synchronized void m() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
T t = new T();
for(int i=0; i<5; i++) {
new Thread(t::m, "THREAD" + i).start();
}
}
}
同步与非同步方法是可以同时调用的,两个同步方法也是可以同时调用的,但是后一个必须等待前一个执行完成才能拿到第二把锁
public class T {
public synchronized void m1() {
System.out.println(Thread.currentThread().getName() + " m1 start...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m1 end");
}
public void m2() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m2 ");
}
public static void main(String[] args) {
T t = new T();
new Thread(t::m1, "t1").start();
new Thread(t::m2, "t2").start();
}
}
synchronized获得的锁是可重入的,也就是说,一个同步方法可以调用另外一个同步方法,因为一个线程已经获取对象锁,再次申请的时候还是可以获取到该对象锁
ReentrantLock
多线程都在争用同一个锁时,使用 ReentrantLock 的总体开支通常要比 synchronized 少得多。
用 ReentrantLock 保护代码块时,一定要使用finally块关闭锁。
public class ReentrantLock2 {
Lock lock = new ReentrantLock();
private void m1() {
try {
lock.lock(); //相当于synchronized(this)
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println(i);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void m2() {
lock.lock();
System.out.println("m2 ...");
lock.unlock();
}
public static void main(String[] args) {
ReentrantLock2 rl = new ReentrantLock2();
new Thread(rl::m1).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(rl::m2).start();
}
}
以上m1()执行完毕后才会执行m2()。m1()必须手动释放锁,否则m2()永远不会执行。ReentrantLock也可以指定为公平锁,线程调度器计算哪个线程等待的时间比较长,会让它先执行,所以不是绝对公平:
public class ReenLock {
static Lock lock = new ReentrantLock(true);
private void m3() {
for (int i = 0; i < 100; i++) {
lock.lock();
System.out.println(Thread.currentThread().getName() + " 获得锁");
lock.unlock();
}
}
public static void main(String[] args) {
ReenLock l = new ReenLock();
new Thread(l::m3).start();
new Thread(l::m3).start();
}
}
volatile
java允许多线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该通过互斥单独获得这个变量。如果一个变量被声明为volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。但它只是保证了可见性,不保证原子性,所以会结合锁一起使用。
jdk1.8后synchronized已经优化,但是还是比volatile重。synchronized是一种悲观锁,一个线程访问某个资源时,它会悲观的认为其他线程也会来访问该资源,java有个乐观锁CAS(Compare And Swap),其设计思想是乐观地认为不会产生并发,所以不上锁,在取数据的时候,判断一下在此期间是否有人修改,如果没有修改,则直接使用。
public class Test {
volatile boolean running = true;
private void m() {
System.out.println(Thread.currentThread().getName() + " m start..");
while (running) {
}
System.out.println(Thread.currentThread().getName() + " m end..");
}
public static void main(String[] args) {
Test t = new Test();
new Thread(() -> t.m(), "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.running = false;
}
}
class LoanTask{
static String LOAN_STATUS = "LOAN_STATUS";
}
class BorrowTask{
public Object getLock() {
return LoanTask.LOAN_STATUS;
}
Object lock = getLock();
synchronized (lock) {
lock.notifyAll();//必须先跟synchronized配合使用
}
}
参考示例程序:lock与unlock
以下仅是做记录
ConcurrentUtil.register(user);
public void offer(T model) {
if (!queue.contains(model)) {
queue.offer(model);
try {
System.out.println("------------11111111--------");
LOCK.lock();
task.execute();
System.out.println("------------33333333--------");
}finally{
LOCK.unlock();
System.out.println("-----44444444444-------");
}
}
}
再研究下task.execute()这个方法。