java基础_day16

一 线程同步(会)

1 临界资源问题

临界资源
在⼀个进程中, 多个线程之间是可以资源共享的。 如果在⼀个进程中的⼀个资源同时被多个线程访问, 这个资源就是⼀个临界资源。
如果多个线程同时访问临界资源, 会对这个资源的值造成影响。
​
临界资源问题出现的原因就是多个线程在同时访问⼀个资源, 因此解决⽅案也很简单, 就是不让多个线程同时访问即可。
在⼀个线程操作⼀个资源的时候, 对这个资源进⾏“上锁”, 被锁住的资源, 其他的线程⽆法访问。
​
类似多个⼈去公共卫⽣间, 每⼀个⼈在进到卫⽣间的时候, 都会从⾥⾯进⾏反锁。 此时, 其他⼈如果也需要使⽤这个卫⽣间, 就得在⻔外等待。

2 线程锁

线程锁, 就是⽤来“锁住”⼀个临界资源, 其他的线程⽆法访问。 在程序中, 可以分为对象锁和类锁
​
对作为锁的对象的要求: 
1.必须是对象
2.必须保证被多个线程共享
​
对象锁: 任何的普通对象或者this, 都可以被当做是⼀把锁来使⽤。 但是需要注意, 必须要保证不同的线程看到的锁, 需要是同⼀把锁才能⽣效。 如果不同的线程看到的锁对象是不⼀样的, 此时这把锁将没有任何意义。
​
注意: 不能直接使⽤匿名对象作为锁,因为这样每次都是在重新new,要保证锁是被⼤家共享.
​
类锁: 可以将⼀个类做成锁, 使⽤类.class (类的字节码⽂件对象)来作为锁。因为类的字节码⽂件的使⽤范围太⼤,所以⼀般我们不使⽤他作为锁,只有在静态⽅法中.

3 synchronized

如果在⼀个⽅法中, 所有的逻辑, 都需要放到同⼀个同步代码段中执⾏。 这样的⽅法, 可以直接做成同步⽅法。
​
1 同步⽅法
⾮静态同步⽅法,使⽤的对象锁(this)
是某个对象实例内,synchronized aMethod(){}可以防⽌多个线程同时访问这个对象的synchronized⽅法(如果⼀个对象有多个synchronized⽅法,只要⼀个线程访问了其中的⼀个synchronized⽅法,其它线程不能同时访问这个对象中任何⼀个synchronized⽅法)。这时,不同的对象实例的synchronized⽅法是不相⼲扰的。也就是说,其它线程照样可以同时访问相同类的另⼀个对象实例中的synchronized⽅法
​
静态同步⽅法,使⽤的类锁(当前类的.class⽂件)
是某个类的范围,synchronized static aStaticMethod{}防⽌多个线程同时访问
这个类中的synchronized static ⽅法。它可以对类的所有对象实例起作⽤。
​
静态同步函数在进内存的时候不会创建对象,但是存在其所属类的字节码⽂件对象,属于class类型的对象,所以静态同步函数的锁是其所属类的字节码⽂件对象
​

4 同步代码块

synchronized关键字⽤于⽅法中的某个区块中,表示只对这个区块的资源实⾏互斥访问。
​
同步代码段, 是来解决临界资源问题最常⻅的⽅式。 将⼀段代码放⼊到同步代码段中, 将这段代码上锁。第⼀个线程抢到了锁标记后, 可以对这个紧接资源上锁, 操作这个临界资源。 此时其他的线程再执⾏到synchronized的时候, 会进⼊到锁池, 直到持有锁的线程使⽤结束后, 对这个资源进⾏解锁。 此时, 处于锁池中的线程都可以抢这个锁标记, 哪⼀个线程抢到了, 就进⼊到就绪态, 没有抢到锁的线程, 依然处于锁池中。
​
同步代码块⼉的构成:
​
synchronized(锁(对象)){
 同步的代码
}

二 线程通信(会)

1 线程通信-打印机打印实例

