阅读总结(a)

1、锁(lock)与volatile
(1)、隐式锁,java提供了强制原子性的内置锁机制:synchronized块或synchronized方法。
操作共享状态的复合操作必须是原子的,以避免竞态条件,比如读-改-写操作和检查再运行操作。
复合操作会在完整的运行期占有锁,以确保其行为为原子的。
不管是同步代码块还是同步方法,每次只有一个线程可以进入,如果其他线程试图进入(不管是同一同步块还是不同的同步块),
JVM会将它们挂起(放入到等锁池中)。这种结构在并发理论中称为临界区。
这里我们可以对Java中用synchronized实现同步和锁的功能做一个总结

静态同步方法会锁定它的Class对象
同步方法可以视为包含整个方法的synchronized(this) { … }代码块
synchronized修饰符并不是方法签名的组成部分,所以不能出现在接口的方法声明中
非同步的方法不关心锁的状态,它们在同步方法运行时仍然可以得以运行
synchronized实现的锁是可重入的锁。

在JVM内部,为了提高效率,同时运行的每个线程都会有它正在处理的数据的缓存副本,当我们使用synchronzied进行同步的时候,真正被同步的是在不同线程中表示被锁定对象的内存块简单的说就是在同步块或同步方法执行完后,对被锁定的对象做的任何修改要在释放锁之前写回到主内存中;在进入同步块得到锁之后,被锁定对象的数据是从主内存中读出来的,持有锁的线程的数据副本一定和主内存中的数据视图是同步的

(2)、volatile关键字:volatile修饰变量或对象,被volatile修饰的变量或对象告诉vm从内存中读取该变量、对象的值

变量的值在使用之前总会从主内存中再读取出来。

对变量值的修改总会在完成之后写回到主内存中。


volatile不能保证多线程中的同步,但是可以保证在多线程中数据是可见的。


(3)、显式锁 Lock 和 ReentrantLock
与内置锁机制不同的是,Lock提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁方法都是显示的。
lock能实现synchronized实现的所有功能,lock()的使用更加方便灵活、功能更加强大。

lock分别为读、写提供了锁,这样效率更高。
public interface Lock {
    //获取锁
    void lock();
    //如果当前线程未被中断,则获取锁。
    void lockInterruptibly() throws InterruptedException;
    //仅在调用时锁为空闲状态才获取锁
    tryLock();
    //如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。
    tryLock(long time,TimeUnit unit);
    //返回绑定此Lock实例的新Condition实例。
    Condition newCondition();
}lock方法获取不到锁则阻塞,tryLock获取不到锁不阻塞直接返回false。


lock带来灵活、方便的同时也引入隐患就是死锁,lock的释放unlock需要在finally代码块里释放锁,如果没有释放锁很可能程序运行不下去,造成死锁。

Lock lock = new ReentrantLock();
lock.lock();
try {
  // update object state
}
finally {
  lock.unlock();
}
必须在finally 中来释放Lock

这也是使用synchronized比使用lock简单的一个点。

synchronized和ReentrantLock之间的选择:
ReentrantLock在加锁和内存上提供的语义与内置锁相同,此外它还提供了一些其他功能,包括定时等待,可中断的锁等待,公平性,以及实现非块结构的加锁。
同时ReentrantLock为读、写分别提供了锁机制,强制的进行了分离。
然而ReentrantLock的危险性比同步机制要高,如果忘记在finally块中调用unlock,那么虽然代码表面上正常运行,
但实际上已经埋下了一颗定时炸弹,并可能伤及其他代码。仅内置锁不能满足需求时,才可以考虑使用ReentrantLock.
在一些内置锁无法满足需求时,ReentrantLock可以作为一种高级工具,当需要一些高级功能时才应该使用ReentrantLock,
这些功能包括:可定时的、可轮询的与可中断的锁获取操作,公平队列,以及非块结构的锁。
否则,还应该优先使用synchronized。ReentrantLock的非块结构仍然意味着,获取锁的操作不能与特定的栈帧关联起来,而内置锁则可以。

(4)、锁的唤醒:

notify 和 notifyAll:
在条件队列API中有两个发出通知的方法,即 notify和 notifyAll。无论调用哪一个,都必须持有与条件队列对象相关联的锁。notify操作会选在条件队列中的一个线程唤醒,其他线程则无法得到唤醒通知。而notifyAll则会唤醒所有等待在条件队列中的线程来竞争锁。使用notify单一的同时可能发生信号丢失问题。
普遍认可的做法是优先使用notifyAll而不是notify。虽然notifyAll可能比notify更低效,但却更容易确保类的行为是正确的。

注意:notify()对于只有一个线程时是有意义的,多线程时需要使用notifyAll(),由于notify()和notifyAll()不精确,因此建议使用信号量或阻塞队列来实现对共享资源的加锁。

2、并发集合ConcurrentHashMap

先说下HashMap:其是线程不安全的,没有同步的机制,在多线程执行时可能会造成数据的错乱,并且效率也低。HashMap底层实现机制是一个数组,数组中的每个元素又是一个单链表,只有hashcode值冲突的元素才放到这个单链表中。HashMap中的元素是由key与value封装成的Entity对象,这是造成其占用很大内存空间的根本原因,其次是他的默认大小是16,在需要增长时增长到原来的2倍,并且还是2的N次方。

ConcurrentHashMap即使线程安全的又是高效的。通过源码分析他的底层实现机制是一个segment数组,即段数组。将数组中的元素分为若干段,每一段对应一个锁,这可以达到锁分离的目的,对其中一段的访问不影响其他的元素,提交了效率。他的并发度是16,是构造器中的一个参数。


