N 段代码掌握Java N 种多线程开发姿势
文章目录
synchronized相关
- 锁对对象独立创建 ==> 应该弄成 static类对象
class T {
private int count =10;
private Object o=new Object();
public void m(){
//任何线程要执行下面的代码,必须先拿到o的锁
synchronized (o){
count--;
System.out.println(Thread.currentThread().getName()+" count = "+count);
}
}
}
- 锁this ==> 类实例对象
class T {
private int count=10;
public void m(){
synchronized (this){
count--;
System.out.println(Thread.currentThread().getName()+" count = "+count);
}
}
}
- 锁实例方法 == 锁实例对象
class T {
private int count=10;
public synchronized void m(){
count--;
System.out.println(Thread.currentThread().getName()+" count = "+count);
}
}
- 锁静态方法 ==> 锁静态类对象
class T {
private static int count=10;
/*修饰静态方法-*/
public synchronized static void m(){ //这里等同于synchronized(yxxy.c_001.T.class)
count--;
System.out.println(Thread.currentThread().getName()+" count = "+count);
}
public static void mm(){
synchronized (T.class){ //这里写synchronized(this)是否可以? ==> 不可以 类对象和实例对象不一样
count--;
}
}
}
- 多线程启动去运行一个方法 ==> 严格意义上输出结果不一定按启动顺序
class T implements Runnable {
private int count=10;
@Override
public synchronized void run() {
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,"Thread"+i).start();
}
}
}
- 验证同步方法和非同步方法是否可以同步调用 ==> 验证异步性
class T {
/*同步方法*/
public synchronized void m1(){
System.out.println(Thread.currentThread().getName()+"m1 start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" m1 end");
}
/*非同步方法*/
public void m2(){
try {
Thread.sleep(5000);
} 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();
/*
* 等效写法
new Thread(t::m1,"t1").start();
new Thread(t::m2,"t2").start();*/
}
}
t1m1 start…
t2 m2
t1 m1 end
- 银行账户脏读场景
package c_008;
import java.util.concurrent.TimeUnit;
/** DirtyRead
* 对业务写方法加锁
* 对业务读方法不加锁
* 容易产生脏读问题(dirtyRead)
* 写还没写完 先读过去了 就是脏读
*/
class Account {
private String name;
private double balance; //余额
//写
public synchronized void set(String name,double balance){
this.name=name;
//模拟睡眠器间,读线程执行完毕,这时张三的余额初始为0
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance=balance;
}
//读
public /*synchronized*/ double getBalance(String name) {
return this.balance;
}
public static void main(String[] args) {
Account a=new Account();
//写100 进余额 需要 2s 完成
new Thread(()->a.set("张三",100.0)).start();
//睡1s
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//读 ==> 读到 0
System.out.println(a.getBalance("张三"));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//读到 100
System.out.println(a.getBalance("张三")); /*读线程*/
}
}
0.0
100.0
- 演示 synchronized 可重入支持继承机制
import java.util.concurrent.TimeUnit;
/**
* 一个同步方法可以调用另外一个同步方法,当然是两个同步方法锁对象一致
* 一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该锁
* 也就是说synchronized获得的锁是可重入的
* 可重入锁保证继承的机制 就是即使两个锁对象 拥有继承关系也可以
*/
class T {
//锁 t 实例对象
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 TT().m();
}
}
//子类
class TT extends T{
@Override
//锁tt 实例对象
synchronized void m(){
System.out.println("child m start");
//调用父类同步方法 此时会申请父类的锁对象
super.m();
System.out.println("chile m end");
}
}
- 演示同步出现异常 ==> 证明出异常会释放锁 ==> 合理的catch
package c_011;
import java.util.concurrent.TimeUnit;
/** 同步业务异常需谨慎!
* 程序在执行过程中,如果出现异常,默认情况锁会被释放
* 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况
* 比如,在一个web app处理过程中,多个servlet线程共同访问一个资源,这时如果异常处理不合适,
* 在第一个线程抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生的数据,
* 因此要非常小心的处理同步业务逻辑中的异常
*/
class T {
int count=0;
//循环对 count 做自增
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();
}
//在 count == 5 时候 模拟出异常 程序会释放锁
if (count == 5){
int i=1 / 0;
}
}
}
//两个线程先后去执行 m 方法
public static void main(String[] args) {
T t=new T();
Runnable r=new Runnable() {
@Override
public void run() {
t.m();
}
};
new Thread(r,"t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r,"t2").start();
}
}
t1 start
t1 count = 1
t1 count = 2
t1 count = 3
t1 count = 4
t1 count = 5
t2 start
t2 count = 6
Exception in thread “t1” java.lang.ArithmeticException: / by zero
at c_011.T.m(T.java:27)
at c_011.T$1.run(T.java:46)
at java.base/java.lang.Thread.run(Thread.java:831)
t2 count = 7
t2 count = 8
- 锁失效
package c_017;
import java.util.concurrent.TimeUnit;
/**
* 锁定某个对象o,如果o的属性发生改变,不影响锁的使用
* 但如果o变成另外一个对象,则锁失效
* 应该避免将锁定对象的引用变成另外的对象
*/
class T {
Object o=new Object();
void m(){
synchronized (o){
while (true){
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
T t=new T();
//启动第一个线程
new Thread(t::m,"t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//创建第二个线程
Thread t2=new Thread(t::m,"t2");
t.o = new Object(); //锁发生改变,所以t2线程得意执行,如果注释掉这句话,线程2永远得不到执行机会
t2.start();
}
}
t1
t1
t1
t2 //锁失效后t2得以进入同步区代码
t2
t1
t2
t1
valiate 相关
- valiate 使用场景
package c_012;
import java.util.concurrent.TimeUnit;
/**
* volatile关键字,使一个变量在多个线程间可见
* A B线程都用到一个变量,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道
* 使用volatile关键字,会让所有线程都读到该变量的修改值
*
* 在下面的代码中,running是存在于堆内存的t对象中
* 当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中直接使用这个copy,并不会每次都去读取
* 堆内存,这样,当主线程修改running的值之后,t1线程感知不到,所以不会停止运行
*
* 使用volatile,将会强制所有线程都去堆内存读取running的值
*
* 可以阅读这篇文章进行更深入的理解
* https://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html
*
* volatile 并不能保证多个线程共同修改runing变量时所带来的不一致问题,也就是说volatile不能替代synchronized
*/
class T {
//对比一下有无volatile的情况下,整个程序运行结果的区别
volatile boolean running = true;
void m(){
System.out.println("m start");
while (running){
System.out.println("m is running");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// running == false
System.out.println("m end");
}
//2s 后循环判断为 false
public static void main(String[] args) {
T t=new T();
new Thread(t::m,"t1").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.running=false;
}
}
-
valiate 无法保证最终一致性
/** * volatile并不能保证多个线程共同修改running变量时所带来的不一致问题 * 也就是说volatile不能替代synchronized * 看到了又如何 两个线程指令一交错 我看到了但我也已经改动了 除非配合 cas */ class T { volatile int count = 0; //volatile代替 synchronized还是会有不同步问题 void m(){ //疯狂对count自增 for (int i = 0; i < 1000000; i++) { count++; } } public static void main(String[] args) { T t=new T(); //模拟线程池 List<Thread> threads=new ArrayList<>(); for (int i = 0; i < 10; i++) { threads.add(new Thread(t::m,"thread-"+i)); } threads.forEach((o)->o.start()); //join 是一种用于主线程与子线程的同步方式 // 子线程调用 join 替代主线程再次休眠 //在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行 //这里就是等所有子线程执行完主线程再执行 threads.forEach((o)->{ try { o.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(t.count); } }
4116738 < 100000
AtomXXX类相关
- 解决上一个 valiate 的最终一致性问题
package c_015;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 解决同样的问题的更高效的方法,使用AtomXXX类
* AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性的
*/
class T {
//使用原子类
AtomicInteger count = new AtomicInteger(0);
void m(){
for (int i = 0; i < 10000000; i++) {
/*if (count.get()<1000000)*/
count.incrementAndGet(); //替代count++的
/*如果这里出现其他方法,那么m方法不能保证原子性*/
}
}
public static void main(String[] args) {
T t=new T();
List<Thread> threads=new ArrayList<>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(t::m,"thread-"+i));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
100000000
底层是乐观锁 https://blog.csdn.net/JunSIrhl/article/details/106858462
面试题1 观察者
/**
* 曾经的面试题:
* 实现一个容器,提供两个方法:add,size
* 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
* 分析下面这个程序,能完成这个功能吗?
*/
public class MyContainer1 {
static List<Object> list = new ArrayList();
public void add(Object o){
list.add(o);
}
public int size(){
return list.size();
}
public static void main(String[] args) {
MyContainer1 c = new MyContainer1();
//启动十个线程去搞
new Thread(()->{
/*添加10个对象*/
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1").start();
//观察者
new Thread(()->{
while (true){
if (c.size() == 5){
break;
}
}
System.out.println("t2结束");
},"t2").start();
}
}
add 1
add 2
add 3
add 4
add 5
add 6
==> t2 线程中的 size没有可见性
- 对 list添加 valiate 后
/**
* 曾经的面试题:
* 实现一个容器,提供两个方法:add,size
* 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
* 给lists添加volatile之后,t2能够接到通知,但结果好像不准确,但是t2线程的死循环很浪费cpu,如果不用死循环怎么做呢?
*
* 分析下面这个程序,能完成这个功能吗?
*/
public class MyContainer2 {
//添加volatile,使t2能够得到通知
volatile List<Object> list = new ArrayList();
public void add(Object o){
list.add(o);
}
public int size(){
return list.size();
}
public static void main(String[] args) {
MyContainer2 c=new MyContainer2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add "+ i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1").start();
new Thread(()->{
while (true){
if (c.size() == 5){
break;
}
}
System.out.println("t2结束");
},"t2").start();
}
}
add 0
add 1
add 2
add 3
add 4
t2结束
add 5
add 6
- 不要死循环 改用通知机制
/**
* 曾经的面试题:
* 实现一个容器,提供两个方法:add,size
* 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
*
* 给lists添加volatile之后,t2能够接到通知,但是t2线程的死循环很浪费cpu,如果不用死循环怎么做呢?
*
* 这里使用wait和notify可以做到,wait会释放锁,而notify不会释放锁
* 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
*
* 阅读下面程序,并分析出结果
* 可以读到输出结果但并不是sieze=5时退出,而是t1结束时t2才接收到通知而退出
*
* * notify之后,t1必须释放锁,t2退出后也必须notify,通知t1继续执行
* * 整个通信过程比较繁琐
*
*
*/
public class MyContainer3 {
//添加volatile,使t2能够得到通知
volatile List<Object> list=new ArrayList();
public void add(Object o){
list.add(o);
}
public int size(){
return list.size();
}
public static void main(String[] args) {
MyContainer3 c=new MyContainer3();
final Object lock = new Object();
//观察者
new Thread(()->{
synchronized (lock){
System.out.println("t2启动");
if (c.size() != 5){
try {
//进入等待 释放锁 -> t1取得锁*/
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2结束");
}
},"t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//对容器做 add 操作
new Thread(()->{
//等待lock锁
synchronized (lock){
System.out.println("t1启动");
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
//通知观察者
if (c.size() == 5){
//对持有本锁的其他线程进行通知, notify不会当前锁释放锁 t2无法获得锁 ->*/
lock.notify();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t1结束");
}
},"t1").start();
}
}
t2启动
t1启动
add 0
add 1
add 2
add 3
add 4
add 5
add 6
add 7
add 8
无效
/**
* notify之后,t1必须释放锁,t2退出后也必须notify,通知t1继续执行
* 整个通信过程比较繁琐
*/
public class MyContainer4 {
//添加volatile,使t2能够得到通知
volatile List<Object> list = new ArrayList();
public void add(Object o){
list.add(o);
}
public int size(){
return list.size();
}
public static void main(String[] args) {
MyContainer4 c = new MyContainer4();
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 e) {
e.printStackTrace();
}
new Thread(()->{
synchronized (lock){
System.out.println("t1启动");
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();
}
}
System.out.println("t1结束");
}
},"t1").start();
}
}
基于 AQS 的CountDownLatch
解决上一段代码频繁释放通知的繁琐
/**
* 使用Latch(门闩/门栓)替代wait notify来进行通知
* 好处是通信方式简单,同时也可以指定等待时间
* 使用await和countdown方法替代wait和notify
* CountDownLatch不涉及锁定,当count的值为零是当前线程继续运行
* 当不涉及同步,只是涉及线程通信的时候,用synchronized+wait/notify就显得太重了
* 这时应该考虑CountDownLacth/cyclicbarrier/semaphore
*
*
*/
public class MyContainer5 {
//添加volatile,使t2能够得到通知
volatile List<Object> list = new ArrayList();
public void add(Object o){
list.add(o);
}
public int size(){
return list.size();
}
public static void main(String[] args) {
MyContainer5 c=new MyContainer5();
CountDownLatch latch = new CountDownLatch(1);
/*监视线程*/
new Thread(()->{
System.out.println("t2启动");
if (c.size() != 5){
try {
//Decrements the count of the latch
//releasing all waiting threads if the count reaches zero.
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 e) {
e.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){
//Decrements the count of the latch
//releasing all waiting threads if the count reaches zero.
latch.countDown();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t1结束");
},"t1").start();
}
}
ReentrantLock相关
- 基本演示
/**
* reentrantlock用于替代synchronized
* 需要注意的是,必须要手动释放锁
* 使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放
*/
class ReentrantLock2 {
//ReentrantLock
Lock lock = new ReentrantLock();
void m1(){
/**
* 在 try-finally 外加锁的话,如果因为发生异常导致加锁失败
* try-finally 块中的代码不会执行
* 相反,如果在 try{ } 代码块中加锁失败,finally 中的代码无论如何都会执行
* 但是由于当前线程加锁失败并没有持有 lock 对象锁,程序会抛出异常
*
* 详情参考 https://blog.csdn.net/u013568373/article/details/98480603
*/
lock.lock(); //相当于synchronized(this)
try {
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();
try {
System.out.println("m2....");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLock2 r1=new ReentrantLock2();
new Thread(r1::m1).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r1::m2).start();
}
}
- trylock
/**
* reentrantlock用于替代synchronized
*
* 需要注意的是,必须要手动释放锁
* 使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放
*
* 使用reentrantlock可以进行“尝试锁定”trylock,这样无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待
*/
class ReentrantLock3 {
Lock lock=new ReentrantLock();
void m1(){
lock.lock(); //相当于synchronized(this)
try {
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println(i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
/**
* 使用trylock进行尝试锁定,不管锁定与否,方法都将继续执行
* 可以根据trylock的返回值来判断是否锁定
* 也可以指定trylock的时间,由于trylock(time)抛出异常,所以要注意unlock的处理,必须方法finally中
*/
void m2(){
boolean locked = false;
try {
//try lock 等待 5s
locked= lock.tryLock(5, TimeUnit.SECONDS);
System.out.println("m2...."+ locked );
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (locked) lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLock3 r1 = new ReentrantLock3();
new Thread(r1::m1).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r1::m2).start();
}
}
0
1
2
3
4
m2…false
5
6
7
- 演示打断
lockInterruptibly
-
如果当前线程未被中断,则获取锁。
-
如果该锁没有被另一个线程保持,则获取该锁并立即返回,将锁的保持计数设置为 1。
-
如果当前线程已经保持此锁,则将保持计数加 1,并且该方法立即返回。 (可重入)
-
如果锁被另一个线程保持,发生以下两种情况之一以前,该线程将一直处于休眠状态:
- 锁由当前线程获得
- 其他某个线程中断当前线程
-
如果当前线程获得该锁,则将锁保持计数设置为 1。
如果当前线程:-
在进入此方法时已经设置了该线程的中断状态
-
在等待获取锁的同时被中断。
则抛出 InterruptedException,并且清除当前线程的已中断状态。
-
package c_020;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用ReentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应
* 在一个线程等待锁的过程中,可以被打断
*/
public class ReentrantLock4 {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Thread t1 = new Thread(()->{
lock.lock();
try {
System.out.println("t1 start");
//无限睡眠、线程2拿不到锁
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
System.out.println("t1 end");
} catch (InterruptedException e) {
System.out.println("InterruptedException");
} finally {
lock.unlock();
}
});
t1.start();
Thread t2 = new Thread(()->{
try {
//此时锁被t1持有 在此等待
lock.lockInterruptibly();
System.out.println("t2 start");
TimeUnit.SECONDS.sleep(5);
System.out.println("t2 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("打断响应且释放锁");
lock.unlock();
}
});
t2.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打断线程2的等待 抛出异常
t2.interrupt();
}
}
- 演示公平锁 ==> 乖乖去阻塞队列等待被唤醒 排队
/**
* ReentrantLock还可以指定公平锁
*/
public class ReentrantLock5 extends Thread {
//参数为true表示为公平锁,轻对比输出结果
private static ReentrantLock lock = new ReentrantLock(true);
@Override
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) {
ReentrantLock5 r1 = new ReentrantLock5();
Thread th1 = new Thread(r1);
Thread th2 = new Thread(r1);
th1.start();
th2.start();
}
}
输出交替
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁…
非公平锁 ==> 不可重入 ==>
Thread-1获得锁
Thread-1获得锁
Thread-1获得锁
Thread-1获得锁
Thread-1获得锁
Thread-2获得锁
Thread-2获得锁
Thread-2获得锁…
面试题 2 生产者与消费者
wait/notify
package c_021;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
/**
* 面试题:写一个固定容量同步容器,拥有put和get方法,以及getCount方法
* 能够支持两个生产者线程以及10个消费者线程的阻塞调用
* 使用wait和nofify、notifyAll来实现
*/
public class MyContainer1<T> {
final private LinkedList<T> list = new LinkedList<T>();
final private int MAX = 10; //最多10个元素
private int count=0;
//生产者
public synchronized void put(T t){
//想想为什么用while而不是用if ->while 中每条语句都会对while中的判断条件进行判断
//满了 ==> 等着
while (list.size() == MAX){
try {
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
list.add(t);
++count;
this.notifyAll();//通知消费者线程进行消费
}
//消费者
public synchronized T get(){
T t = null;
//没得拿 wait 等着
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) {
MyContainer1<String> c =new MyContainer1<>();
//启动十个消费者线程
for (int i = 0; i < 10; i++) {
new Thread(()->{
//一次拿五个
for (int j = 0; j < 5; j++) {
System.out.println("cget:" + c.get());
}
},"c" + i).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//启动生产者线程
for (int i = 0; i < 2; i++) {
new Thread(()->{
//一次生产25个
for (int j = 0; j < 25; j++) {
c.put(Thread.currentThread().getName()+" "+j);
}
},"p" + i).start();
}
}
}
condition条件变量
package c_021;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 面试题:写一个固定容量同步容器,拥有put和get方法,以及getCount方法
* 能够支持两个生产者线程以及10个消费者线程的阻塞调用
*
* 使用Lock和Condition来实现
* 对比两种方式,condition的方式可以更加精确的指定哪些线程被唤醒
* notifyall的唤醒是由JVM选择的
*
*/
public class MyContainer2<T> {
final private LinkedList<T> list = new LinkedList<T>();
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){
lock.lock();
try {
while (list.size() == MAX){ //想想为什么用while而不是用if
/*Causes the current thread to wait until it is signalled or interrupted.*/
producer.await();
}
list.add(t);
++count;
//通知消费者线程进行消费
consumer.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public T get(){
T t = null;
lock.lock();
try {
while (list.size() == 0){
consumer.await();
}
t = list.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();
}
}
}
ThreadLocal相关
- 基本使用 - 背景
package c_022;
import java.util.concurrent.TimeUnit;
/**
* ThreadLocal线程局部变量
*/
public class ThreadLocal1 {
volatile static Person p = new Person(); /*使用volatile-->线程可见
*注意这里不写Volatile可能发生问题,也可能不发生问题*/
public static void main(String[] args) {
//2s 前后 pname
new Thread(()->{
System.out.println(p.name);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(p.name);
}).start();
//修改 name
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.name="lisi1";
}).start();
}
}
class Person{
String name="zhangsan";
}
zhangsan
…
lisi1
- 使用 threadlocal
package c_022;
import java.util.concurrent.TimeUnit;
/**
* ThreadLocal线程局部变量
*
* ThreadLocal是使用空间换时间,synchronized是使用时间换空间
* 比如在hibernate中的session就存在ThreadLocal中,避免synchronized的使用
*/
public class ThreadLocal2 {
/*不使用volatile,各个线程维护各自的"本地副本”*/
static ThreadLocal<Person> t1 = new ThreadLocal<>();
public static void main(String[] args) {
System.out.println(t1.get());
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*使用get获取默认值,get不到线程2中的new Person (lisi) 所以结果为null*/
System.out.println(t1.get());
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.set(new Person()); /*set->设置副本为new Person*/
}).start();
}
static class Person{
String name="zhangsan";
}
}
null
Null
面试题 3 火车票
情景复现
/**
* 有n张火车票,每张票都有一个编号
* 同时有10个窗口对外售票
* 请写一个模拟程序
* 分析下面的程序可能会产生哪些问题?
* 重复销售?超量销售? --> 不加锁的情况下 都有可能
*/
public class TicketSeller1 {
static List<String> tickets = new ArrayList<>();
//初始 1w 张票
static {
for (int i = 0; i < 10000; i++) {
tickets.add("票编号:" + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
//无限抢票
new Thread(()->{
while (tickets.size() > 0){
System.out.println(Thread.currentThread().getName()+"销售了--" + tickets.remove(0));
}
},"窗口"+i).start();
}
}
}
-
static Vector< String> tickets = new Vector<>(); 低级线程安全
-
synchronized (tickets){ ==> 十分低效
-
static Queue< String> tickets=new ConcurrentLinkedQueue<>(); 高级并发容器
package c_024; import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; /** * 有n张火车票,每张票都有一个编号 * 同时有10个窗口对外售票 * 请写一个模拟程序 * * 分析下面的程序可能会产生哪些问题? * 重复销售?超量销售? * */ public class TicketSeller4 { //并发容器 static Queue<String> tickets = new ConcurrentLinkedQueue<>(); static { for (int i = 0; i < 1000; i++) { tickets.add("票编号:"+i); } } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ while (true){ //先poll再判断,就不用加syn -->但输出结果非顺序(判断操作分离 String s = tickets.poll(); if (s == null){ break; }else{ System.out.println(Thread.currentThread().getName()+"销售了--" + s); } } },"窗口"+ i).start(); } } }
并发容器相关【待补充】
线程池相关
/**
* 认识Executor
*/
public class T01_MyExecutor implements Executor {
public static void main(String[] args) {
new T01_MyExecutor().execute(()->{
//Runnable
System.out.println("hello executor");
});
}
@Override
public void execute(Runnable command){
new Thread(command).run();
//command.run();
}
}
/**
* 认识ExecutorService,阅读API文档
*/
/**
* 认识Callable,对Runnable进行了扩展
*/
/**
* 认识Executors
*/
package c_026;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 线程池的概念
*/
public class T05_ThreadPool {
public static void main(String[] args) throws InterruptedException {
//execute submit
ExecutorService service= Executors.newFixedThreadPool(5);
for (int i = 0; i < 6; i++) {
service.execute(()->{
try {
TimeUnit.MILLISECONDS.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
//打印线程池信息
System.out.println(service);
//等任务执行完成后结束
service.shutdown();
System.out.println(service.isTerminated());
//是否shutdown状态
System.out.println(service.isShutdown());
System.out.println(service);
//给时间让任务都执行完,根据CPU的性能,需要调控好时间才能看出区别
TimeUnit.SECONDS.sleep(10);
System.out.println(service.isTerminated());
System.out.println(service.isShutdown());
System.out.println(service);
}
}
java.util.concurrent.ThreadPoolExecutor@a09ee92[Running, pool size = 5, active threads = 5, queued tasks = 1, completed tasks = 0]
false
true
java.util.concurrent.ThreadPoolExecutor@a09ee92[Shutting down, pool size = 5, active threads = 5, queued tasks = 1, completed tasks = 0]
pool-1-thread-1
pool-1-thread-5
pool-1-thread-3
pool-1-thread-2
pool-1-thread-4
pool-1-thread-1
false
true
java.util.concurrent.ThreadPoolExecutor@a09ee92[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 6]
Future Callable
单线程与线程池用法
public class T06_Future {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//定义任务
FutureTask<Integer> task = new FutureTask<>(()->{
//Callable
TimeUnit.MILLISECONDS.sleep(500);
return 1000;
});
//新创建线程来执行这个任务task
new Thread(task).start();
//阻塞,等待计算完成,取回结果 --> 1000
System.out.println(task.get());
//线程池操作
ExecutorService service= Executors.newFixedThreadPool(5);
/*任务定义*/
Future<Integer> f = service.submit(()->{
TimeUnit.MILLISECONDS.sleep(500);
return 1;
});
System.out.println(f.get());
System.out.println(f.isDone());
}
}
1000
1
true
- 并行计算质数
/**
* 应用
* nasa --> 平行(并行)计算
*/
public class T07_ParallelComputing {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//正常计算 统计时间
long start = System.currentTimeMillis();
List<Integer> results = getPrime(1,200000);
long end = System.currentTimeMillis();
System.out.println(end - start);
//线程池方式
ExecutorService service= Executors.newFixedThreadPool(5);
MyTask t1=new MyTask(1,80000);
MyTask t2=new MyTask(80001,130000);
MyTask t3=new MyTask(130001,170000);
MyTask t4=new MyTask(170000,200000);
Future<List<Integer>> f1 = service.submit(t1);
Future<List<Integer>> f2 = service.submit(t2);
Future<List<Integer>> f3 = service.submit(t3);
Future<List<Integer>> f4 = service.submit(t4);
start=System.currentTimeMillis();
f1.get();
f2.get();
f3.get();
f4.get();
end=System.currentTimeMillis();
System.out.println(end - start);
}
//获取 start ==> end 之间的质数
static class MyTask implements Callable<List<Integer>>{
int startPos, endPos;
MyTask(int s,int e){
this.startPos = s;
this.endPos = e;
}
//重写Callable的cALL方法 --> 大致跟run方法一样
@Override
public List<Integer> call() throws Exception {
List<Integer> r = getPrime(startPos , endPos);
return r;
}
}
//判断质数
private static boolean isPrime(int num){
for (int i = 2; i < num / 2; i++) {
if (num % i == 0){
return false;
}
}
return true;
}
//获取质数
private static List<Integer> getPrime(int start, int end) {
List<Integer> result= new ArrayList<>();
for (int i = start; i <= end; i++) {
if (isPrime(i)){
result.add(i);
}
}
return result;
}
}
CachedPool
无限救急
public class T08_CachedPool {
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newCachedThreadPool();
System.out.println(service);
for (int i = 0; i < 2; i++) {
service.execute(()->{
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
System.out.println(service);
TimeUnit.SECONDS.sleep(80);
System.out.println(service);
}
}
java.util.concurrent.ThreadPoolExecutor@279f2327[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
java.util.concurrent.ThreadPoolExecutor@279f2327[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
pool-1-thread-1
pool-1-thread-2
SingleThreadExecutor
单线程处理多任务
ExecutorService service= Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
final int j = i;
service.execute(()->{
System.out.println(j + " "+Thread.currentThread().getName());
});
}
0 pool-1-thread-1
1 pool-1-thread-1
2 pool-1-thread-1
3 pool-1-thread-1
4 pool-1-thread-1
SchedulePool
创建一个周期线程池,支持定时及周期性任务执行
package c_026;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class T10_SchedulePool {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
/**
* Creates and executes a periodic action that becomes enabled first
* after the given initial delay*/
service.scheduleAtFixedRate(()->{
try {
//1s 内随机睡
// TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
//延迟 0 秒 0.5秒执行一次任务
},0,500, TimeUnit.MILLISECONDS);
}
}
pool-1-thread-4
pool-1-thread-4
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-4
pool-1-thread-4
pool-1-thread-4
pool-1-thread-4
pool-1-thread-1
pool-1-thread-1