【多线程与高并发】3-JUC同步锁

锁的分类:

乐观锁(CAS),悲观锁(synchronized),自旋锁(CAS),读写锁(共享锁、排他锁),分段锁(LongAdder,ConcurrentHashMap)

ReentrantLock 可重入锁

reentrantlock用于替代synchronized,底层CAS
wait/notify为重点
本例中由于m1锁定this,只有m1执行完毕的时候,m2才能执行

NonfairSync->Sync->AbstractQueuedSynchronizer

public class T01_ReentrantLock1 {
	synchronized void m1() {
		for(int i=0; i<10; i++) {
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(i);
			if(i == 2) m2();
		}
	}
	
	synchronized void m2() {
		System.out.println("m2 ...");
	}
	
	public static void main(String[] args) {
		T01_ReentrantLock1 rl = new T01_ReentrantLock1();
		new Thread(rl::m1).start();
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//new Thread(rl::m2).start();
	}
}

需要注意的是,必须要必须要必须要手动释放锁(重要的事情说三遍)* 使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放

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();
    }
}

使用reentrantlock可以进行"尝试锁定"tryLock,这样无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待

try {
    locked = lock.tryLock(5, TimeUnit.SECONDS);
    System.out.println("m2 ..." + locked);
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    if(locked) lock.unlock();
}

使用ReentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应,在一个线程等待锁的过程中,可以被打断

public static void main(String[] args) {
    Lock lock = new ReentrantLock();
    Thread t1 = new Thread(()->{
        try {
            lock.lock();
            System.out.println("t1 start");
            TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
            System.out.println("t1 end");
        } catch (InterruptedException e) {
            System.out.println("interrupted!");
        } finally {
            lock.unlock();
        }
    });
    t1.start();

    Thread t2 = new Thread(()->{
        try {
            //lock.lock();
            lock.lockInterruptibly(); //可以对interrupt()方法做出响应
            System.out.println("t2 start");
            TimeUnit.SECONDS.sleep(5);
            System.out.println("t2 end");
        } catch (InterruptedException e) {
            System.out.println("interrupted!");
        } finally {
            lock.unlock();
        }
    });
    t2.start();

    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    t2.interrupt(); //打断线程2的等待
}

ReentrantLock还可以指定为公平锁,队列数线程是否检查,先来后到

public class T05_ReentrantLock5 extends Thread {	
    private static ReentrantLock lock=new ReentrantLock(true); 
    //参数为true表示为公平锁,请对比输出结果
    public void run() {
    for(int i=0; i<100; i++) {
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+"获得锁");
        }finally{
            lock.unlock();
        }
    }
  }
    public static void main(String[] args) {
        T05_ReentrantLock5 rl=new T05_ReentrantLock5();
        Thread th1=new Thread(rl);
        Thread th2=new Thread(rl);
        th1.start();
        th2.start();
    }
}

CountDownLatch 门闩(shuan)

latch.await();
倒数计数,用来等待线程结束。

