本篇主要总结同步器的相关例子:包括synchronized、volatile、原子变量类(AtomicXxx)、CountDownLatch、ReentrantLock和ThreadLocal。还涉及到wait和notify/notifyAll。


回忆关于线程的几个基本知识点:

◆ 线程的概念(程序中不同的执行路径可以放到不同的CPU中同步运行);

◆ 如何启动一个线程(继承Thread类 / 实现Runnable接口 / 实现Callable接口,,,调用start()方法);

◆ 基本的线程同步方式  (synchronized——锁定的是一个对象、...)

对于synchronized的理解:

由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。

目录

1.对某个对象加锁

♣ 例1.1:new一个对象作为锁

♣ 例1.2:锁定自身对象

♣ 例1.3:synchronized用在静态方法上

♣ 例1.4:锁住线程的run方法

♣ 例1.5:同步方法与非同步方法是否可以同时调用?—— 可以

♣ 例1.6:对业务写方法加锁,对业务读方法不加锁,容易产生脏读问题。

♣ 例1.7:一个同步方法可以调用另外一个同步方法吗——可以

♣ 例1.8:在继承中,子类重写的同步方法可以调用父类的同步方法

♣ 例1.9:出现异常,默认情况下锁会被释放,

2.volatile关键字——使一个变量在多个线程中可见

♣ 例2.1 volatile的可见性

♣ 例2.2  volatile不具备原子性

♣ 例2.3  synchronized既保证可见性又保证了原子性

♣例2.4 实现一个容器,提供两个方法add、size,线程1添加10个元素到容器,线程2监控元素的的个数,当个数为5个时,线程2提示并结束。

♣♣ 2.4.1 给容器list添加volatile

♣♣ 2.4.2  使用wait()和notify()做到,wait释放锁,而notify不会释放锁

♣♣ 2.4.3 使用CountDownLatch(门闩)的await和countdown方法替代wait、notify方法来进行通知。

3.原子变量类(AtomXxx)

♣ 例3.1  原子性操作int类型——AtomicInteger

4.ReentrantLock-重入锁

♣ 例4.1 ReentrantLock可以用来代替synchronized

♣ 例4.2  ReentrantLock可以进行尝试锁定tryLock()

♣ 例4.3  ReentrantLock可调用lockInterruptibly方法,对线程的interrupt方法作出响应,在一个线程等待的过程中,可以被打断。

♣ 例4.4 ReentrantLock可以指定为共享锁

♣ 例4.5  写一个固定容量的同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用。

♣♣  4.5.1 使用wait和notify/notifyAll()来实现

♣♣  4.5.1 使用Lock和Condition来实现

5.ThreadLocal 线程局部变量


1.对某个对象加锁

例1.1:new一个对象作为锁

