锁编程和线程通信
一个并发问题:,一百张票,很多人抢,可能会出现超卖问题,
一个并发问题:
一百张票,很多人抢,可能会出现超卖问题
public static int cnt = 100; public static void main(String[] args) throws InterruptedException { List list = new ArrayList<>(); for (int i=0;i<1000;i++){ Thread thread = new Thread(){ @Override public void run() { if(cnt>0){ String name = Thread.currentThread().getName(); System.out.println(name+"抢到了"+cnt+"张票"); cnt--; } } }; list.add(thread); } //启动 for (int i=0;i第一个问题:cnt>0作为条件,会出现并发问题;
第二个问题:cnt在--的时候,也可能导致并发问题
使用锁进行解决
public static int cnt = 100; //创建一把锁 static Object lock = new Object(); public static void main(String[] args) throws InterruptedException { List list = new ArrayList<>(); for (int i=0;i<1000;i++){ Thread thread = new Thread(){ @Override public 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 synchronized基本使用:锁对象
所有的锁必须要使用同一把锁对象才能有结果
以下代码是无法锁住线程方法的,如果多线程,下面代码,每个线程对象都会使用自己的独立锁
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的任务
public class TickTask implements Runnable { public int cnt = 100; @Override public 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去锁住一个对象,必须要判断这个对象是不是同一个对象,通过monitor对锁进行监控,实现争抢锁的实现逻辑
锁的信息是利用对象Head信息比较,其中MarkWord用于储存对象自身的运行是数据
每次有线程需要获取锁的时候,都需要将锁的信息提供给monitor进行查询
如果锁对象运行时数据发生变化的时候,可能会导致锁对象在比较时发生不一致的情况
结论,不要修改锁储存的数据,以避免锁失效
Lock手动锁的作用synchronized是自动锁,什么时候加锁,什么时候释放锁都是系统完成,如果使用不当,会出现死锁
创建一个男孩线程去找女孩约会
public class Boy extends Thread { @Override public 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{ @Override public 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() { @Override public 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和method2都所有锁,一个线程加载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; } @Override public 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; } @Override public 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来控制线程的顺序
-
给生产者添加
-
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(); } } } } }
给消费者添加
Product product;//操作的商品 public ConsumerThread(Product product) { this.product = product; } @Override public 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(); } } } } }
-
- 可以让数据读取和修改分开的锁
上一篇:网络通信编程
下一篇:手机怎么录屏?安卓苹果录屏教程