线程同步

线程同步

多个线程操作同一个资源

并发: 同一个对象被多个线程同时操作。

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用。

队列和锁保证线程同步的安全性

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放资源即可,存在的问题:

一个线程持有锁会导致其他所有需要此锁的线程挂起;

在多个线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;

如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。

 

同步方法

由于我们可以通过private关键字来保护数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种方法:synchronized方法和synchronized块

public synchronized void method(int args){}

synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。

缺陷:若将一个大的方法申明为synhronized将会影响效率

三大线程不安全案例

一、
   package com.wang.ssynchronized;

//不安全的买票
public class UnsafeBuyTicket {
   public static void main(String[] args) {
       BuyTicket station=new BuyTicket();
       new Thread(station,"a").start();
       new Thread(station,"b").start();
       new Thread(station,"c").start();
  }
}
class BuyTicket implements Runnable{
   private int ticketNumbers=10;
   boolean flag=true;//外部停止方式
   @Override
   public void run() {
       //买票
       while (flag){
           try {
               buy();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
  }

   private void buy() throws InterruptedException {
       //判断是否有票
       if(ticketNumbers<=0){
           flag=false;
           return;
      }
       //模拟延时
       Thread.sleep(100);
       //买票
       System.out.println(Thread.currentThread().getName()+"拿到"+ticketNumbers--);
  }
}

二、

package com.wang.ssynchronized;
import com.wang.duixiang.A;

//不安全的取钱
public class UnsafeBank {
   public static void main(String[] args) {
       Account account=new Account(2000,"wangyingjing");

       Drawing you=new Drawing(account,500,"你");
       Drawing girlfriend=new Drawing(account,2000,"girlfriend");

       you.start();
       girlfriend.start();
  }
}

//账户
class Account{
   public int money;//余额
   String name;//卡名

   public Account(int money, String name) {
       this.money = money;
       this.name = name;
  }
}

//银行:模拟取款
class Drawing extends Thread {//不涉及多个线程操作同一对象,所以用继承Thread
   Account account;//账户
   int drawingMoney;//取了多少钱
   int nowMoney;//现有多少

   public Drawing(Account account, int drawingMoney, String name) {
       super(name);
       this.account = account;
       this.drawingMoney = drawingMoney;

  }

   //取钱
   @Override
   public void run() {
       //首先判断是否有钱

           if ((account.money - drawingMoney) < 0) {
               System.out.println(Thread.currentThread().getName() + "钱不够,取不了");
               return;
          }
           //卡内余额
           account.money = account.money - drawingMoney;
           //手里的钱
           nowMoney = nowMoney + drawingMoney;

           System.out.println(account.name + "余额为:" + account.money);
           //Thread.currentThread().getName()=this.getName() 因为继承了Thread,这里的this指代Thread
           System.out.println(this.getName() + "手里的钱" + nowMoney);
      }
  }

三、
package com.wang.ssynchronized;
import java.util.ArrayList;
import java.util.List;
//线程不安全的集合
public class UnsafeList {
   public static void main(String[] args) {
       List<String> list=new ArrayList<>();
       for (int i = 0; i < 10000; i++) {
           new Thread(()->{
               list.add(Thread.currentThread().getName());
          }).start();
      }
       System.out.println(list.size());
  }
}
//有可能两个线程同一瞬间操作了同一个位置,把两个数据添加到了同一个位置,数据被覆盖

锁方法

定义buy方法时加synchronized

package com.wang.ssynchronized;

public class SafeBuyTicket {
//安全的买票
       public static void main(String[] args) {
           com.wang.ssynchronized.BuyTicket station=new com.wang.ssynchronized.BuyTicket();
           new Thread(station,"a").start();
           new Thread(station,"b").start();
           new Thread(station,"c").start();
      }
   class BuyTicket implements Runnable{
       private int ticketNumbers=10;
       boolean flag=true;//外部停止方式
       @Override
       public void run() {
           //买票
           while (flag){
               try {
                   buy();
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
      }
       
       //synchronized 同步方法,锁的是this
       
       private synchronized void buy() throws InterruptedException {
           //判断是否有票
           if(ticketNumbers<=0){
               flag=false;
               return;
          }
           //模拟延时
           Thread.sleep(100);
           //买票
           System.out.println(Thread.currentThread().getName()+"拿到"+ticketNumbers--);
      }
  }
}

同步块:synchronized(Obj){}

Obj称之为同步监视器

      Obj可以是任何对象,但是推荐使用共同资源作为同步监视器

      同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class

      同步监视器的执行过程:1、第一个线程访问,锁定同步监视器,执行其中代码

                                                2、第二个线程访问,发现同步监视器被锁定,无法访问

                                                3、第一个线程访问完毕,解锁同步监视器

                                                4、第二个线程访问,发现同步监视器没有锁,然后锁定并访问

对要进行变化的对象上锁,需要增删改的对象

package com.wang.ssynchronized;
//安全的取钱
public class SafeBank {
   public static void main(String[] args) {
       Account account=new Account(100,"wang");

       Drawing you=new Drawing(account,50,"你");
       Drawing friend=new Drawing(account,10,"friend");

       you.start();
       friend.start();
  }
}
//账户
class Account{
   public int money;//余额
   String name;//卡名

   public Account(int money, String name) {
       this.money = money;
       this.name = name;
  }
}

//银行:模拟取款
class Drawing extends Thread{//不涉及多个线程操作同一对象,所以用继承Thread
   Account account;//账户
   int drawingMoney;//取了多少钱
   int nowMoney;//现有多少

   public Drawing(Account account,int drawingMoney,String name){
       super(name);
       this.account=account;
       this.drawingMoney=drawingMoney;

  }
   //取钱
   @Override
   public void run() {

    //锁的对象是变化的量,需要增删改的对象

       synchronized (account){

           //首先判断是否有钱
           if ((account.money-drawingMoney)<0){
               System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
               return;
          }
           //卡内余额
           account.money=account.money-drawingMoney;
           //手里的钱
           nowMoney= nowMoney+drawingMoney;

           System.out.println(account.name+"余额为:"+account.money);
           //Thread.currentThread().getName()=this.getName() 因为继承了Thread,这里的this指代Thread
           System.out.println(this.getName()+"手里的钱"+nowMoney);
      }
      }
  }
package com.wang.ssynchronized;
//线程安全的集合
import java.util.ArrayList;
import java.util.List;

public class SafeList {
   public static void main(String[] args) {
       List<String> list=new ArrayList<>();

       for (int i = 0; i < 10000; i++) {
           new Thread(() -> {
               
            //synchronized块  
               
               synchronized (list) {
                   
                   list.add(Thread.currentThread().getName());
              }
          }).start();
      }
       try {
           Thread.sleep(1000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println(list.size());

  }
}

死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象的锁” 时,就可能会发生“死锁” 的问题

package com.wang.ssynchronized;

//死锁 多个线程互相持有对方需要的资源,形成僵持
public class DeadLock {
   public static void main(String[] args) {
       Makeup girl1=new Makeup(0,"wang");
       Makeup girl2=new Makeup(1,"ying");

       girl1.start();
       girl2.start();

  }
}
//口红
class Lipstick{

}

//镜子
class Mirror{

}

//化妆
class Makeup extends Thread{

   //需要的资源只有一份,用static来保证只有一份
   static Lipstick lipstick=new Lipstick();
   static Mirror mirror=new Mirror();

   int choice;//选择
   String girlName;//使用化妆品的人

   public Makeup(int choice,String girlName){
       this.choice=choice;
       this.girlName=girlName;
  }

   @Override
   public void run() {
       try {
           makeup();
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
  }
   //化妆,互相持有对方的锁,就是 需要拿到对方的资源
   private void makeup() throws InterruptedException {
       if (choice==0){
           synchronized(lipstick){//获得口红的锁
               System.out.println(this.girlName+"获得口红的锁");

               Thread.sleep(1000);

               synchronized (mirror){//一秒钟后想获得镜子
                   System.out.println(this.girlName+"获得镜子的锁");
              }
          }
      }else {
           synchronized (mirror){
               System.out.println(this.girlName+"获得镜子的锁");

               Thread.sleep(2000);

               synchronized (lipstick){
                   System.out.println(this.girlName+"获得口红的锁");
              }
          }
      }
  }
}

程序卡死,无法执行完毕

解决方法

package com.wang.ssynchronized;

public class SolveDeadLock {
   //死锁 多个线程互相持有对方需要的资源,形成僵持
   public static void main(String[] args) {
       Makeup1 girl1 = new Makeup1(0, "wang");
       Makeup1 girl2 = new Makeup1(1, "ying");

       girl1.start();
       girl2.start();

  }
}
   //口红
   class Lipstick1{

  }

   //镜子
   class Mirror1{

  }

   //化妆
   class Makeup1 extends Thread{

       //需要的资源只有一份,用static来保证只有一份
       static Lipstick1 lipstick=new Lipstick1();
       static Mirror1 mirror=new Mirror1();

       int choice;//选择
       String girlName;//使用化妆品的人

       public Makeup1(int choice,String girlName){
           this.choice=choice;
           this.girlName=girlName;
      }

       @Override
       public void run() {
           try {
               makeup();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
       //化妆,互相持有对方的锁,就是 需要拿到对方的资源
       private void makeup() throws InterruptedException {
           if (choice==0){
               synchronized(lipstick){//获得口红的锁
                   System.out.println(this.girlName+"获得口红的锁");

                   Thread.sleep(1000);

              }
               
               synchronized (mirror){//一秒钟后想获得镜子
                   System.out.println(this.girlName+"获得镜子的锁");
              }
               
          }else {
               synchronized (mirror){
                   System.out.println(this.girlName+"获得镜子的锁");

                   Thread.sleep(2000);
                   
              }
               
               synchronized (lipstick){
                   System.out.println(this.girlName+"获得口红的锁");
              }
               
          }
      }
  }

把锁拿出来,不要抱对方的锁,死锁问题就可以解决

产生死锁的四个必要条件

①互斥条件:一个资源每次只能被一个进程使用

②请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放

③不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺

④循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

Lock(锁)

从JDK5.0开始,Java提供了更强大的线程同步机制---通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

ReentrantLock(可重入锁)类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁。

package com.wang.ssynchronized;

import java.util.concurrent.locks.ReentrantLock;

//测试Lock锁
public class TestLock {
   public static void main(String[] args) {
       TestLock2 testLock2=new TestLock2();

       new Thread(testLock2,"wang").start();
       new Thread(testLock2,"ying").start();
       new Thread(testLock2,"jing").start();
  }
}
class TestLock2 implements Runnable{

   private int ticketNumbers=10;

   //定义可重入锁
   private final ReentrantLock lock=new ReentrantLock();

   @Override
   public void run() {
       while (true){
           try {
               
               lock.lock();//加锁

               if (ticketNumbers<=0){
                   break;
              }
               try {
                   Thread.sleep(100);
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
               System.out.println(Thread.currentThread().getName()+"拿到了"+ticketNumbers--);

          }finally {

               lock.unlock();//解锁

          }


      }
  }
}

synchronized与Lock的对比

①Lock是显示锁(手动开启和关闭锁,别忘记关闭锁),synchronized的隐式锁,出了作用域自动释放。

②Lock只有代码块锁,synchronized有代码块锁和方法锁。

③使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)。

④优先使用顺序:Lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)。

posted @ 2019-12-03 21:52  王迎婧  阅读(442)  评论(0编辑  收藏  举报