/*** sychronized关键字* 对某个对象加锁*/
public class SyncronizeObject {private int count = 10;private Object o = new Object();public void m() {synchronized(o) {  //任何线程要执行下面的代码,必须要先拿到o的锁count -- ;System.out.println(Thread.currentThread().getName()+"count = " + count);}}

例1 中,这把锁是自己new的,每次new出一个毫无其他功能的对象就当锁的对象比较麻烦。所以可以用synchronized(this)。

例1.2:锁定自身对象

public class SynchronizeThis {private int count = 10;public void m1() {synchronized (this) {  //任何线程要执行下面的代码,必须先拿到this的锁count -- ;System.out.println(Thread.currentThread().getName() + "count = " + count);}}//m1()等同于下面的m2()public synchronized void m2() {  //等同于在方法的代码执行时要synchronized(this)count -- ;System.out.println(Thread.currentThread().getName() + "count = " +count);}
}

♣ 例1.3:synchronized用在静态方法上

package cn.xx55xx.concurrence;
/*类中静态方法和静态属性属性是不需要new出对象来访问的,没有new出来,就没有this引用的存在,所以当锁定一个静态方法时,相当于锁定的是当前类的class对象*/
public class SynchronizedStaticMethod {private  static int count = 10;//等同于synchronized(cn.xx55xx.concurrence.SynchronizedStaticMethod.class)public synchronized static void m1() {count --;System.out.println(Thread.currentThread().getName() + "count = " + count);}public static void m2() {synchronized (SynchronizedStaticMethod.class) { //*.class是Class中的一个对象,这里是不能用synchronized(this)的count -- ;}}}

♣ 例1.4:锁住线程的run方法

public class SynchronizedThread implements Runnable{private int count = 10;@Overridepublic synchronized void run() {count -- ;System.out.println(Thread.currentThread().getName() + " count = " + count);}public static void main(String[] args) {SynchronizedThread t = new SynchronizedThread();for (int i=0; i<5; i++) {new Thread(t,"THREAD-" + i).start();}}
}

run方法不加synchronized时,可能会出现以下这些情况(还有别的可能):

因为不加synchronized,count--和打印语句中间,有可能有别的线程来执行count--,导致前后数据不一致。加了synchronized这两条语句相当于是一个原子操作,一个run方法执行完毕释放了锁,下一个线程才能拿到锁执行run方法!

♣ 例1.5:同步方法与非同步方法是否可以同时调用?—— 可以

/*同步方法与非同步方法是可以同时调用的。只有synchronized修饰的方法在运行过程中才需要申请锁,普通方法是不需要申请的*/
public class AsynAndSynMethod {public synchronized void m1() {  //同步方法System.out.println(Thread.currentThread().getName() + " m1.start... ");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+" m1 end");}public void m2() {  //非同步方法try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+" m2 ");}public static void main(String[] args) {AsynAndSynMethod t = new AsynAndSynMethod();new Thread(()->t.m1(),"t1").start();  //new Thread(t::m1,"t1").start();new Thread(()->t.m2(),"t2").start();  //new Thread(t::m2,"t2").start();}
}

执行结果:

♣ 例1.6:对业务写方法加锁,对业务读方法不加锁,容易产生脏读问题。

/*对业务写方法加锁,对业务读方法不加锁,容易产生脏读问题,读到在写的过程中还没有完成的数据,可以对读方法加锁*/
public class Account {String name;double balance; //账户余额为成员变量 默认为0.0public synchronized void set(String name, double balance) { //写this.name = name;try {Thread.sleep(2000); //2s} catch (InterruptedException e) {e.printStackTrace();}this.balance = balance;}public double getBalance(String name) {  //读return this.balance;}public static void main(String[] args) {Account a = new Account();new Thread(()->a.set("张三",100.0)).start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(a.getBalance("张三"));  //0.0try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(a.getBalance("张三"));  //100.0}
}

♣ 例1.7:一个同步方法可以调用另外一个同步方法吗——可以

/*一个同步方法可以调用另外一个同步方法。一个线程已经拥有某个对象的锁,再次申请的时候任然会得到该对象的锁,即synchronized获得的锁是可重入的。*/
public class SynMethodCallAnother {synchronized void m1() {System.out.println("m1 start...");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}m2();}synchronized void m2() {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("m2");}
}

♣ 例1.8:在继承中,子类重写的同步方法可以调用父类的同步方法

/*一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到锁的对象,即可重入的。
在继承中,子类同步方法可以调用父类的同步方法*/
public class CallSuperclassSynMethod {synchronized void m() {System.out.println("m start...");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("m end");}public static void main(String[] args) {new Child().m();  //锁定的都是同一个对象(子类对象)}
}
class Child extends CallSuperclassSynMethod {@Overridesynchronized void m() {System.out.println("child m start");super.m();System.out.println("child m end");}
}

♣ 例1.9:出现异常,默认情况下锁会被释放,

/** 程序执行过程中,如果出现异常,默认情况锁会被释放,所以在并发处理的过程中,有异常要多加小心,不然会发生不一致的情况;
* 比如,在一个web application处理请求时,多个servlet线程共同访问同一个资源,这时如果异常处理不合适;
* 在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生的数据,因此要非常小心地处理同步业务逻辑中的异常。*/
public class SynchronizedException {int count = 0;synchronized void m(){System.out.println(Thread.currentThread().getName() + " start");while(true) {count ++ ;System.out.println(Thread.currentThread().getName() + " count = " +count);try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}if (count == 5) {int i = 1/0; //此处抛出异常,锁将会被释放。要想锁不被释放,可以在这里进行catch,然后循环继续}}}public static void main(String[] args) {SynchronizedException se = new SynchronizedException();Runnable r = new Runnable() {@Overridepublic void run() {se.m();}};new Thread(r, "t1").start();try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}new Thread(r,"t2").start();}
}

t1遇到异常释放了锁,t2立刻拿到了这把锁。参看下图的执行结果:

2.volatile关键字——使一个变量在多个线程中可见

♣ 例2.1 volatile的可见性

/**volatile关键字,使一个变量在多个线程中可见;但是volatile并不能保证多个线程共同修改running变量时所带来不一致的问题,即volatile不能完全代替synchronized* A、B线程都用到一个变量,java默认是A线程中保留一份copy,这样如果线程B修改了该变量,则线程A未必知道。* 使用volatile关键字,会强制让所有的线程都去读变量的修改后的值。*/
public class VolatileVisible {volatile boolean running = true;  //对比一下有无volatile的情况下,整个程序运行结果的区别void m() {System.out.println("m start");while(running) {  //死循环。只有running=false时,才能执行后面的语句}System.out.println("m end");}public static void main(String[] args) {VolatileVisible vt = new VolatileVisible();new Thread(vt::m,"t1").start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}vt.running = false;  //若running不被volatile关键字修饰时,线程“看不见”running被修改了}
}

volatile 只保证原子性,而 synchronized 既保证可见性又保证原子性,但是synchronized太“重”了(效率很低)!

♣ 例2.2  volatile不具备原子性

/*10个线程分别执行10000次count++,count是对象vna的成员变量,按理来说最终count=100000,但是最终每次执行结果都不一样,count一直小于100000,说明volatile不具备原子性*/
public class VolatileNoAtomic {volatile int count = 0;void m() {for(int i=0; i<10000; i++) {count ++ ;}}public static void main(String[] args) {VolatileNoAtomic vna = new VolatileNoAtomic();List<Thread> threads = new ArrayList<>();for (int i=0; i<10; i++) {threads.add(new Thread(vna::m, "thread" + i));}threads.forEach(o->o.start());threads.forEach((o)->{try {//join()方法阻塞调用此方法的线程,直到线程t完成,此线程再继续。通常用于在main()主线程内,等待其它线程完成再结束main()主线程。o.join(); //相当于在main线程中同步o线程,o执行完了,main线程才有执行的机会} catch (InterruptedException e) {e.printStackTrace();}});System.out.println(vna.count);}
}

                 ...  每次结果都不一样,不是预期的100000。

♣ 例2.3  synchronized既保证可见性又保证了原子性

/*上例中,可以用synchronized解决,synchronized可以保证可见性和原子性,volatile只能保证可见性*/
public class SynVisibleAndAtomic {int count = 0;synchronized void m() {  //m方法加了synchronized修饰,保证了原子性和可见性for (int i=0; i<10000; i++) {count ++ ;}}public static void main(String[] args) {SynVisibleAndAtomic sva = new SynVisibleAndAtomic();List<Thread> threads = new ArrayList<>();for (int i=0; i<10; i++) {threads.add(new Thread( sva::m , "thread-" + i));}threads.forEach((o)->o.start());threads.forEach((o)-> {try {o.join();} catch (InterruptedException e) {e.printStackTrace();}});System.out.println(sva.count); //100000}
}

♣例2.4 实现一个容器,提供两个方法add、size,线程1添加10个元素到容器,线程2监控元素的的个数,当个数为5个时,线程2提示并结束。

♣♣ 2.4.1 给容器list添加volatile

/*给list添加volatile之后,t2能够接到通知,但t2线程的死循环很浪费CPU*/
public class MyContainerVolatile {volatile List list = new ArrayList();public void add(Object o) {  //addlist.add(o);}public int size() {   //sizereturn list.size();}public static void main(String[] args) {MyContainerVolatile mcv = new MyContainerVolatile();new Thread( () -> {  //该线程负责往list里添加for (int i=0; i<10; i++) {mcv.add(new Object());System.out.print(" add-" + i);try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}},"t1").start();new Thread( () -> { //该线程一直监测list的size,直到size=5while(true) {  //一直监测着,很浪费CPUif(mcv.size() == 5) {  //此处未加同步,仍然可能会出现t1中又一次++为6了,才breakbreak;}}System.out.print(" t2结束 ");},"t2").start();}
}

上面的代码能解决问题,但t2线程的死循环很浪费CPU,影响性能!更优方法请参看下例:

♣♣ 2.4.2  使用wait()和notify()做到,wait释放锁,而notify不会释放锁

当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。

notify/notifyAll() 被执行时,会唤醒一个或多个正处于等待状态的线程,然后继续往下执行直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁

/*wait会释放锁,notify则不会。t1中notify唤醒t2,本线程不会释放锁,会一直执行下去直至被wait或synchronized代码块结束*/
public class MyContainerWaitNotify {volatile List list = new ArrayList();public void add(Object o) {list.add(o);}public int size() {return list.size();}public static void main(String[] args) {MyContainerWaitNotify mcwn = new MyContainerWaitNotify();final Object lock = new Object();new Thread(()->{synchronized (lock) {System.out.print(" ***线程t2启动*** ");if (mcwn.size() != 5) {try {lock.wait();  //size不等于5时,就一直在那等着,直到被t1叫醒} catch (InterruptedException e) {e.printStackTrace();}}System.out.print(" ***线程t2结束*** ");lock.notify();  //通知t1继续执行}}, "t2").start();new Thread(()->{synchronized (lock) {for(int i=0; i<10; i++) {mcwn.add(new Object());System.out.print(" add-"  + i);if (mcwn.size() == 5) {lock.notify();  //唤醒另一个线程t2,本线程继续执行,直至synchronized包裹的代码块结束或者调用了waittry {lock.wait(); //释放锁,让t2得以执行} catch (InterruptedException e) {e.printStackTrace();}}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}},"t1").start();}
}

上述使用wait和notify也解决了问题,但是有点过于复杂。java提供了门栓/门闩。

♣♣ 2.4.3 使用CountDownLatch(门闩)的await和countdown方法替代wait、notify方法来进行通知。

/*使用Latch(门栓)的await和countdown方法替代wait notify方法来进行通知。好处是通信方式简单,同时也可以指定等待时间。
* CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行.
* 当不涉及同步,只涉及锁定,用synchronized+wait/notify就显得太重了。这时应该考虑CountDownLatch/cyclicbarrier/semephore.*/
public class MyContainerLatch {volatile List list = new ArrayList(); //添加volatile,使t2能够得到通知public void add(Object o) {list.add(o);}public int size() {return list.size();}public static void main(String[] args) {MyContainerLatch mcl = new MyContainerLatch();CountDownLatch latch = new CountDownLatch(1);  //当1变成0时,门就开了new Thread(() -> {System.out.print(" *t2启动* ");if (mcl.size() != 5) {try {latch.await();  //等待不需要锁定一个对象//latch.await(5000,TimeUnit.MILLISECONDS); //也可以指定等待时间} catch (InterruptedException e) {e.printStackTrace();}}System.out.print(" *t2结束* ");},"t2").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{System.out.print(" *t1启动* ");for (int i=0; i<10; i++) {mcl.add(new Object());System.out.print(" add-" + i);if (mcl.size() == 5) {latch.countDown(); //打开门闩,让t2得以执行。调用一次countDown,就减1}try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}System.out.print(" *t1结束* ");},"t1").start();}
}

3.原子变量类(AtomXxx)

原子变量类 (Atomics)是基于CAS实现的能够保障对共享变量进行read-modify-write更新操作的原子性和可见性的一组工具类。所谓的read-modify-write更新操作,是指对共享变量的更新不是一个简单的赋值操作,而是变量的新值依赖于变量的旧值,例如自增操作 “count++”。由于volatile无法保障自增操作的原子性,而原子变量类的内部实现通常借助一个volatile变量并保障对该变量的read-modify-write更新操作的原子性,因此它可以被看作增强型的volatile变量。原子变量类一共有12个,可以被分为4组:

AtomicXxx类本身是原子性的,但是不能保证多个方法连续调用是原子性的!在 java.util.concurrent.atomic 包中。

♣ 例3.1  原子性操作int类型——AtomicInteger

public class AtomicIntegerTest {AtomicInteger count = new AtomicInteger(0);void m() {for (int i=0; i<10000; i++) {count.incrementAndGet(); incrementAndGet()-先+1,再返回; getAndIncrement()-先返回,再+1}}public static void main(String[] args) {AtomicIntegerTest ait = new AtomicIntegerTest();List<Thread> threads = new ArrayList<>();for (int i=0; i<10; i++) {threads.add(new Thread( ait::m , "thread" + i));}threads.forEach((o)->o.start());threads.forEach((o)->{try {o.join();} catch (InterruptedException e) {e.printStackTrace();}});System.out.println(ait.count); //100000}
}

4.ReentrantLock-重入锁

ReentrantLock重入锁,是实现Lock接口的一个类。在Java中通常实现锁有两种方式,一种是synchronized关键字,另一种是Lock。(synchronized在jdk1.5之后做了优化,性能提升了很多,只是使用ReentrantLock更灵活一些)。

synchronized和Lock的区别
tips synchronized(关键字) Lock(接口)
实现 基于JVM层面实现(JVM控制锁的获取和释放) 基于JDK层面实现(我们可以借助JDK源码理解)
使用 不用我们手动释放锁 需要手动上锁和释放锁(finally中unlock)
锁获取超时 不支持。拿不到锁就一直在那等着,等到“死”。 支持。可以设置超时时间,时间过了没拿到就放弃,即Lock可以知道线程有没有拿到锁。
获取锁响应中断 不支持。 支持。可以设置是否可以被打断。
释放锁的条件 满足一个即可:①占有锁的线程执行完毕②占有锁的线程异常退出③占有锁的线程进入waiting状态释放锁 调用unlock()方法
公平与否 非公平锁。(公平指的是哪个线程等的时间长就把锁交给谁) 默认为非公平锁,可以设置为公平锁(排队等候)。

♣ 例4.1 ReentrantLock可以用来代替synchronized

/* ReentrantLock用来替代synchronized* ReentrantLock必须要手动释放锁。使用synchronized锁定如果遇到异常,jvm会自动释放锁,但是Lock必须手动释放,因此常常在finally中释放锁*/
public class ReentrantLockTest1 {Lock lock = new ReentrantLock();void m1() {try {lock.lock();  //加锁  //相当于synchronized(this)for (int i=0; i<10; i++) {TimeUnit.SECONDS.sleep(1);System.out.print(" " + i);}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();  //释放锁}}void m2() {lock.lock();  //加锁System.out.print(" m2()... ");lock.unlock();  //释放锁}public static void main(String[] args) {ReentrantLockTest1 r1 = new ReentrantLockTest1();new Thread(r1::m1).start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(r1::m2).start();}
}

♣ 例4.2  ReentrantLock可以进行尝试锁定tryLock()

/*使用ReentrantLock可以进行尝试锁定tryLock();若无法锁定或在指定时间内无法锁定,线程可以决定是否等待*/
public class ReentrantLockTryLock {Lock lock = new ReentrantLock();void m1() {try {lock.lock();for (int i=0; i<10; i++) {TimeUnit.SECONDS.sleep(1);System.out.print(" " + i);}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}/*** 使用tryLock进行尝试锁定,不管锁定与否,方法都将会继续执行,可以根据tryLock的返回值判定是否被锁定了* 可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unlock的处理,必须放到finally中。*/void m2() {/* boolean locked = lock.tryLock();System.out.print("  m2..." + locked + "  ");if (locked) lock.unlock();  //false */  //不指定尝试时间boolean locked = false;try {locked = lock.tryLock(5,TimeUnit.SECONDS); //指定超时时间为5sSystem.out.println("  m2..." + locked + "  ");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public static void main(String[] args) {ReentrantLockTryLock r1 = new ReentrantLockTryLock();new Thread(r1::m1).start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}new Thread(r1::m2).start();}
}

♣ 例4.3  ReentrantLock可调用lockInterruptibly方法,对线程的interrupt方法作出响应,在一个线程等待的过程中,可以被打断。

/*ReentrantLock可调用lockInterruptibly()方法,对线程的interrupt()方法作出响应,在一个线程等待的过程中,可以被打断。
* ReentrantLock的lock()方法是不能被打断的,即锁用lock()方法锁定,线程调用interrupt()方法是毫无作用的*/
public class ReentrantLockInterruptibly {public static void main(String[] args) {Lock lock = new ReentrantLock();Thread t1 = new Thread(() -> {try {lock.lock();System.out.print("  t1 start...  ");TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);  //t1不停的运行,睡死了System.out.print("  t1 end...  ");} catch (InterruptedException e) {System.out.print(" t1-interrupted! ");} finally {lock.unlock();}});t1.start();Thread t2 = new Thread(() -> {try {// lock.lock(); //不能对interrupt()方法作出响应lock.lockInterruptibly();  //也是上锁,但是可以对interrupt()方法作出响应System.out.print("  t2 start...  ");TimeUnit.SECONDS.sleep(5);System.out.print("  t2 end...   ");} catch (InterruptedException e) {System.out.println("  t2-interrupted!  ");} finally {lock.unlock();}});t2.start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}t2.interrupt();  //打断t2的等待}
}

♣ 例4.4 ReentrantLock可以指定为共享锁

/*ReentrantLock可以指定为公平锁,构造方法中将fair属性设置为true即为公平锁,fair默认为false*/
public class ReentrantLockFair extends Thread {private static ReentrantLock lock = new ReentrantLock(true); //参数为true表示为公平锁,可对比输出结果@Overridepublic void run() {for (int i=0; i<20; i++) {lock.lock();try {System.out.println(Thread.currentThread().getName() + "-获得锁 ");} finally {lock.unlock();}}}public static void main(String[] args) {ReentrantLockFair r1 = new ReentrantLockFair();Thread t1 = new Thread(r1);Thread t2 = new Thread(r1);t1.start();t2.start();}
}

fair设置为true时,两个线程交替执行,如下图;  fair设置为false时,执行顺序是随机的,并不是谁等的时间长谁执行。

♣ 例4.5  写一个固定容量的同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用。

♣♣  4.5.1 使用wait和notify/notifyAll()来实现

/*写一个固定容量的同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用。
* wait和notifyAll方法来实习。*/
public class ProducerCustomerWaitNotifyAll {final private LinkedList<Object> list = new LinkedList<Object>();final private int MAX = 10; //最多十个元素private int count = 0;public synchronized void put(Object t) {while (list.size() == MAX) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}list.add(t);++count;this.notifyAll(); //通知消费者进程进行消费//notify只叫醒一个,叫醒的可能还是生产者,所有线程一直wait,程序就卡死了,所以用notifyAll}public synchronized Object get() {Object t = null;while (list.size() == 0) {  //try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}t = list.removeFirst();count --;this.notifyAll();  //通知生产者进程进行生产return t;}public static void main(String[] args) {ProducerCustomerWaitNotifyAll pc = new ProducerCustomerWaitNotifyAll();for (int i=0; i<10; i++) {  //10个消费者new Thread(() -> {for (int j=0; j<5; j++) {  //每个消费者最多消费5个System.out.println(pc.get());}},"c"+i).start();}//启动生产者线程for (int i=0; i<2; i++) { //2个生产者new Thread(() -> {for (int j=0; j<25; j++) {  //每个生产者最多生产25个pc.put(Thread.currentThread().getName() + " " + j);}},"p"+i).start();}}
}

♣♣  4.5.1 使用Lock和Condition来实现

/*使用Lock和Condition来实现生产者和消费者的同步容器,
相比使用wait/notifyAll,使用Conditionde的方式能更加精确地指定哪些线程被唤醒。*/
public class ProducerConsumerLockCondition {final private LinkedList<Object> list = new LinkedList<>();final private int MAX = 10;private int count = 0;private Lock lock = new ReentrantLock();private Condition producer = lock.newCondition();private Condition consumer = lock.newCondition();public void put(Object obj) {try {lock.lock();while (list.size() == MAX) {producer.await();}list.add(obj);++count ;consumer.signalAll(); //通知消费者进行消费} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public Object get() {Object obj = null;try {lock.lock();while (count == 0) {consumer.await();}obj = list.removeFirst();count -- ;producer.signalAll();  //通知生产者进行生产} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}return obj;}public static void main(String[] args) {ProducerConsumerLockCondition c = new ProducerConsumerLockCondition();for (int i=0; i<10; i++) {new Thread(()->{for (int j=0; j<5; j++) {System.out.println(c.get());}},"c"+i).start();}//启动生产者线程for (int i=0; i<2; i++) { //2个生产者new Thread(() -> {for (int j=0; j<25; j++) {  //每个生产者最多生产25个c.put(Thread.currentThread().getName() + " " + j);}},"p"+i).start();}}
}

5.ThreadLocal 线程局部变量

/*ThreadLocal是使用空间换时间,synchronized是使用时间换空间。
* 比如在Hibernate中的session就存在于ThreadLocal中,避免Synchronized的使用
* 线程局部变量属于每个线程都有自己的,线程间不共享,互不影响*/
public class ThreadLocalTest {static ThreadLocal<Person> tL = new ThreadLocal<>(); //每个线程的tL互不影响public static void main(String[] args) {new Thread(() -> {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(tL.get());}).start();new Thread(()-> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}tL.set(new Person());}).start();}static class Person {String name = "zhangsan";}
}

第二个线程设置了值,但是第一个线程get得到的是:,说明线程局部变量是互不影响的!

补充:

常用的并发工具类

闭锁:CountDownLatch

  闭锁允许一个线程或多个线程等待特定情况,同步完成线程中其他任务。

栅栏:CyclicBarrier

  CyclicBarrier和CountDownLatch都可以协同多个线程,让指定数量的线程等待期他所有的线程都满足某些条件之后才继续执行。CyclicBarrier可以重复使用(reset),而CountDownLatch只能够使用一次,如果还需要使用,必须重现new一个CountDownLatch对象。构造方法CyclicBarrier(int, Runnable) 所有线程达到屏障后,执行Runnable。

信号量:Semaphore

  信号量用来控制同时访问特定资源的线程数量。

交换者:Exchanger

  Exchanger 交换者用于在两个线程之间传输数据,被调用后等待另一个线程达到交换点,然后相互交互数据。

常用的并发容器(下一篇会详细讲到

ConcurrentHashMap:JDK1.7实现:分段锁;JDK1.8实现:元素(key)锁+链表+红黑树

SkipList:跳表自动随机维护一套索引,用于高效的索引List中的有序数据。

ConcurrentSkipListMap:TreeMap的并发实现

ConcurrentSkipListSet:TreeSet的并发实现

ConcurrentLinkedQueue:LinkedList的并发实现

CopyOnWriteArrayList:写时复制,在添加元素是,复制一个新的容器,在新容器中新增元素;读数据都在Old容器中操作,进行读写分离。数据一致性较弱,适合读多写少的场景。

CopyOnWriteArraySet:同上

下一篇:Java高并发编程 (马士兵老师视频)笔记(二)并发容器

Java高并发编程 (马士兵老师视频)笔记(一)同步器相关推荐

  1. 29W 字总结阿里 Java 高并发编程:案例 + 源码 + 面试 + 系统架构设计

    下半年的跳槽季已经开始,好多同学已经拿到了不错的 Offer,同时还有一些同学对于 Java 高并发编程还缺少一些深入的理解,不过不用慌,今天老师分享的这份 27W 字的阿里巴巴 Java 高并发编程 ...

  2. Java高并发编程详解系列-Java线程入门

    根据自己学的知识加上从各个网站上收集的资料分享一下关于java高并发编程的知识点.对于代码示例会以Maven工程的形式分享到个人的GitHub上面.   首先介绍一下这个系列的东西是什么,这个系列自己 ...

  3. Java高并发编程学习(三)java.util.concurrent包

    简介 我们已经学习了形成Java并发程序设计基础的底层构建块,但对于实际编程来说,应该尽可能远离底层结构.使用由并发处理的专业人士实现的较高层次的结构要方便得多.要安全得多.例如,对于许多线程问题,可 ...

  4. @冰河老师的巨作,人手一册的Java高并发编程指南,值得了解一下

    还真不好意思,这次 Java Thread Pool 惊爆了! 高并发是每一个后端开发工程师都头疼的问题,也是工程师能力的分水岭.要想基于JDK核心技术,玩转高并发编程,必须要好好修炼内功才行. 文章 ...

  5. Java高并发编程:活跃性危险

    Java高并发程序中,不得不出现资源竞争以及一些其他严重的问题,比如死锁.线程饥饿.响应性问题和活锁问题.在安全性与活跃性之间通常存在依赖,我们使用加锁机制来确保线程安全,但是如果过度地使用加锁,则可 ...

  6. Java高并发编程详解系列-7种单例模式

    引言 在之前的文章中从技术以及源代码的层面上分析了关于Java高并发的解决方式.这篇博客主要介绍关于单例设计模式.关于单例设计模式大家应该不会陌生,作为GoF23中设计模式中最为基础的设计模式,实现起 ...

  7. java高并发编程(二)

    马士兵java并发编程的代码,照抄过来,做个记录. 一.分析下面面试题 /*** 曾经的面试题:(淘宝?)* 实现一个容器,提供两个方法,add,size* 写两个线程,线程1添加10个元素到容器中, ...

  8. Java高并发编程(二):Java并发机制的底层实现机制

    Java代码在编译后会变成Java字节码,字节码在之后被类加载机制加载到JVM中,JVM执行字节码,最终需要转换为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令. ...

  9. Java高并发编程详解系列-Future设计模式

    导语   假设,在一个使用场景中有一个任务需要执行比较长的时间,通常需要等待任务执行结束之后或者是中途出错之后才能返回结果.在这个期间调用者只能等待,对于这个结果Future设计模式提供了一种凭据式的 ...

最新文章

  1. 未来二十年的AI科研应当往何处发展?AAAI给出了一份答案
  2. python unpack函数_Python numpy.unpackbits函数方法的使用
  3. MYSQL批量插入数据库实现语句性能分析
  4. 唤起微信/QQ返回不了当前页面解决方法
  5. 数据库mysql驱动在8.0以上解决时区问题
  6. mat opencv java_OpenCV Mat到JavaCV Mat转换
  7. Java异常处理实验原理_Java异常处理原理与原则
  8. java8的日期API总结(JSR310)
  9. 当你感到迷茫焦虑时,请听听白岩松这个演讲
  10. 我们在电脑房里上计算机课英语怎么说,2017年英语六级作文及翻译:计算机和人...
  11. 拓端tecdat|R语言分析协变量之间的非线性关系
  12. 计算机的优势和劣势_计算机专业毕业生考研还是就业应该怎么选?
  13. 强连通分量 Kosaraju科萨拉朱算法
  14. 网页录屏将视频截图保存成gif图片并压缩的方法超详细
  15. Navicat Premium 简介、安装、使用
  16. 普通本科毕业一年,靠着这1000道JAVA面试题,终逆袭上岸
  17. bzoj1375 双调路径
  18. android.view.InflateException: Binary XML file line #22: Binary XML file line #22:: Error inflating
  19. java 如何上传文件_java如何上传文件 | 快速入门
  20. sort函数进行二维vector的排序

热门文章

  1. Base64在线加密解密
  2. 【浏览器兼容性】如何隐藏微软的ie和edge浏览器密码输入框的小眼睛
  3. speedoffice(Word)文字怎么修改字体颜色
  4. ROS机器人SLAM学习:Gazebo定位与导航自主仿真
  5. TCP粘包问题以及解决方法
  6. Flutter 使用自定义 fluro 路由转场动画实现个性化页面切换
  7. 百万点赞怎么来?用 Python 制作抖音视频原来这么简单
  8. 2021,OpenSquare回顾过去,展望未来
  9. w7系统桌面没有计算机图标不见了,win7桌面上我的电脑图标不见了怎么办
  10. 如何在计算机桌面恢复我的电脑,桌面上我的电脑图标不见了怎么恢复?桌面计算机图标不见了的3个解决方法...