3、通过共享对象达到共享数据的目的
(1)、设计线程安全的类。
     找出构造对象状态的所有变量。
     约束状态变量的不变性条件。
     建立对象状态的并发访问管理策略。
(2)、实例封闭
     如果某对象不是线程安全的,我们可以通过多种技术使其在多线程中能安全的使用。确保该对象只能由单个线程访问。
public class AnimalSet{  
         private final Set mySet = new HashSet();  
           
         public sychronized void addAnimaln(Animal p) {  
              mySet.add(p)  
         }  
      
         public sychronized boolean containsAnimal(Animal p) {  
              return mySet.contains(p);  
         }  
    }  


虽然HashSet并非线程安全的。但是mySet是私有的不会逸出。唯一能访问mySet的代码是addPerson()和containsPerson()。
在执行上他们都要获的PersonSet 上的锁。PersonSet的状态完全又它的内置锁保护。所以AnimalSet是一个线程安全的类。


4、基础模块


(1)、同步容器类。线程安全的容器包括Vector和Hashtable。同步的封装容器类由Collections.sychronizedXXX工厂方法创建。
     eg:synchronizedList,synchronizedMap(m)、synchronizedSet(s)这些是通过synchronized同步方法来实现的,达到了线程安全但是效率低
(2)、同步工具类。
     阻塞队列(BlockingQueue(LinkedBlockingQueue,ArrayBlockingQueue,PriorityBlockingQueue,SynchronousQueue))不仅可以保存对象的容器,而且还可以协调生产者和消费者之间的控制流。
     信号量(Semaphore):用来控制同时访问某个特定资源的操作数量。通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。Semaphore允许线程获取许可, 未获得许可的线程需要等待.这样防止了在同一时间有太多的线程执行。Semaphore实现的功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。
 eg:模拟30辆车去泊车,而车位有10个的场景. 当车位满时,出来一辆车,才能有一辆车进入停车.

转载 http://mouselearnjava.iteye.com/blog/1921468

package my.concurrent.semaphore;  
  
import java.util.concurrent.Semaphore;  
  
public class Car implements Runnable {  
  
     private final Semaphore parkingSlot;  
  
     private int carNo;  
  
     /** 
     * @param parkingSlot 
     * @param carName 
     */  
     public Car(Semaphore parkingSlot, int carNo) {  
          this.parkingSlot = parkingSlot;  
          this.carNo = carNo;  
     }  
  
     public void run() {  
  
          try {  
               parkingSlot.acquire();  
               parking();  
               sleep(300);  
               parkingSlot.release();  
               leaving();  
  
          } catch (InterruptedException e) {  
               // TODO Auto-generated catch block  
               e.printStackTrace();  
          }  
  
     }  
  
     private void parking() {  
          System.out.println(String.format("%d号车泊车", carNo));  
     }  
  
     private void leaving() {  
          System.out.println(String.format("%d号车离开车位", carNo));  
     }  
  
     private static void sleep(long millis) {  
          try {  
               Thread.sleep(millis);  
          } catch (InterruptedException e) {  
               // TODO Auto-generated catch block  
               e.printStackTrace();  
  
          }  
     }  
  
}  
  
package my.concurrent.semaphore;  
  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.Semaphore;  
  
public class ParkingCars {  
  
     private static final int NUMBER_OF_CARS = 30;  
  
     private static final int NUMBER_OF_PARKING_SLOT = 10;  
  
     public static void main(String[] args) {  
  
          /* 
          * 采用FIFO, 设置true 
          */  
          Semaphore parkingSlot = new Semaphore(NUMBER_OF_PARKING_SLOT, true);  
  
          ExecutorService service = Executors.newCachedThreadPool();  
  
          for (int carNo = 1; carNo <= NUMBER_OF_CARS; carNo++) {  
               service.execute(new Car(parkingSlot, carNo));  
          }  
  
          sleep(3000);  
  
          service.shutdown();  
  
          /* 
          * 输出还有几个可以用的资源数 
          */  
          System.out.println(parkingSlot.availablePermits() + " 个停车位可以用!");  
     }  
  
     private static void sleep(long millis) {  
          try {  
               Thread.sleep(millis);  
          } catch (InterruptedException e) {  
               // TODO Auto-generated catch block  
               e.printStackTrace();  
          }  
     }  
  
}  

运行结果:
1号车泊车 
4号车泊车 
9号车泊车 
2号车泊车 
8号车泊车 
10号车泊车 
3号车泊车 
12号车泊车 
14号车泊车 
6号车泊车 
2号车离开车位 
4号车离开车位 
6号车离开车位 
1号车离开车位 
9号车离开车位 
3号车离开车位 
5号车泊车 
8号车离开车位 
10号车离开车位 
11号车泊车 
7号车泊车 
12号车离开车位 
13号车泊车 
14号车离开车位 
16号车泊车 
17号车泊车 
20号车泊车 
19号车泊车 
18号车泊车 
15号车泊车 
5号车离开车位 
20号车离开车位 
18号车离开车位 
22号车泊车 
11号车离开车位 
7号车离开车位 
13号车离开车位 
15号车离开车位 
21号车泊车 
26号车泊车 
23号车泊车 
28号车泊车 
25号车泊车 
16号车离开车位 
27号车泊车 
17号车离开车位 
30号车泊车 
24号车泊车 
29号车泊车 
19号车离开车位 
25号车离开车位 
24号车离开车位 
22号车离开车位 
26号车离开车位 
28号车离开车位 
30号车离开车位 
21号车离开车位 
23号车离开车位 
27号车离开车位 
29号车离开车位 
10 个停车位可以用!

你可能感兴趣的:(多线程)