美文网首页
高并发编程一

高并发编程一

作者: 紫雨杰 | 来源:发表于2018-05-15 17:27 被阅读0次

Java高并发编程一

 1、synchronized 关键字,锁住的不是一个代码块,而是一个对象;
 2、一个synchronized代码块相当于一个原子操作,是不可分的;
 3、同步和非同步方法是否可以同时调用?         ----> 可以
 4、对业务写方法加锁,对业务读方法不加锁,容易产生脏读问题(dirtyRead)

 5、一个同步方法是否可以调用另外一个同步方法?         ----> 可以
    一个线程已经拥有某个对象的锁,再次申请的时候是否仍然会得到该对象的锁?         ----> 可以
    也就是说synchronized获得的锁是可重入(即获得锁之后还可以再获取一遍锁)

 6、继承中有可能发生的情形,子类重写父类的同步方法,然后再重写父类的同步方法中调用父类的同步方法,这是可以的,不会产生死锁

 7、程序在执行过程中,如果出现异常,默认情况锁会被释放,所以在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。
    【线程抛出异常会释放锁,如果不想释放锁,可以添加try catch 进行异常处理】

    比如:在一个web应用程序处理过程中,多个servlet线程共同访问同一资源,这时如果异常处理不合适,在第一个线程中抛出异常,
         其它线程就会进入同步代码区,有可能会访问到异常产生时的数据,因此要小心处理同步业务逻辑中的异常。

 8、使用volatile关键字,会让所有线程都会读到变量的修改值
    【即当多个线程进行操作共享数据时,使用volatile关键字可以保证内存中的数据是可见的】

     ▲注意:
          ①、volatile 不具备互斥性;
          ②、volatile 不能保证数据的"原子性";
          ③、JVM的底层有一个优化,叫做重排序,使用volatile关键字后,不能重排序了

          所以volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能代替synchronized

     ★ 在下面的代码中,running是存在于堆内存的t 对象中(堆内存、栈内存统称为主内存)
         当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区中,在运行过程中,直接使用自己工作区中的值,
         并不会每次都去读取堆内存中的running值,这样,当主线程修改running的值之后,t1线程感知不到,所以就不会停止运行

     

    【注意】
         在while中,即死循环中执行一些语句,比如添加sleep方法等的时候,可能会停止运行,因为可能某一时刻CPU空闲了,可能去会读取主内存中的running值
     public class T {
        /*volatile*/ boolean running = true; //对比有无volatile的情况下,整个程序的运行结果
        void m() {
            System.out.println("m start");
            while(running) {
                /*
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
             }
              System.out.println("m end!");
        }

        public static void main(String[] args) {
              T t = new T();
    
              new Thread(t::m, "t1").start();
    
              try {
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }

              t.running = false;    
        }   
}

 9、synchronized 可以保证可见性和原子性,volatile只能保证可见性
    synchronized 的效率比volatile低很多

10、JDK1.5之后,在java.util.concurrent.atomic 包下提供了一些原子变量。
     ▲  解决同样的问题的更高效的方法,就是使用atomicXXX类,atomicXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性的;
     ▲  atomicXXX类可以保证可见性

 11、synchronized优化:
             同步代码块中的语句越少越好

public class T {

    int count = 0;

    synchronized void m1() {
        //do sth need not sync
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
        count ++;
    
        //do sth need not sync
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    void m2() {
        //do sth need not sync
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
        //采用细粒度的锁,可以使线程争用时间变短,从而提高效率
        synchronized(this) {
            count ++;
        }
        //do sth need not sync
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 12、锁定某对象o,如果o的属性发生改变,不影响锁的使用,但是如果o变成了另外一个对象,则锁定的对象会发生改变,
        应该避免将锁定对象的引用变成另外的对象。

     ▲ 锁的是堆内存中真正new出来的对象上,而不是锁在栈内存的引用上

 13、不要以字符串常量作为锁定对象,可能会出现死锁

     在下面的例子中,m1和m2其实锁定的是同一个对象,这种情况还会发生比较诡异的现象,比如用到了一个类库,在该类库中代码锁定了字符串"hello",
     但是你读不到源码,所以在自己的代码中也锁定了"hello",这时就有可能发生非常诡异的死锁阻塞,因为你的程序和你用到的类库不经意间使用了同一把锁。

      public class T {

          String s1 = "Hello";
          String s2 = "Hello";
          void m1() {
              synchronized(s1) {            
              }
          }

          void m2() {
              synchronized(s2) {            
              }
          }
      }

 14、面试题:
        实现一个容器,提供两个方法,add,size,写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束

     分析:
      ● 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,该怎么做呢?
      ● 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁,需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
      ● notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行,否则输出结果为:
        输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出

              public class T {

                   //添加volatile,使t2能够得到通知
                  volatile List lists = new ArrayList();
                  public void add(Object o) {
                      lists.add(o);
                  }

                 public int size() {
                        return lists.size();
                 }

                 public static void main(String[] args) {
                         T c = new T();
                         final Object lock = new Object();

                          new Thread(() -> {
                                synchronized(lock) {
                                        System.out.println("t2启动");
                                        if(c.size() != 5) {      //和生产者消费者不同,此时不需要使用while
                                            try {
                                                  lock.wait();
                                            } catch (InterruptedException e) {
                                                  e.printStackTrace();
                                            }
                                        }
                                      System.out.println("t2 结束");
                                      //通知t1继续执行
                                    lock.notify();
                                }
                          }, "t2").start();

                          try {
                                TimeUnit.SECONDS.sleep(1);
                          } catch (InterruptedException e1) {
                                e1.printStackTrace();
                          }

                          new Thread(() -> {
                                  System.out.println("t1启动");
                                  synchronized(lock) {
                                      for(int i=0; i<10; i++) {
                                          c.add(new Object());
                                          System.out.println("add " + i);
                                          if(c.size() == 5) {
                                                  lock.notify();
                                                  //释放锁,让t2得以执行
                                                  try {
                                                        lock.wait();
                                                  } catch (InterruptedException e) {
                                                        e.printStackTrace();
                                                }
                                          }
                                         try {
                                            TimeUnit.SECONDS.sleep(1);
                                         } catch (InterruptedException e) {
                                            e.printStackTrace();
                                        }
                                      }
                                   }
                          }, "t1").start();
                  }
            }

        上述方式整个通信过程比较繁琐
      ● 使用Latch(门闩)替代wait notify来进行通知,好处是通信方式简单,同时也可以指定等待时间
        使用await和countdown方法替代wait和notify,CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行
        当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了,这时应该考虑countdownlatch/cyclicbarrier/semaphore

              public class T {
                   //添加volatile,使t2能够得到通知
                  volatile List lists = new ArrayList();
                  public void add(Object o) {
                      lists.add(o);
                  }

                 public int size() {
                        return lists.size();
                 }

                 public static void main(String[] args) {
                         T c = new T();
                         CountDownLatch latch = new CountDownLatch(1);

                          new Thread(() -> {
                                        System.out.println("t2启动");
                                        if(c.size() != 5) {      //和生产者消费者不同,此时不需要使用while
                                            try {
                                                  latch.await();
                                                  //也可以指定等待时间
                                                  //latch.await(5000, TimeUnit.MILLISECONDS);
                                            } catch (InterruptedException e) {
                                                  e.printStackTrace();
                                            }
                                        }
                                      System.out.println("t2 结束");
                          }, "t2").start();

                          try {
                                TimeUnit.SECONDS.sleep(1);
                          } catch (InterruptedException e1) {
                                e1.printStackTrace();
                          }

                          new Thread(() -> {
                                  System.out.println("t1启动");
                                      for(int i=0; i<10; i++) {
                                          c.add(new Object());
                                          System.out.println("add " + i);
                                          if(c.size() == 5) {
                                                  // 打开门闩,让t2得以执行
                                                  latch.countDown();
                                          }
                                         try {
                                            TimeUnit.SECONDS.sleep(1);
                                         } catch (InterruptedException e) {
                                            e.printStackTrace();
                                        }
                                      }
                          }, "t1").start();
                  }
            }

相关文章

网友评论

      本文标题:高并发编程一

      本文链接:https://www.haomeiwen.com/subject/cdkpkftx.html