一个并发的问题
现在有100张票,很多人来抢,可能就会出现超卖的问题
public static int cnt = 100;public static void main(String[] args) throws InterruptedException {List<Thread> list = new ArrayList<>();for (int i=0;i<1000;i++){Thread thread = new Thread(){@Overridepublic void run() {if(cnt>0){String name = Thread.currentThread().getName();System.out.println(name+"抢到了"+cnt+"张票");cnt--;}}};list.add(thread);}//启动for (int i=0;i<list.size();i++){list.get(i).start();}for (int i=0;i<list.size();i++){list.get(i).join();}System.out.println("剩下的票:"+cnt);}
第一个问题:cnt>0作为条件,会导致并发的问题
- 第二个问题:cnt在—的时候,也可能导致并发的问题
使用锁进行解决
public static int cnt = 100;//创建一把锁static Object lock = new Object();public static void main(String[] args) throws InterruptedException {List<Thread> list = new ArrayList<>();for (int i=0;i<1000;i++){Thread thread = new Thread(){@Overridepublic void run() {//使用锁将可能出现并发的代码锁住synchronized (lock){if(cnt>0){String name = Thread.currentThread().getName();System.out.println(name+"抢到了"+cnt+"张票");cnt--;}}}};list.add(thread);}//启动for (int i=0;i<list.size();i++){list.get(i).start();}for (int i=0;i<list.size();i++){list.get(i).join();}System.out.println("剩下的票:"+cnt);}
synchronized
基本使用,锁对象
- 所有的线程必须要使用同一把锁对象才可以有效果
以下代码是无法锁住线程方法的, 如果是多线程,下面的代码,每个线程对象都会使用自己的独立锁 ```java public class TicketThread extends Thread{ public static int cnt = 100; Object lock = new Lock();
@Override public void run() { //使用锁将可能出现并发的代码锁住 synchronized (lock){
if(cnt>0){String name = Thread.currentThread().getName();System.out.println(name+"抢到了"+cnt+"张票");cnt--;}
} } }
- 可以将Object lock使用static修饰,或者从外部传入同一把锁- 修饰this对象- 注意必须是多个线程使用一个对象的时候才有效果,一般用于单例模式- 创建一个实现了Runnable的任务```javapublic class TickTask implements Runnable {public int cnt = 100;@Overridepublic void run() {//使用锁将可能出现并发的代码锁住synchronized (this){if(cnt>0){String name = Thread.currentThread().getName();System.out.println(name+"抢到了"+cnt+"张票");cnt--;}}}}
- 通过线程池,让多个线程调用同一个task
public static void main(String[] args) {//线程池+runnable组合ExecutorService service = Executors.newFixedThreadPool(10);TickTask task = new TickTask();for(int i=0;i<1000;i++){service.execute(task);}}
- 修饰方法
- 一般情况需要需要通过static配合使用才有效果,因为锁住是类的方法,而不是对象的方法
- 如果是对象的方法,必须要保证是同一个对象才有效果
- 一般情况,最好不要锁方法
修饰类的类对象
- 每个类被jvm加载之后,都会生产一个类的对象,而且是唯一的
- 大多数的情况直接锁类对象,都是有效果的,因为类对象是唯一的
synchronized(TicketThread.class){if(cnt>0){String name = Thread.currentThread().getName();System.out.println(name+"抢到了"+cnt+"张票");cnt--;}}
synchronized基本原理
synchronized是自动锁,什么时候加锁,什么时候释放锁,都是系统完成的,如果在使用不当,那么会出现死锁
创建一个男孩线程去找女孩约会
public class Boy extends Thread {@Overridepublic void run() {try {date();} catch (InterruptedException e) {e.printStackTrace();}}public static void date() throws InterruptedException {//男孩只能去找一个女孩约会synchronized (Boy.class){System.out.println("男去找女孩约会了");Thread.sleep(10);//加上锁,防止女孩同和两个男孩约会synchronized(Girl.class){System.out.println("女孩在约会中...");}System.out.println("在一起约会了...");}}}
创建一个女孩去找男孩约会
public class Girl extends Thread{@Overridepublic void run() {try {date();} catch (InterruptedException e) {e.printStackTrace();}}public void date() throws InterruptedException {//女孩只能去找一个男孩约会synchronized (Girl.class){System.out.println("女孩去找男孩约会了");//防止男孩和多个女孩约会synchronized (Boy.class){System.out.println("男孩在约会了");}System.out.println("在一起约会了");}}}
开始约会
public static void main(String[] args) throws InterruptedException {new Boy().start();new Girl().start();}
Lock是JDK提供的锁对象,可以手动控制锁的加载和释放
lock是一个接口
public interface Lock {void lock(); //主动锁住代码,会阻塞代码void lockInterruptibly() throws InterruptedException;//可以中断的锁boolean tryLock();//尝试获取锁,非阻塞锁boolean tryLock(long time, TimeUnit unit) throws InterruptedException; //尝试获取锁,指定一个等待时间void unlock();//释放锁Condition newCondition();//条件锁,可以分为读锁和写锁}
ReentrantLock的基本使用
public class TestLock {static int cnt =10;public static void main(String[] args) {Lock lock = new ReentrantLock();//创建一个任务Runnable runnable = ()->{try{lock.lock();//当前线程主动获取锁,如果其他线程已经获取了锁,那么会阻塞if(cnt>0){System.out.println("抢到资源"+cnt);cnt--;if(cnt==5){throw new RuntimeException();}}}finally { //无论是否抛出异常,都释放锁lock.unlock();// 释放锁}};//创建线程池执行任务ExecutorService service = Executors.newFixedThreadPool(5);for (int i=0;i<100;i++){service.execute(runnable);}service.shutdown();}}
tryLock的使用
尝试获取锁,如果成功则返回true,如果失败则返回false
Lock lock = new ReentrantLock();//创建一个任务Runnable runnable = ()->{if(lock.tryLock()){//尝试获取锁,不会阻塞线程try{if(cnt>0){System.out.println("抢到资源"+cnt);cnt--;}} finally { //无论是否抛出异常,都释放锁lock.unlock();// 释放锁}}else{System.out.println("没有抢到资源");}};
可以带时间参数的,在获取锁的时候,可以设置等待的时间
//创建一个任务Runnable runnable = () -> {try {//第一个参数是等待多长时间//第二个参数是等待时间的单位if (lock.tryLock(1,TimeUnit.MICROSECONDS)) {try {if (cnt > 0) {System.out.println("抢到资源" + cnt);cnt--;}}finally {lock.unlock();}} else {System.out.println("没有抢到资源");}} catch (InterruptedException e) {e.printStackTrace();}};
lockInterruptibly:可以中断锁
public static void main(String[] args) throws InterruptedException {Lock lock = new ReentrantLock();Runnable task = new Runnable() {@Overridepublic void run() {try {lock.lockInterruptibly();//可以中断try {String name = Thread.currentThread().getName();System.out.println(name + "执行任务");Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}finally {lock.unlock();}} catch (InterruptedException e) {// e.printStackTrace();System.out.println("不想再等待了");}}};Thread t1 = new Thread(task);Thread t2 = new Thread(task);t1.start();t2.start();Thread.sleep(1000);t2.interrupt(); //中断线程2的等待}
锁的分类
可重载锁:一个已经获得锁的线程,可以重复再加载锁
案例
class MyClass {public synchronized void method1() {method2();}public synchronized void method2() {}}
method1 和 methods都所有锁,一个线程加载了method1的锁之后,还可以继续加载method2的锁,如果不是可重载锁,则不能加载
- 可中断锁:A线程获取了锁,B线程在等待锁的过程中,可以直接中断等待
- 公平锁:使得多个线程在获取锁的机会上是比较公平
Lock lock = new ReentrantLock(true);//true是公平,默认false是不公平 读写锁:可以让数据的读取和修改的锁分开
- 当一个读取数据的线程获取锁的时候,另一个线程如果是读取数据,不需要锁
- 当一个读取数据的线程获取锁的时候,另一个线程如果是写如数据,需要锁
- 当一个写入数据的线程获取锁的时候,另一个线程无论读取还是写,都需要锁
- 基本规则
- 写-写:互斥,阻塞。读-写:互斥,读阻塞写、写阻塞读。读-读:不互斥、不阻塞。
读写分类
public class MyData2 {int value = 10;//创建读写锁ReentrantReadWriteLock lock = new ReentrantReadWriteLock();//分为写锁ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();//读锁ReentrantReadWriteLock.ReadLock readLock = lock.readLock();public int getValue() {readLock.lock();try{Thread.sleep(1000);return value;} catch (InterruptedException e) {e.printStackTrace();} finally {readLock.unlock();}return 0;}public void setValue(int value) {writeLock.lock();try{Thread.sleep(1000);this.value = value;} catch (InterruptedException e) {e.printStackTrace();} finally {writeLock.unlock();}}}
线程通信
在使用多线程的开发的时候,会发现线程运行顺序是无序的,无法对线程进行精确的控制,但是有些场景是需要对线程的顺序进行控制的,例如:生产者-消费-商品的问题
创建一个生产者-消费-商品的场景
创建商品
public class Product {int id;String brand;//标签,例如北京,长沙String name;//名称,例如烤鸭,臭豆腐}
创建生产者线程
//生产者线程public class ProductThread extends Thread{Product product;//操作的商品public ProductThread(Product product) {this.product = product;}@Overridepublic void run() {for (int i=1;i<=10;i++){product.setId(i);if(i%2==0){product.setBrand("长沙");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}product.setName("臭豆腐");}else{product.setBrand("北京");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}product.setName("烤鸭");}System.out.println("厨师加工了"+product.getId()+"-"+product.getBrand()+product.getName());}}}
创建一个消费者线程
public class ConsumerThread extends Thread{Product product;//操作的商品public ConsumerThread(Product product) {this.product = product;}@Overridepublic void run() {for (int i=1;i<=10;i++){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("顾客吃了:"+product.getId()+"-"+product.getBrand()+product.getName());}}}
通过synchronized + notify/wait来控制线程的顺序
给生产者添加 ```java public class ProductThread extends Thread{ Product product;//操作的商品
public ProductThread(Product product) { this.product = product; }
@Override public void run() { for (int i=1;i<=10;i++){
synchronized (product){product.setId(i);if(i%2==0){product.setBrand("长沙");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}product.setName("臭豆腐");}else{product.setBrand("北京");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}product.setName("烤鸭");}System.out.println("厨师加工了"+product.getId()+"-"+product.getBrand()+product.getName());product.notify();try {//放弃锁,进入到等待的状态product.wait(); //阻塞状态} catch (InterruptedException e) {e.printStackTrace();}}
} } }
- 给消费者添加```javaProduct product;//操作的商品public ConsumerThread(Product product) {this.product = product;}@Overridepublic void run() {for (int i=1;i<=10;i++){synchronized (product){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("顾客吃了:"+product.getId()+"-"+product.getBrand()+product.getName());product.notify();//告诉厨师可以继续开始做了try {product.wait();//客户进入等待状态} catch (InterruptedException e) {e.printStackTrace();}}}}}