实例:打印机打印
实现功能:不断输⼊不断输出
总结:需要给输⼊任务和输出任务同时加⼀把锁,保证两个任务之间是同步的给两个任务加⼀把锁:可以是desc或者Object.class
​
不建议使⽤Object.class:由于Object的使⽤范围太⼤,可能造成不必要的错误.desc合适,因为他只被当前的两个任务共享.
​
注意:对于当前的情况只给⼀个线程加锁,⽆法实现两个线程的同步
​
示例代码:
/*
分析:
两个线程:输⼊线程和输出线程
两个任务区:输⼊任务,输出任务
⼀份数据
*/
public class Demo2 {
 public static void main(String[] args) {
 //数据对象
 Desc desc = new Desc();
 //创建两个任务
 Input input = new Input(desc);
 Output output = new Output(desc);
 //创建线程
 Thread in = new Thread(input);
 Thread out = new Thread(output);
 //开启线程
 in.start();
 out.start();
 }
}
//数据类
class Desc {
 String name;
 String sex;
}
//输⼊任务
class Input implements Runnable{
 Desc desc;
 public Input(Desc desc) {
 this.desc = desc;
 }
 int i = 0;
 public void run() {
 while (true){
 synchronized (desc) {
 if (i == 0) {
 desc.name = "特没谱";
 desc.sex = "男";
 } else {
 desc.name = "安倍⼩三";
 desc.sex = "⼥";
 }
 i = (i + 1) % 2;
 }
 }
 }
}
//输出任务
class Output implements Runnable{
 Desc desc;
 public Output(Desc desc) {
 this.desc = desc;
 }
 public void run() {
 while (true){
 synchronized (desc) {
 System.out.println(desc.name + " " +
desc.sex);
 }
 }
 }
}

2 线程通信功能优化