private static void usingCountDownLatch() {
        Thread[] threads = new Thread[100];
        CountDownLatch latch = new CountDownLatch(threads.length);
        for(int i=0; i<threads.length; i++) {
            threads[i] = new Thread(()->{
                int result = 0;
                for(int j=0; j<10000; j++) result += j;
                latch.countDown();
            });
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("end latch");
    }

CyclicBarrier 循环栅栏

限流举例(Guava RateLimiter)
适用场景:复杂操作顺序执行->并发操作

    //CyclicBarrier barrier = new CyclicBarrier(20);
    CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("满人"));

    /*CyclicBarrier barrier = new CyclicBarrier(20, new Runnable() {
        @Override
        public void run() {
            System.out.println("满人,发车");
        }
    });*/

    for(int i=0; i<100; i++) {
            new Thread(()->{
                try {
                    barrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
    }
}

Phaser 阶段

public class T09_TestPhaser2 {
    static Random r = new Random();
    static MarriagePhaser phaser = new MarriagePhaser();
    static void milliSleep(int milli) {
        try {
            TimeUnit.MILLISECONDS.sleep(milli);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        phaser.bulkRegister(7);
        for(int i=0; i<5; i++) {
            new Thread(new Person("p" + i)).start();
        }
        new Thread(new Person("新郎")).start();
        new Thread(new Person("新娘")).start();

    }

    static class MarriagePhaser extends Phaser {
        @Override
        protected boolean onAdvance(int phase, int registeredParties) {
            switch (phase) {
                case 0:
                    System.out.println("所有人到齐了!" + registeredParties);
                    System.out.println();
                    return false;
                case 1:
                    System.out.println("所有人吃完了!" + registeredParties);
                    System.out.println();
                    return false;
                case 2:
                    System.out.println("所有人离开了!" + registeredParties);
                    System.out.println();
                    return false;
                case 3:
                    System.out.println("婚礼结束!新郎新娘抱抱!" + registeredParties);
                    return true;
                default:
                    return true;
            }
        }
    }

    static class Person implements Runnable {
        String name;

        public Person(String name) {
            this.name = name;
        }

        public void arrive() {
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 到达现场!\n", name);
            phaser.arriveAndAwaitAdvance();
        }

        public void eat() {
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 吃完!\n", name);
            phaser.arriveAndAwaitAdvance();
        }

        public void leave() {
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 离开!\n", name);
            phaser.arriveAndAwaitAdvance();
        }

        private void hug() {
            if(name.equals("新郎") || name.equals("新娘")) {
                milliSleep(r.nextInt(1000));
                System.out.printf("%s 洞房!\n", name);
                phaser.arriveAndAwaitAdvance();
            } else {
                phaser.arriveAndDeregister();
                //phaser.register()
            }
        }

        @Override
        public void run() {
            arrive();
            eat();
            leave();
            hug();
        }
    }
}

ReadWriteLock(重点 读写锁)

StampedLock 是其升级版

public class T10_TestReadWriteLock {
    static Lock lock = new ReentrantLock();// 效率非常低
    private static int value;

    static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static Lock readLock = readWriteLock.readLock(); //共享锁
    static Lock writeLock = readWriteLock.writeLock();//排他锁

    public static void read(Lock lock) {
        try {
            lock.lock();
            Thread.sleep(1000);
            System.out.println("read over!");
            //模拟读取操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void write(Lock lock, int v) {
        try {
            lock.lock();
            Thread.sleep(1000);
            value = v;
            System.out.println("write over!");
            //模拟写操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        //Runnable readR = ()-> read(lock);
        Runnable readR = ()-> read(readLock);

        //Runnable writeR = ()->write(lock, new Random().nextInt());
        Runnable writeR = ()->write(writeLock, new Random().nextInt());

        for(int i=0; i<18; i++) new Thread(readR).start();
        for(int i=0; i<2; i++) new Thread(writeR).start();
    }
}

Semaphore 信号灯

只有N个线程同时进行,可用于限流(车道、收费站)

public static void main(String[] args) {
    //Semaphore s = new Semaphore(2);
    Semaphore s = new Semaphore(2, true); //默认false不公平
    //允许一个线程同时执行
    //Semaphore s = new Semaphore(1);

    new Thread(()->{
        try {
            s.acquire();//-1  获得锁(许可)
            System.out.println("T1 running...");
            Thread.sleep(200);
            System.out.println("T1 running...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            s.release();
        }
    }).start();

    new Thread(()->{
        try {
            s.acquire();
            System.out.println("T2 running...");
            Thread.sleep(200);
            System.out.println("T2 running...");
            s.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}

Exchanger(交换器)

2者交换:游戏中交换装备

static Exchanger<String> exchanger = new Exchanger<>();
// 可以看成一个容器
public static void main(String[] args) {
    new Thread(()->{
        String s = "T1";
        try {
            s = exchanger.exchange(s); //变成T2
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " " + s);
    }, "t1").start();

    new Thread(()->{
        String s = "T2";
        try {
            s = exchanger.exchange(s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " " + s);
    }, "t2").start();
}

LockSupport

public static void main(String[] args) {
    Thread t = new Thread(()->{
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
            if(i == 5) {
                LockSupport.park();//停止
            }
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });

    t.start();

    LockSupport.unpark(t);// 并行执行 放行

    /*try {
        TimeUnit.SECONDS.sleep(8);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("after 8 senconds!");

    LockSupport.unpark(t);*/

}
  • 实现一个容器,提供两个方法,add,size,写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
  1. 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁,需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
    阅读下面的程序,并分析输出结果
    notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行,整个通信过程比较繁琐
public class T04_NotifyFreeLock {
	//添加volatile,使t2能够得到通知
	volatile List lists = new ArrayList();
	public void add(Object o) {
		lists.add(o);
	}
	public int size() {
		return lists.size();
	}
	public static void main(String[] args) {
		T04_NotifyFreeLock c = new T04_NotifyFreeLock();
		final Object lock = new Object();
		new Thread(() -> {
			synchronized(lock) {
				System.out.println("t2启动");
				if(c.size() != 5) {
					try {
						lock.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println("t2 结束");
				//通知t1继续执行
				lock.notify();
			}
		}, "t2").start();
		
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}

		new Thread(() -> {
			System.out.println("t1启动");
			synchronized(lock) {
				for(int i=0; i<10; i++) {
					c.add(new Object());
					System.out.println("add " + i);
					if(c.size() == 5) {
						lock.notify();
						//释放锁,让t2得以执行
						try {
							lock.wait();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					try {
						TimeUnit.SECONDS.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}, "t1").start();
	}
}
  1. 使用Latch(门闩)替代wait notify来进行通知,好处是通信方式简单,同时也可以指定等待时间,使用await和countdown方法替代wait和notify CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行,
    当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了,这时应该考虑countdownlatch/cyclicbarrier/semaphore
public static void main(String[] args) {
    T05_CountDownLatch c = new T05_CountDownLatch();
    CountDownLatch latch = new CountDownLatch(1);
    new Thread(() -> {
        System.out.println("t2启动");
        if (c.size() != 5) {
            try {
                latch.await();
                //也可以指定等待时间
                //latch.await(5000, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("t2 结束");
    }, "t2").start();

    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e1) {
        e1.printStackTrace();
    }
    new Thread(() -> {
        System.out.println("t1启动");
        for (int i = 0; i < 10; i++) {
            c.add(new Object());
            System.out.println("add " + i);
            if (c.size() == 5) {
                // 打开门闩,让t2得以执行
                latch.countDown();
            }
           // 下方如果注释,则产生时间差,可能到6 T2才进行打印,解决方法,门闩互相打开关闭
            /*try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
        }
    }, "t1").start();

}

解决方法:

public static void main(String[] args) {
		T05_CountDownLatch c = new T05_CountDownLatch();
		CountDownLatch latch1 = new CountDownLatch(1);
       CountDownLatch latch2 = new CountDownLatch(1);
		new Thread(() -> {
			System.out.println("t2 qidong");
			if (c.size() != 5) {
                try {
                    latch2.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
			System.out.println("t2 jieshu");
           latch1.countDown();
		}, "t2").start();

		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}

		new Thread(() -> {
			System.out.println("t1 qidong");
			for (int i = 0; i < 10; i++) {
				c.add(new Object());
				System.out.println("add " + i);
				if (c.size() == 5) {
                    latch2.countDown();
                    try {
                        latch1.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
			}
		}, "t1").start();
	}
  1. 使用LockSupport
  • 相比wait/notify更灵活,内部使用unsafe类
static Thread t1 = null, t2 = null;
public static void main(String[] args) {
    T07_LockSupport_WithoutSleep c = new T07_LockSupport_WithoutSleep();
    t1 = new Thread(() -> {
        System.out.println("t1启动");
        for (int i = 0; i < 10; i++) {
            c.add(new Object());
            System.out.println("add " + i);
            if (c.size() == 5) {
                LockSupport.unpark(t2);
                LockSupport.park();
            }
        }
    }, "t1");
    t2 = new Thread(() -> {
        //System.out.println("t2启动");
        //if (c.size() != 5) {
            LockSupport.park();
        //}
        System.out.println("t2 结束");
        LockSupport.unpark(t1);
    }, "t2");
    t2.start();
    t1.start();
}

扩展思路:使用Semaphore

public class SemaphoreTest {
    static Thread t1, t2;

    public static void main(String[] args) {
        SemaphoreTest semaphoreTest = new SemaphoreTest();
        Semaphore s = new Semaphore(1);
        t1 = new Thread(() -> {
            try {
                s.acquire();
                for (int i = 0; i < 5; i++) {
                    System.out.println(i);
                }
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            try {
                t2.start();
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            try {
                s.acquire();
                for (int i = 5; i < 10; i++) {
                    System.out.println(i);
                }
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }, "t1");

        t2 = new Thread(() -> {
            try {
                s.acquire();
                System.out.println("t2 结束");
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2");

        t1.start();
    }
}
  • 写一个固定容量同步容器,拥有put和get方法,以及getCount方法,
  • 能够支持2个生产者线程以及10个消费者线程的阻塞调用
public class MyContainer2<T> {
	final private LinkedList<T> lists = new LinkedList<>();
	final private int MAX = 10; //最多10个元素
	private int count = 0;
	private Lock lock = new ReentrantLock();
        // Condition本质就是不同的等待队列
	private Condition producer = lock.newCondition();
	private Condition consumer = lock.newCondition();
	
	public void put(T t) {
		try {
			lock.lock();
			while(lists.size() == MAX) { //想想为什么用while而不是用if?
				producer.await();
			}
			lists.add(t);
			++count;
			consumer.signalAll(); //通知消费者线程进行消费
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public T get() {
		T t = null;
		try {
			lock.lock();
			while(lists.size() == 0) {
				consumer.await();
			}
			t = lists.removeFirst();
			count --;
			producer.signalAll(); //通知生产者进行生产
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
		return t;
	}
	
	public static void main(String[] args) {
		MyContainer2<String> c = new MyContainer2<>();
		//启动消费者线程
		for(int i=0; i<10; i++) {
			new Thread(()->{
				for(int j=0; j<5; j++) System.out.println(c.get());
			}, "c" + i).start();
		}
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//启动生产者线程
		for(int i=0; i<2; i++) {
			new Thread(()->{
				for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j);
			}, "p" + i).start();
		}
	}
}
posted @ 2021-12-21 23:32  辽河老男孩  阅读(71)  评论(0)    收藏  举报