线程安全

本文深入探讨了Java中的线程安全问题,包括JVM内存模型、synchronized关键字的使用、锁重入、异常释放锁、同步与异步执行、并发脏读以及死锁。通过示例代码展示了如何使用synchronized解决并发问题,解释了wait/notify通信机制以及线程间的通讯。此外,还讨论了守护线程、用户线程以及上下文切换对系统性能的影响。
摘要由CSDN通过智能技术生成

线程安全问题

线程工作的时候将数据从主存区复制到工作内存区域,处理结束后将数据写回,多个线程访问统一资源是可能产生线程安全问题

image-20210224143602255

JVM内存模型

可见性:一个线程操作主存区的数据对于其他的线程是不可见的

原子性:一个线程对主存区数据的操作过程是不可分割的,不能被其他线程打断

例子

两个线程同时修改密码,输出的结果不同

class User {
   private String name;
   private String pass;

   public User(String name, String pass) {
      this.name = name;
      this.pass = pass;
   }

   public synchronized void set(String name, String pass) {
      this.name = name;
      try {
         Thread.sleep(5000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      this.pass = pass;
      System.out.println(Thread.currentThread().getName() + "-name=" + this.name + "pass=" + this.pass);
   }
}

class UserServlet {

   private User user;

   public UserServlet() {
      user = new User("zs", "123456");
   }

   public void setPass(String name, String pass) {
      user.set(name, pass);
   }
}

public class DemoThread00 {
   
   public static void main(String[] args) {

      final UserServlet us = new UserServlet();
      
      new Thread(new Runnable() {
         @Override
         public void run() {
            us.setPass("ls", "777777");
         }
      }).start();
      
      new Thread(new Runnable() {
         @Override
         public void run() {
            us.setPass("ww", "888888");
         }
      }).start();
   }
}

产生了姓名被覆盖的问题

Thread-0-name=wwpass=777777
Thread-1-name=wwpass=888888

syncronized关键字

**缺点:**会造成锁竞争问题

  1. 线程安全的概念:当多个线程访问某一个类、对象或方法时,这个类、对象或方法都能表现出与单线程执行时一致的行为,那么这个类、对象或方法就是线程安全的。

  2. 线程安全问题都是由全局变量及静态变量引起的。

  3. 若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程 安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

上面的例子中set方法加入syncronized关键字可以解决问题

Thread-0-name=lspass=777777
Thread-1-name=wwpass=888888

Synchronized的作用是加锁,所有的synchronized方法都会顺序执行,(这里只占用CPU的顺序)。

Synchronized方法执行方式:

  • 首先尝试获得锁
  • 如果获得锁,则执行Synchronized的方法体内容。
  • 如果无法获得锁则等待,并且不断的尝试去获得锁,一旦锁被释放,则多个线程会同时去尝试获得锁,造成锁竞争问题。

锁竞争问题,在高并发、线程数量高时会引起CPU占用居高不下,或者直接宕机

例子

public class DemoThread02{
   
   private /*static*/ int count = 0;
   
   //如果是static变量会怎样?
   public /*synchronized static*/ void add() {
      count++;
      try {
         Thread.sleep(1000);
      } catch (InterruptedException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName()+">count="+count);
   }
   
   public static void main(String[] args) {
      
      /**
          Synchronized是获得对象锁,如果作用在static类型上,则升级为类锁
       */
      
      //内部类无法访问非final对象
      /**
       * 为什么要用final修饰:
       * 
       * 内部类对象的生命周期会超过局部变量的生命周期。
       * 局部变量的生命周期:当该方法被调用时,该方法中的局部变量在栈中被创建,当方法调用结束时,退栈,这些局部变量全部死亡。
       * 而内部类对象生命周期与其它类一样:自创建一个匿名内部类对象,系统为该对象分配内存,直到没有引用变量指向分配给该对象的内存,它才会死亡(被JVM垃圾回收)。
       * 所以完全可能出现的一种情况是:成员方法已调用结束,局部变量已死亡,但匿名内部类的对象仍然活着。
       * 
       * 匿名内部类对象可以访问同一个方法中被定义为final类型的局部变量。
       * 定义为final后,编译程序的实现方法:对于匿名内部类对象要访问的所有final类型局部变量,都拷贝成为该对象中的一个数据成员。
       * 这样,即使栈中局部变量已死亡,但被定义为final类型的局部变量的值永远不变,因而匿名内部类对象在局部变量死亡后,照样可以访问final类型的局部变量,因为它自己拷贝了一份,且与原局部变量的值始终一致。
       */
      final DemoThread02 thread1 = new DemoThread02();
      final DemoThread02 thread2 = new DemoThread02();
      Thread t1 = new Thread(new Runnable(){
         @Override
         public void run() {
            thread1.add(); //1.同一个对象、同一把锁
         }
      }, "thread1");
      
      Thread t2 = new Thread(new Runnable(){
         @Override
         public void run() {
            thread1.add();   //1、同一个对象、同一把锁
            //thread2.add();
         }
      }, "thread2");
      
      t1.start();
      t2.start();
   }
}

同一个对象、同一把锁,都运行thread1,未加上关键字Synchronized,没有顺序执行(期望输出结果是先1后2)

thread1>count=2
thread2>count=2

加上方法关键字Synchronized

thread1>count=1
thread2>count=2

**注意:**如果没有使用同一个对象,那么起不到效果,不同对象的锁方法并不互斥,如t1线程执行o1对象实例的锁方法,t2线程执行o2对象实例的锁方法,两者不互斥

全局变量加上static,还是执行顺序同步的

Synchronized作用在非静态方法上代表的对象锁,一个对象一个锁,多个对象之间不会发生锁竞争。Synchronized作用在静态方法上则升级为类锁,所有对象对象共享一把锁,存在锁竞争。

将t2执行thread2,结果是顺序执行的

thread1>count=1
thread2>count=2

同步和异步

同步:必须等待方法执行完毕,才能向下执行,共享资源访问的时候,为了保证线程安全,必须同步。

异步:不用等待其他方法执行完毕,即可立即执行,例如Ajax异步。

对象锁只针对synchronized修饰的方法生效、 对象中的所有synchronized方法都会同步执行、而**非 synchronized方法异步执行 **

避免误区:类中有两个synchronized方法,两个线程分别调用两个方法,相互之间也需要竞争锁, 因为两个方法从属于一个对象,而我们是在对象上加锁

public class DemoThread03{
   
   //同步执行
   public synchronized void print1() {
      System.out.println(Thread.currentThread().getName()+">hello!");
      try {
         Thread.sleep(3000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
   
   //异步执行
   public void print2() {
      System.out.println(Thread.currentThread().getName()+">hello!");
   }
   
   public static void main(String[] args) {
      
      final DemoThread03 thread = new DemoThread03();
      Thread t1 = new Thread(new Runnable(){
         @Override
         public void run() {
            thread.print1();
         }
      }, "thread1");
      
      Thread t2 = new Thread(new Runnable(){
         @Override
         public void run() {
            thread.print2();
         }
      }, "thread2");
      
      t1.start();
      t2.start();
   }
}

print2未加关键字则同时输出,加关键字则顺序执行一个个输出

thread1>hello!
thread2>hello!

并发脏读问题

public class DemoThread04 {

   private String name = "张三";
   private String address = "大兴";

   public synchronized void setVal(String name, String address) {
      this.name = name;
      try {
         Thread.sleep(2000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      this.address = address;
      System.out.println("setValue final result:username = " + name + " , address = " + address);
   }

   public /*synchronized*/ void getVal() {
      System.out.println("getValue:username = " + this.name + " , address = " + this.address);
   }

   public static void main(String[] args) throws Exception {

      final DemoThread04 dr = new DemoThread04();
      Thread t1 = new Thread(new Runnable() {
         @Override
         public void run() {
            dr.setVal("李四", "昌平");
         }
      });
      t1.start();
      
      Thread.sleep(1000);

      dr.getVal();
   }

}

get方法未加锁,读到脏数据

getValue:username = 李四 , address = 大兴
setValue final result:username = 李四 , address = 昌平

多个线程访问同一个资源,在一个线程修改数据的过程中,有另外的线程来读取数据,就会引起脏读的产生。

为了避免脏读我们一定要保证数据修改操作的原子性**(加锁)**、并且对读取操作也要进行同步控制

get方法加锁后

setValue final result:username = 李四 , address = 昌平
getValue:username = 李四 , address = 昌平

Oracle如何处理脏读

一个数据库有5千万条数据
线程A在9:00向数据库发起查询操作,要通过全表扫描来查询最后一条数据,运行耗时需要10分钟。
线程B在9:05项数据库发起更新操作,对最后一条数据进行了更新。
问题1:线程A得到的数据,是修改前的数据,还是修改后的数据?
答:获取修改前的数据,因为Oracle数据修改时,都会先将原数据放入到undo空间中,undo空间
可以放入多个版本的快照。
问题2:如果有多个线程对数据进行了修改,那么线程A是否还能够获取到修改前的数据?
答:可能得到也可能得不到,需要根据undo空间的大小来确定,多次修改如果覆盖了最初的undo
数据,则会返回snapshot too old异常。

synchronized锁重入

同一个线程得到了一个对象的锁之后,再次请求此对象时可以再次获得该对象的锁。

同一个对象内的多个synchronized方法可以锁重入(一个同步方法中调用了另外一个同步方法)。

public class DemoThread05{
   
   public synchronized void run1(){
      System.out.println(Thread.currentThread().getName()+">run1...");
      //调用同类中的synchronized方法不会引起死锁
      run2();
   }
   
   public synchronized void run2(){
      System.out.println(Thread.currentThread().getName()+">run2...");
   }
   
   public static void main(String[] args) {
      final DemoThread05 demoThread05 = new DemoThread05();
      Thread thread = new Thread(new Runnable() {
         @Override
         public void run() {
            demoThread05.run1();
         }
      });
      thread.start();
   }
}

执行结果如下,可以执行run2方法

Thread-0>run1...
Thread-0>run2...

父子类可以锁重入,进行sleep并没有释放锁,程序正常执行(方法中调用另一个方法)

public class DemoThread06 {
   public static void main(String[] args) {
      Thread t1 = new Thread(new Runnable() {
         @Override
         public void run() {
            Child sub = new Child();
            sub.runChild();
         }
      });
      t1.start();
   }
}

class Parent {
   public int i = 10;
   public synchronized void runParent() {
      try {
         i--;
         System.out.println("Parent>>>>i=" + i);
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
}

class Child extends Parent {
   
   public synchronized void runChild() {
      try {
         while (i > 0) {
            i--;
            System.out.println("Child>>>>i=" + i);
            Thread.sleep(100);
            //调用父类中的synchronized方法不会引起死锁
            this.runParent();
         }
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
}
Child>>>>i=9
Parent>>>>i=8
Child>>>>i=7
Parent>>>>i=6
Child>>>>i=5
Parent>>>>i=4
Child>>>>i=3
Parent>>>>i=2
Child>>>>i=1
Parent>>>>i=0

抛出异常释放锁

一个线程在获得锁之后执行操作,发生错误抛出异常,则自动释放锁

public class DemoThread07 {
   
   private int i = 0;
   
   public synchronized void run(){
      while(true){
         i++;
         System.out.println(Thread.currentThread().getName()+"-run>i="+i);
         
         try {
            Thread.sleep(1000);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         
//       int k = 10/0;
         
         if (i == 10) {
            throw new RuntimeException();
         }
      }
   }
   
   public synchronized void get(){
      System.out.println(Thread.currentThread().getName()+"-get>i="+i);
   }
   
   public static void main(String[] args) throws InterruptedException {
      final DemoThread07 demoThread07 = new DemoThread07();
      new Thread(new Runnable() {
         @Override
         public void run() {
            demoThread07.run();
         }
      },"t1").start();
      
      //保证t1线程先执行
      Thread.sleep(1000);
      
      new Thread(new Runnable() {
         @Override
         public void run() {
            demoThread07.get();
         }
      },"t2").start();
   }
}

在run方法抛出异常后释放了锁,get获取到锁才能执行

t1-run>i=1
t1-run>i=2
t1-run>i=3
t1-run>i=4
t1-run>i=5
t1-run>i=6
t1-run>i=7
t1-run>i=8
t1-run>i=9
t1-run>i=10
t2-get>i=10
Exception in thread "t1" java.lang.RuntimeException
	at com.mimaxueyuan.demo.base.DemoThread07.run(DemoThread07.java:41)
	at com.mimaxueyuan.demo.base.DemoThread07$1.run(DemoThread07.java:55)
	at java.lang.Thread.run(Thread.java:748)

添加代码int k = 10/0; 提前释放锁

Exception in thread "t1" java.lang.ArithmeticException: / by zero
	at com.mimaxueyuan.demo.base.DemoThread07.run(DemoThread07.java:38)
	at com.mimaxueyuan.demo.base.DemoThread07$1.run(DemoThread07.java:55)
	at java.lang.Thread.run(Thread.java:748)

总结:

  1. 可以利用抛出异常,主动释放锁

  2. 程序异常时防止资源被死锁、无法释放

  3. 异常释放锁可能导致数据不一致

synchronized代码块

以上学习的都是synchronized作用在方法上

synchronized代码块:

可以达到更细粒度的控制

  • 当前对象锁

  • 类锁

  • 任意对象锁

例子

public class DemoThread08 {

   public void run1() {
      synchronized (this) {
         try {
            System.out.println(Thread.currentThread().getName()+">当前对象锁..");
            Thread.sleep(2000);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }

   public void run2() {
      synchronized (DemoThread08.class) {
         try {
            System.out.println(Thread.currentThread().getName()+">类锁..");
            Thread.sleep(2000);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }

   private Object objectLock = new Object();
   public void run3() {
      synchronized (objectLock) {
         try {
            System.out.println(Thread.currentThread().getName()+">任意对象锁..");
            Thread.sleep(2000);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }

   //测试方法
   public static void test(final int type){
      if(type==1){
         System.out.println("当前对象锁测试...");
      }else if(type==2){
         System.out.println("类锁测试...");
      }else{
         System.out.println("任意对象锁测试...");
      }
      final DemoThread08 demo1 = new DemoThread08();
      final DemoThread08 demo2 = new DemoThread08();
      Thread t1 = new Thread(new Runnable() {
         @Override
         public void run() {
            if(type==1){
               demo1.run1();  
            }else if(type==2){
               demo1.run2();
            }else{
               demo1.run3();
            }
         }
      },"t1");
      Thread t2 = new Thread(new Runnable() {
         @Override
         public void run() {
            if(type==1){
               demo1.run1();  
            }else if(type==2){
               demo2.run2();
            }else{
               demo1.run3();
            }
         }
      },"t2");
      t1.start();
      t2.start();
   }
   
   public static void main(String[] args) {
      test(1);
//    test(2);
//    test(3);
      /*
      final DemoThread08 demo1 = new DemoThread08();
      final DemoThread08 demo2 = new DemoThread08();
      Thread t1 = new Thread(new Runnable() {
         @Override
         public void run() {
            demo1.run2();  
         }
      },"t1");
      t1.start();
      
      //保证先去运行demo1的run2方法
      try {
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      
      Thread t2 = new Thread(new Runnable() {
         @Override
         public void run() {
            demo2.run1();  
         }
      },"t2");
      t2.start();

       */
   }
}

执行test(1)

当前对象锁测试...
t2>当前对象锁..
t1>当前对象锁..

执行test(2)

类锁测试...
t1>类锁..
t2>类锁..

执行test(3)

任意对象锁测试...
t1>任意对象锁..
t2>任意对象锁..

执行main下方的代码,几乎同时输出,说明同类型锁之间互斥,不同类型的锁之间互不干扰

t1>类锁..
t2>当前对象锁..

不要在线程内修改对象锁的引用

不要在线程内修改对象锁的引用,否则会造成锁失效问题

public class DemoThread09 {

   private String lock = "lock handler";
   
   private void method(){
      synchronized (lock) {
         try {
            System.out.println("当前线程 : "  + Thread.currentThread().getName() + "开始");
            //锁的引用被改变,则其他线程可获得锁,导致并发问题
            lock = "change lock handler";
            Thread.sleep(2000);
            System.out.println("当前线程 : "  + Thread.currentThread().getName() + "结束");
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }
   
   public static void main(String[] args) {
   
      final DemoThread09 changeLock = new DemoThread09();
      Thread t1 = new Thread(new Runnable() {
         @Override
         public void run() {
            changeLock.method();
         }
      },"t1");
      Thread t2 = new Thread(new Runnable() {
         @Override
         public void run() {
            changeLock.method();
         }
      },"t2");
      t1.start();
      
      try {
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      //由于锁的引用被改变,所以t2线程也进入到method方法内执行。
      t2.start();
   }
   
}

由于中间改变锁的引用,t2也能获取到锁

当前线程 : t1开始
当前线程 : t2开始
当前线程 : t1结束
当前线程 : t2结束

在线程中修改了锁对象的属性,而不修改引用则不会引起锁失效、不会产生线程安全问题。

class Person {
   private String name;
   private int age;

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   public int getAge() {
      return age;
   }

   public void setAge(int age) {
      this.age = age;
   }

   @Override
   public String toString() {
      return "DemoThread10 [name=" + name + ", age=" + age + "]";
   }
}

public class DemoThread10 {

   private Person person = new Person();

   public void changeUser(String name, int age) {
      synchronized (person) {
         System.out.println("线程" + Thread.currentThread().getName() + "开始" + person);
         // 打开注释:引起对象引用发生变化,t1、t2线程同时进入方法、导致线程安全问题
         //person = new Person();
         //如果只修改了值,不修改引用,锁还是生效的状态
         person.setAge(age);
         person.setName(name);
         System.out.println("线程" + Thread.currentThread().getName() + "修改为" + person);
         try {
            Thread.sleep(2000);
         } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
         }
         System.out.println("线程" + Thread.currentThread().getName() + "结束" + person);
      }
   }

   public static void main(String[] args) {
      final DemoThread10 thread10 = new DemoThread10();
      new Thread(new Runnable() {
         @Override
         public void run() {
            thread10.changeUser("小白", 99);
         }
      }, "t1").start();

      new Thread(new Runnable() {
         @Override
         public void run() {
            thread10.changeUser("小黑", 100);
         }
      }, "t2").start();
   }
}

执行结果

线程t1开始DemoThread10 [name=null, age=0]
线程t1修改为DemoThread10 [name=小白, age=99]
线程t1结束DemoThread10 [name=小白, age=99]
线程t2开始DemoThread10 [name=小白, age=99]
线程t2修改为DemoThread10 [name=小黑, age=100]
线程t2结束DemoThread10 [name=小黑, age=100]

如果执行过程中执行person = new Person(); 注释set操作

线程t1开始DemoThread10 [name=null, age=0]
线程t1修改为DemoThread10 [name=null, age=0]
线程t1结束DemoThread10 [name=null, age=0]
线程t2开始DemoThread10 [name=null, age=0]
线程t2修改为DemoThread10 [name=null, age=0]
线程t2结束DemoThread10 [name=null, age=0]

线程A修改了对象锁的引用,则线程B实际的到了新的对象锁,而不是锁被释放了,因此引发了线程安全问题。

并发与死锁

是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

public class DemoThread12 {
   
   private Object lock1 = new Object();
   private Object lock2 = new Object();
   
   public void execute1() {
      synchronized (lock1) {
         System.out.println("线程" + Thread.currentThread().getName() + "获得lock1执行execute1开始");
         try {
            Thread.sleep(2000);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         synchronized (lock2) {
            System.out.println("线程" + Thread.currentThread().getName() + "获得lock2执行execute1开始");
         }
      }
   }

   public void execute2() {
      synchronized (lock2) {
         System.out.println("线程" + Thread.currentThread().getName() + "获得lock2执行execute2开始");
         try {
            Thread.sleep(2000);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         synchronized (lock1) {
            System.out.println("线程" + Thread.currentThread().getName() + "获得lock1执行execute2开始");
         }
      }
   }
   
   public static void main(String[] args) {
      final DemoThread12 demo = new DemoThread12();
      new Thread(new Runnable() {
         @Override
         public void run() {
            demo.execute1();
         }
      }, "t1").start();

      new Thread(new Runnable() {
         @Override
         public void run() {
            demo.execute2();
         }
      }, "t2").start();
   }
}

执行结果,输出以下内容,并且程序一直在执行,两个线程各自占据了锁lock1和锁lock2,都在等对方释放资源,造成死锁

线程t1获得lock1执行execute1开始
线程t2获得lock2执行execute2开始

线程之间的通讯

  • 每个线程都是独立运行的个体,线程通讯能让多个线程之间协同工作

  • Object类中的wait/notify方法可以实现线程间通讯

  • Wait/notify必须与synchronized一同使用

  • Wait释放锁、notify不释放锁

每个线程访问一个 volatile 作用域时会在继续执行之前读取它的当前值,而不是(可能)使用一个缓存的值。

不使用wait/notify的通信

public class DemoThread17{
   
   private volatile List<String> list = new ArrayList<String>();
   private volatile boolean canGet = false;
   
   public void put(){
      for(int i=0;i<10;i++){
         try {
            Thread.sleep(1000);
         } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
         }
         list.add("A");
         System.out.println("线程"+Thread.currentThread().getName()+"添加第"+i+"个元素");
         if(i==5){
            //循环到第次则通知其他线程开始获取数据进行处理
            canGet = true;
            System.out.println("线程"+Thread.currentThread().getName()+"发出通知");
         }
      }
   }
   
   public void get(){
      while(true){
         if(canGet){
            for(String s:list){
               System.out.println("线程"+Thread.currentThread().getName()+"获取元素:"+s);
            }
            break;
         }
      }
   }
   
   public static void main(String[] args) {
      
      final DemoThread17 demo = new DemoThread17();
      
      new Thread(new Runnable() {
         @Override
         public void run() {
            demo.put();
         }
      },"t1").start();;
      
      new Thread(new Runnable() {
         @Override
         public void run() {
            demo.get();
         }
      },"t2").start();
      
   }

   
}

结果

线程t1添加第0个元素
线程t1添加第1个元素
线程t1添加第2个元素
线程t1添加第3个元素
线程t1添加第4个元素
线程t1添加第5个元素
线程t1发出通知
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t1添加第6个元素
线程t1添加第7个元素
线程t1添加第8个元素
线程t1添加第9个元素

使用wait/notify的通信

public class DemoThread18{
   
   //原子类
   private volatile List<String> list = new ArrayList<String>();
   private Object lock = new Object();
   
   public void put(){
      synchronized (lock) {
         for(int i=0;i<10;i++){
            try {
               Thread.sleep(1000);
            } catch (InterruptedException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
            }
            list.add("A");
            System.out.println("线程"+Thread.currentThread().getName()+"添加第"+i+"个元素");
            if(list.size()==5){
               //数据准备好了,发出唤醒通知,但是不释放锁
               lock.notify();
               System.out.println("发出通知...");
            }
         }
      }
   }
   
   public void get(){
      synchronized (lock) {
         try {
            System.out.println("线程"+Thread.currentThread().getName()+"业务处理,发现有需要的数据没准备好,则发起等待");
            System.out.println("线程"+Thread.currentThread().getName()+"wait");
            lock.wait(); //wait操作释放锁,否则其他线程无法进入put方法
            System.out.println("线程"+Thread.currentThread().getName()+"被唤醒");
            for(String s:list){
               System.out.println("线程"+Thread.currentThread().getName()+"获取元素:"+s);
            }
         } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
         }
      }
   }
   
   public static void main(String[] args) {
      
      final DemoThread18 demo = new DemoThread18();
      
      new Thread(new Runnable() {
         @Override
         public void run() {
            demo.get();
         }
      },"t1").start();;
      
      try {
         Thread.sleep(1000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      
      new Thread(new Runnable() {
         @Override
         public void run() {
            demo.put();
         }
      },"t2").start();
      
   }

   
}

结果如下,notify不释放锁

线程t1业务处理,发现有需要的数据没准备好,则发起等待
线程t1wait
线程t2添加第0个元素
线程t2添加第1个元素
线程t2添加第2个元素
线程t2添加第3个元素
线程t2添加第4个元素
发出通知...
线程t2添加第5个元素
线程t2添加第6个元素
线程t2添加第7个元素
线程t2添加第8个元素
线程t2添加第9个元素
线程t1被唤醒
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A

notify与notifyAll的区别

Notify只会通知一个wait中的线程,并把锁给他,不会产生锁竞争问题,但是该线程处理完毕之后 必须再次notify或notifyAll,完成类似链式的操作。

NotifyAll会通知所有wait中的线程,会产生锁竞争问题。

public class DemoThread22 {

   public synchronized void run1() {
      System.out.println("进入run1方法..");
//    this.notifyAll();
      this.notify();
      System.out.println("run1执行完毕,通知完毕..");
   }

   public synchronized void run2() {
      try {
         System.out.println("进入run2方法..");
         this.wait();
         System.out.println("run2执行完毕..");
//       this.notify();
//       System.out.println("run2发出通知..");
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }

   public synchronized void run3() {
      try {
         System.out.println("进入run3方法..");
         this.wait();
         System.out.println("run3执行完毕..");
//       this.notify();
//       System.out.println("run3发出通知..");
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }

   public static void main(String[] args) throws InterruptedException {

      final DemoThread22 demo = new DemoThread22();

      new Thread(new Runnable() {

         @Override
         public void run() {
            demo.run2();
         }
      }).start();

      new Thread(new Runnable() {

         @Override
         public void run() {
            demo.run3();
         }
      }).start();
      
      Thread.sleep(1000L);
      
      new Thread(new Runnable() {
         @Override
         public void run() {
            demo.run1();
         }
      }).start();
   }
}

run1中使用notify方法,run2和run3都在wait,执行结果如下,run3还未执行,程序未终止(notify只能通知一个线程)

进入run2方法..
进入run3方法..
进入run1方法..
run1执行完毕,通知完毕..
run2执行完毕..

在run2中也notify,链式通知,始终只有一个线程获得锁

进入run2方法..
进入run3方法..
进入run1方法..
run1执行完毕,通知完毕..
run2执行完毕..
run2发出通知..
run3执行完毕..

如果在run2和run3中都不执行notify,而是在run中notifyAll,可以通知到所有的线程(会引发锁竞争)

进入run2方法..
进入run3方法..
进入run1方法..
run1执行完毕,通知完毕..
run3执行完毕..
run2执行完毕..

阻塞式线程安全队列实现

  • 如果队列满了不能报错,而是执行wait

  • 如果get没有数据,也要执行wait状态

  • 线程安全:不允许多个线程同时写,不能在put的同时进行get(避免脏读)

class MQueue {
   
   private List<String> list = new ArrayList<String>();
   
   private int maxSize;
   
   private Object lock = new Object();
   
   public MQueue(int maxSize){
      this.maxSize=maxSize;
      System.out.println("线程"+Thread.currentThread().getName()+"已初始化长度为"+this.maxSize+"的队列");
   }
   
   public void put(String element){
      synchronized (lock) {
         if(this.list.size()==this.maxSize){
            try {
               System.out.println("线程"+Thread.currentThread().getName()+"当前队列已满put等待...");
               lock.wait();
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
         this.list.add(element);
         System.out.println("线程"+Thread.currentThread().getName()+"向队列中加入元素:"+element);
         lock.notifyAll(); //通知可以取数据
      }
   }
   
   public String take(){
      synchronized (lock) {
         if(this.list.size()==0){
            try {
               System.out.println("线程"+Thread.currentThread().getName()+"队列为空take等待...");
               lock.wait();
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
         String result = list.get(0);
         list.remove(0);
         System.out.println("线程"+Thread.currentThread().getName()+"获取数据:"+result);
         lock.notifyAll(); //通知可以加入数据
         return result;
      }
   }
}

public class DemoThread20 {
   public static void main(String[] args) {
      final MQueue q = new MQueue(5);
      
      new Thread(new Runnable() {
         @Override
         public void run() {
            q.put("1");
            q.put("2");
            q.put("3");
            q.put("4");
            q.put("5");
            q.put("6");
         }
      },"t1").start();
      
      new Thread(new Runnable() {
         @Override
         public void run() {
            q.put("11");
            q.put("21");
            q.put("31");
            q.put("41");
            q.put("51");
            q.put("61");
         }
      },"t2").start();
      
      new Thread(new Runnable() {
         @Override
         public void run() {
            q.take();
            q.take();
            q.take();
            q.take();
            q.take();
         }
      },"t3").start();
      
      new Thread(new Runnable() {
         @Override
         public void run() {
            q.take();
            q.take();
            q.take();
            q.take();
            q.take();
         }
      },"t4").start();
   }
}

执行结果

线程main已初始化长度为5的队列
线程t1向队列中加入元素:1
线程t1向队列中加入元素:2
线程t1向队列中加入元素:3
线程t1向队列中加入元素:4
线程t1向队列中加入元素:5
线程t1当前队列已满put等待...
线程t2当前队列已满put等待...
线程t3获取数据:1
线程t3获取数据:2
线程t3获取数据:3
线程t3获取数据:4
线程t3获取数据:5
线程t2向队列中加入元素:11
线程t1向队列中加入元素:6
线程t2向队列中加入元素:21
线程t2向队列中加入元素:31
线程t2向队列中加入元素:41
线程t2当前队列已满put等待...
线程t4获取数据:11
线程t4获取数据:6
线程t4获取数据:21
线程t4获取数据:31
线程t4获取数据:41
线程t2向队列中加入元素:51
线程t2向队列中加入元素:61

wait方法解析

wait和notify为Object类中的方法。而join方法为Thread类直接提供的方法。等待时间到了如果没有被唤醒也继续执行

public final void wait() throws InterruptedException
public final native void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
 public final void wait() throws InterruptedException {
        wait(0);
    }

调用

public final native void wait(long timeout) throws InterruptedException;
//进行舍入,只要纳秒值大于0就进位
public final void wait(long timeout, int nanos) throws InterruptedException {
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }

    if (nanos > 0) {
        timeout++;
    }

    //调用本地接口
    wait(timeout);
}

例子

public class WaitDemo0 {

    public static void main(String[] args) {

        final Object lock = new Object();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    System.out.println(Thread.currentThread().getName()+" get Lock, waiting 2000 million");
                    try {
                        lock.wait(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+" release Lock,run over");
                }
            }
        }, "t1");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //提醒:修改这个数值的大小,来观察t1线程wailt(timeout)的效果
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock){
                    System.out.println(Thread.currentThread().getName()+" get Lock, notify");
                    lock.notify();
                    System.out.println(Thread.currentThread().getName()+" release Lock,run over");
                }
            }
        }, "t2");

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("final:"+t1.getName()+" and " + t2.getName() + " run over!");
        System.out.println("t1's state:"+t1.getState());
        System.out.println("t2's state:"+t2.getState());
    }

}

执行结果

t1 get Lock, waiting 2000 million
t1 release Lock,run over
t2 get Lock, notify
t2 release Lock,run over
final:t1 and t2 run over!
t1's state:TERMINATED
t2's state:TERMINATED

修改t2 sleep 1秒

t1 get Lock, waiting 2000 million
t2 get Lock, notify
t2 release Lock,run over
t1 release Lock,run over
final:t1 and t2 run over!
t1's state:TERMINATED
t2's state:TERMINATED

守护线程和用户线程

线程分类:daemon线程(守护线程)、user线程(用户线程)

易混淆知识点:main函数所在的线程就是一个用户线程

重要知识点1:最后一个user线程结束时,JVM会正常退出,不管是否有守护线程正在运行。反过来说:只要有一个用户线程还没结束,JVM 进程就不会结束。

重要知识点2:父线程结束后,子线程还可以继续存活,子线程的生命周期不受父线程的影响

/**
 * 守护线程和用户线程Demo
 */
public class DaemonAndUserThreadDemo0 {

    public static void main(String[] args) {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){

                }
            }
        });

        //提醒:打开和关闭这个设置观察JVM进程是否终止
        //thread.setDaemon(true);

        thread.start();

        //输出线程是否为守护线程
        System.out.println(thread.getName() +" is daemon? "+ thread.isDaemon());
        System.out.println(Thread.currentThread().getName() +" is daemon? "+ Thread.currentThread().isDaemon());

        System.out.println("main is over");
    }
}

执行结果,while死循环,用户线程不结束,JVM进程不结束(main线程结束了,但是它创建的子线程thread依然存活)

Thread-0 is daemon? false
main is daemon? false
main is over

修改thread为守护线程,mian函数是唯一的用户线程,执行完毕后JVM进程就停止了

Thread-0 is daemon? true
main is daemon? false
main is over

Java虚拟机启动时会启动一个DestroyJavaVM的线程,在所有的用户线程结束后终止JVM进程

tomcat中提供的接收和处理的线程都是守护线程,执行shutdown命令时会等待用户线程关闭,而kill是强行关闭

上下文切换

image-20210224225228830

当前线程使用完时间片后就会进入就绪状态,让出CPU执行权给其他线程,此时就是从当前线程的上下文切换到了其他线程。 当发生上下文切换的时候需要保存执行现场,待下次执行时进行恢复。 所以频繁的、大量的上下文切换会造成一定资源开销

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值