实例:打印机打印
功能:对⼀次输⼊⼀次输出代码的改进
总结:进⾏了代码优化
⾯向对象的精髓:谁的活⼉谁⼲,不是你的活⼉不要⼲
将数据准备的活⼉从输⼊任务输出任务提出来,放⼊数据类Desc2
​
示例代码:
public class Demo4 {
 public static void main(String[] args) {
 //创建了⼀份数据
 Desc2 desc = new Desc2();
 //创建了输⼊任务和输出任务对象
 Input2 input = new Input2(desc);
 Output2 output = new Output2(desc);
 //创建输⼊线程和输出线程
 Thread in = new Thread(input);
 Thread out = new Thread(output);
 //开启线程
 in.start();
 out.start();
 }
}
//数据类
class Desc2{
 String name;
 String sex;
 boolean flag = false;//⽤于执⾏唤醒等待的切换
 //负责输⼊
 public void setData(String name,String sex) {
 if (flag == true) {//当flag值为true,就让当前的线程处于等待状
态
 try {
 wait();
 } catch (InterruptedException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }//当执⾏这⾏代码的时候,这⾥对应的是哪个线程,就操作的是哪个
线程
 }
 this.name = name;
 this.sex = sex;
 flag = !flag;
 notify();//唤醒的是通⼀把锁下的线程,因为现在只有⼀个输⼊线程,⼀
个输出线程.所以这⾥唤醒的是输出线程
 //当线程池中没有被当前的锁标记的线程可唤醒时,我们成为空唤醒,空唤
醒不影响程序的执⾏.
 }
 //负责输出
 public void getData() {
 if (flag == false) {//让输出线程等待
 try {
 wait();
 } catch (InterruptedException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 }
 System.out.println("姓名:"+name+" 性别:"+sex);
 flag = ! flag;
 notify();//唤醒的是输⼊线程
 }
}
//输⼊任务
class Input2 implements Runnable{
 Desc2 desc;
 public Input2(Desc2 desc) {
 this.desc = desc;
 }
 @Override
 public void run() {
 int i=0;
 while (true) {
 synchronized (desc) {
 if (i == 0) {
 desc.setData("超超", "男");
 }else {
 desc.setData("欣欣", "⼥");
 }
 i=(i+1)%2;
 }
 }
 }
}
//输出任务
class Output2 implements Runnable{
 Desc2 desc;
 public Output2(Desc2 desc) {
 this.desc = desc;
 }
 @Override
 public void run() {
 while (true){
 synchronized (desc) {
 desc.getData();
 }
 }
 }
}

三 ⽣产者消费者模式 (重点)

⽣产者消费者问题是研究多线程程序经典问题之⼀,它描述是有⼀块缓冲区作为仓库,⽣产者可以将产品放⼊仓库,消费者则可以从仓库中取⾛产品。在Java中⼀共有四种⽅法⽀持同步,其中前三个是同步⽅法,⼀个是管道⽅法。
(1)Object的wait() / notify()⽅法 (2)Lock和Condition的await() / signal()⽅法
(3)BlockingQueue阻塞队列⽅法 (4)PipedInputStream /PipedOutputStream
这⾥只对第⼀种第⼆种做讲解,其他的有兴趣的⼩伙伴可以⾃⼰进阶学习
咱们前⾯通过---打印机打印实例--的深⼊理解,最终的代码实现的就是⽣产者消费者模
式,对应的是单⽣产者单消费者.下⾯我们就使⽤标准模型代码理解⼀下.

1 单⽣产者消费者(会)

/*
* 单⽣产者单消费者
* 需要的线程:两个---⼀个⽣产线程⼀个消费线程
* 需要的任务:两个---⼀个⽣产任务⼀个消费任务
* 需要数据:⼀份---产品
*/
public class Demo5 {
 public static void main(String[] args) {
 //准备数据
 Product product = new Product();
 //准备任务
 Producer producer = new Producer(product);
 Consumer consumer = new Consumer(product);
 //准备⽣产线程消费线程
 Thread pro = new Thread(producer);
 Thread con = new Thread(consumer);
 //开启线程
 pro.start();
 con.start();
 }
}
//创建数据类--产品
class Product {
 String name;//名字
 double price;//价格
 int number;//数量
 //标识--控制唤醒等待
 boolean flag = false;
 //准备⽣产
 public synchronized void setProduce(String name,double
price){
if (flag == true){
 try {
 wait();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 this.name = name;
 this.price = price;
 System.out.println(Thread.currentThread().getName()+" 
⽣产了:"+this.name+" 价格:"+this.price+" 数量:"+this.number);
 number++;
 flag = !flag;
 notify();
 }
 //准备消费
 public synchronized void getConsume(){
 if (flag == false){
 try {
 wait();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 System.out.println(Thread.currentThread().getName()+" 
消费了:"+this.name+" 价格:"+this.price);
 flag = !flag;
 notify();
 }
}
//创建⽣产任务
class Producer implements Runnable{
 Product product;
 public Producer(Product product) {
 this.product = product;
 }
 @Override
 public void run() {
 while (true) {
 product.setProduce("bingbing", 10);
 }
 }
}
//创建消费任务
class Consumer implements Runnable{
 Product product;
 public Consumer(Product product) {
 this.product = product;
 }
 @Override
 public void run() {
 while (true) {
 product.getConsume();
 }
 }
}

2 多⽣产者多消费者(了解)

总结:将单⽣产者单消费者代码不做更改,直接再添加⼀个⽣产线程,⼀个消费线程,就形成了多⽣产者多消费者多消费者.
​
出现的错误1
错误描述:当有两个⽣产线程,两个消费线程同时存在的时候,有可能出现⽣产⼀次,消费多次或者⽣产多次消费⼀次的情况.
原因:当线程被重新唤醒之后,没有判断标记,直接执⾏了下⾯的代码
解决办法:将标记处的if改成while
​
出现的错误2
问题描述:继续运⾏程序,会出现死锁的情况(4个线程同时处于等待状态)
原因:唤醒的是本⽅的线程,最后导致所有的线程都处于等待状态.
解决办法:将notify改成notifyAll.保证将对⽅的线程唤醒
​
示例代码:
/*
* 多⽣产者多消费者
* 需要的线程:四个---两个⽣产线程两个消费线程
* 需要的任务:两个---⼀个⽣产任务⼀个消费任务
* 需要数据:⼀份---产品
*
* ⽣产任务与消费任务共⽤⼀个数据--产品类
* 要求:最终也要实现⼀次⽣产⼀次消费
*/
public class Demo6 {
 public static void main(String[] args) {
 //准备数据
 Product1 product = new Product1();
 //准备任务
 Producer1 producer = new Producer1(product);
 Consumer1 consumer = new Consumer1(product);
 //准备⽣产线程消费线程
 Thread pro1 = new Thread(producer);
 Thread pro2 = new Thread(producer);
 Thread con1 = new Thread(consumer);
 Thread con2 = new Thread(consumer);
 //开启线程
 pro1.start();
 con1.start();
 pro2.start();
 con2.start();
 }
}
//创建数据类--产品
class Product1 {
 String name;//名字
 double price;//价格
 int number;//数量
 //标识--控制唤醒等待
 boolean flag = false;
 //准备⽣产
 public synchronized void setProduce(String name,double
price){
 while (flag == true){
 try {
 wait();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 this.name = name;
 this.price = price;
 System.out.println(Thread.currentThread().getName()+" 
⽣产了:"+this.name+" 价格:"+this.price+" 数量:"+this.number);
 number++;
 flag = !flag;
 //notify();
 notifyAll();
 }
 //准备消费
 public synchronized void getConsume(){
 while (flag == false){
 try {
 wait();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 System.out.println(Thread.currentThread().getName()+" 
消费了:"+this.name+" 价格:"+this.price);
 flag = !flag;
 //notify();
 notifyAll();
 }
}
//创建⽣产任务
class Producer1 implements Runnable{
 Product1 product;
 public Producer1(Product1 product) {
 this.product = product;
 }
 @Override
 public void run() {
 while (true) {
 product.setProduce("bingbing", 10);
 }
 }
}
//创建消费任务
class Consumer1 implements Runnable{
 Product1 product;
 public Consumer1(Product1 product) {
 this.product = product;
 }
 @Override
 public void run() {
 while (true) {
 product.getConsume();
 }
 }
}

四 Lock锁

1为什么使⽤Lock锁?

在我们使⽤synchronized进⾏同步的时候,锁对象是Object类的对象,使⽤的wait,notify⽅法都来⾃Object类,但是咱们知道并不是所有的对象都会⽤到同步,所以这样⽤法不太合理,⽽且锁相关的功能很多,Lock就是将锁⾯向对象的结果.不光是将锁⾯向对象了,同时将wait,notify等⽅法也做了⾯相对象处理.形成了Condition接⼝.当我们想实现多⽣产者多消费者模式时,可以使⽤Lock实现同步,同时配合Condition接⼝实现唤醒等待.
​
 //创建锁对象
 Lock lock = new ReentrantLock();
 //⽤于⽣产任务的Condition
 Condition proCon = lock.newCondition();
 //⽤于消费任务的Condition
 Condition conCon = lock.newCondition();

2:⽐较synchronized和Lock

⽐较synchronized和Lock
1.synchronized:从jdk1.0就开始使⽤的同步⽅法-称为隐式同步
synchronized(锁对象){//获取锁 我们将锁还可以称为锁旗舰或者监听器
同步的代码
}//释放锁
2.Lock:从jdk1.5开始使⽤的同步⽅法-称为显示同步
原理:Lock本身是接⼝,要通过他的⼦类创建对象⼲活⼉
常⽤⼦类:ReentrantLock
使⽤过程:
⾸先调⽤lock()⽅法获取锁
进⾏同步的代码块⼉
使⽤unlock()⽅法释放锁
使⽤的场景:
当进⾏多⽣产者多消费者的功能时,使⽤Lock,其他的都使⽤synchronized
使⽤效率:Lock⾼于synchronized
⽐较Object多wait,notify和Condition的await,signal

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值