Skip to content

Latest commit

 

History

History
243 lines (194 loc) · 6.47 KB

多线程及线程同步.md

File metadata and controls

243 lines (194 loc) · 6.47 KB

多线程和线程同步

进程和线程

  • 进程和线程
    • 操作系统中运⾏多个软件
    • ⼀个运⾏中的软件可能包含多个进程
    • ⼀个运⾏中的进程可能包含多个线程
  • CPU 线程和操作系统线程
    • CPU 线程
      • 多核 CPU 的每个核各⾃独⽴运⾏,因此每个核⼀个线程
      • 四核⼋线程」:CPU 硬件⽅在硬件级别对 CPU 进⾏了⼀核多线程的⽀持(本质上 依然是每个核⼀个线程)
    • 操作系统线程:操作系统利⽤时间分⽚的⽅式,把 CPU 的运⾏拆分给多条运⾏逻辑,即为 操作系统的线程
    • 单核 CPU 也可以运⾏多线程操作系统
  • 线程是什么:按代码顺序执⾏下来,执⾏完毕就结束的⼀条线
    • UI 线程为什么不会结束?因为它在初始化完毕后会执⾏死循环,循环的内容是刷新界⾯

多线程的使⽤

  • Thread 和 Runnable

    • Thread

      Thread thread = new Thread() {
       @Override
       public void run() {
       System.out.println("Thread started!");
       }
      };
      thread.start();
      
    • Runnable

      Runnable runnable = new Runnable() {
       @Override
       public void run() {
       System.out.println("Thread with Runnable started!");
       }
      };
      Thread thread = new Thread(runnable);
      thread.start();
      
  • ThreadFactory

    ThreadFactory factory = new ThreadFactory() {
     int count = 0;
     @Override
     public Thread newThread(Runnable r) {
     count ++;
     return new Thread(r, "Thread-" + count);
     }
    };
    Runnable runnable = new Runnable() {
     @Override
     public void run() {
     System.out.println(Thread.currentThread().getName() + "
    started!");
     }
    };
    Thread thread = factory.newThread(runnable);
    thread.start();
    Thread thread1 = factory.newThread(runnable);
    thread1.start();
    
  • Executor 和线程池

    • 常⽤: newCachedThreadPool()

      Runnable runnable = new Runnable() {
       @Override
       public void run() {
       System.out.println("Thread with Runnable started!");
       }
      };
      Executor executor = Executors.newCachedThreadPool();
      executor.execute(runnable);
      executor.execute(runnable);
      executor.execute(runnable);
      
    • 短时批量处理: newFixedThreadPool()

      ExecutorService executor = Executors.newFixedThreadPool(20);
      for (Bitmap bitmap : bitmaps) {
       executor.execute(bitmapProcessor(bitmap));
      }
      executor.shutdown();
      
  • Callable 和 Future

    Callable<String> callable = new Callable<String>() {
     @Override
     public String call() {
     try {
     Thread.sleep(1500);
     } catch (InterruptedException e) {
     e.printStackTrace();
     }
     return "Done!";
     }
    };
    ExecutorService executor = Executors.newCachedThreadPool();
    Future<String> future = executor.submit(callable);
    try {
     String result = future.get();
     System.out.println("result: " + result);
    } catch (InterruptedException | ExecutionException e) {
     e.printStackTrace();
    }
    

线程同步与线程安全

  • synchronized

    • synchronized ⽅法

      private synchronized void count(int newValue) {
       x = newValue;
       y = newValue;
       if (x != y) {
       System.out.println("x: " + x + ", y:" + y);
       }
      }
      
    • synchronized 代码块

      private void count(int newValue) {
       synchronized (this) {
       x = newValue;
       y = newValue;
       if (x != y) {
       System.out.println("x: " + x + ", y:" + y);
       }
       }
      }
      
      synchronized (monitor1) {
       synchronized (monitor2) {
       name = x + "-" + y;
       }
      }
      
    • monitor:

      • 非静态方法前调用synchronized时候monitor是由本类的实例化object来监视
      • 代码块中调用synchronized是可以指定monitor
      • 静态方法中调用synchronized是由class来监视
    • synchronized 的本质

      • 保证⽅法内部或代码块内部资源(数据)的互斥访问。即同⼀时间、由同⼀个 Monitor 监视的代码,最多只能有⼀个线程在访问
      • 保证线程之间对监视资源的数据同步。即,任何线程在获取到 Monitor 后的第⼀时 间,会先将共享内存中的数据复制到⾃⼰的缓存中;任何线程在释放 Monitor 的第⼀ 时间,会先将缓存中的数据复制到共享内存中。
  • volatile

    • 保证加了 volatile 关键字的字段的操作具有原⼦性和同步性,其中原⼦性相当于实现了针对 单⼀字段的线程间互斥访问。因此 volatile 可以看做是简化版的 synchronized。
    • volatile 只对基本类型 (byte、char、short、int、long、float、double、boolean) 的赋值 操作和对象的引⽤赋值操作有效。
  • java.util.concurrent.atomic 包:

    • 下⾯有 AtomicInteger AtomicBoolean 等类,作⽤和 volatile 基本⼀致,可以看做是 通⽤版的 volatile。

      AtomicInteger atomicInteger = new AtomicInteger(0);
      ...
      
      atomicInteger.getAndIncrement();
      
  • Lock / ReentrantReadWriteLock

    • 同样是「加锁」机制。但使⽤⽅式更灵活,同时也更麻烦⼀些。

      Lock lock = new ReentrantLock();
      ...
      
      lock.lock();
      try {
       x++;
      } finally {
       lock.unlock();
      }
      
    • ⼀般并不会只是使⽤ Lock ,⽽是会使⽤更复杂的锁,例如 ReadWriteLock :

      ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
      Lock readLock = lock.readLock();
      Lock writeLock = lock.writeLock();
      private int x = 0;
      private void count() {
       writeLock.lock();
       try {
       x++;
       } finally {
       writeLock.unlock();
       }
      }
      private void print(int time) {
       readLock.lock();
       try {
       for (int i = 0; i < time; i++) {
       System.out.print(x + " ");
       }
       System.out.println();
       } finally {
       readLock.unlock();
       }
      }
      
  • 线程安全问题的本质:

    在多个线程访问共同的资源时,在某⼀个线程对资源进⾏写操作的中途(写⼊已经开始,但还没 结束),其他线程对这个写了⼀半的资源进⾏了读操作,或者基于这个写了⼀半的资源进⾏了写 操作,导致出现数据错误。

  • 锁机制的本质: 通过对共享资源进⾏访问限制,让同⼀时间只有⼀个线程可以访问资源,保证了数据的准确性。

  • 不论是线程安全问题,还是针对线程安全问题所衍⽣出的锁机制,它们的核⼼都在于共享的资 源,⽽不是某个⽅法或者某⼏⾏代码。

2020 7.18 23:28