目录
模拟数组库
数据库就类似于一个集合,element其实就是相当于数据库中的一行记录。就比如有一个图书表,有人会去查阅、编辑或是添加信息,他们操作的都是一个图书表中的数据;这时候数据库的操作一定是高并发的操作,比如有人在查阅书籍,同时别人在插入图书信息,读操作一定会受到影响。
Book.java
public class Book {
private String name;
private double price;
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
BookTable.java 模拟数据库
import java.util.List;
import java.util.Vector;
public class BookTable {
private List<Book> bookTable;
public BookTable() {
bookTable = new Vector<>(30);
}
public void add(Book book){
bookTable.add(book);
}
public Book get(int index){
return bookTable.get(index);
}
public int size(){
return bookTable.size();
}
}
Vector与ArrayList
Vector与ArrayList两者的底层都是实现了静态数组,而Vector是线程安全的,ArrayList是线程不安全的;
而Vector在1.5之后就被弃用了
源码对比
通过观察Vector源码发现不少方法有synchronized关键字
夸张的是连获取长度的size()方法都上锁了
public synchronized int size() {
return elementCount;
}
接下来我们主要来看一下增删查操作,所有的读和写操作都是用了synchronized对方法进行了锁定
//增
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
//查
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
//删
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
1、比如当一个线程要进行写(add)操作时,它会先获得锁,如果其他线程也要对同一个对象进行写操必须要排队等待
2、如果有两个线程对同一个对象进行操作,一个进行读,一个进行写,因为synchronized只锁代码块,所以读写操作时互不干扰的
而ArrayList中的方法没有一个用synchronized修饰!
Vector
有两个写线程,一个读线程
public class VectorThread {
public static void main(String[] args) {
BookTable bookTable = new BookTable();
//写a
new Thread(()->{
for(int i=0;i<30;i++){
Book book = new Book("a"+Integer.toString(i));
bookTable.add(book);
System.out.println("线程"+Thread.currentThread().getId()+",添加:"+book.getName());
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
//写b
new Thread(()->{
for(int i=0;i<30;i++){
Book book = new Book("b"+Integer.toString(i));
bookTable.add(book);
System.out.println("线程"+Thread.currentThread().getId()+",添加:"+book.getName());
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
//读
new Thread(()->{
try {
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
for(int i=0;i<bookTable.size();i++){
Book book = bookTable.get(i);
System.out.println("线程"+Thread.currentThread().getId()+",-------读:"+book.getName());
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
由于Vector中的add方法是上了锁的,两个线程对同一个bookTable对象进行写时实际上存在某一线程在add方法外等待阻塞(排队)的情况,这要是在高并发的写操作中是很影响效率的,讲道理多个线程向数据库中插入数据应当是互不干扰的,排队就没什么意义,这也就是Vector的不足之处
。。。。
ArrayList + synchronized
改一下BookTable.java中的bookTable变量类型为ArrayList
这时候如果有一个写进程,一个读进程那是互不干扰的
而两个写进程,一个读进程就可能会出现空指针异常,因为多进程同时进入add、get方法时存在modCount等局部变量(共享的);我们需要让写进程结束了再让读进程进来,这时候与Vector不同的是我们应该锁的不是方法而是对象!
public class ArrayListThread {
public static void main(String[] args) {
BookTable bookTable = new BookTable();
//写a
new Thread(()->{
synchronized (bookTable) {
for(int i=0;i<30;i++){
Book book = new Book("a"+Integer.toString(i));
bookTable.add(book);
System.out.println("线程"+Thread.currentThread().getId()+",添加:"+book.getName());
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
//写b
new Thread(()->{
synchronized (bookTable) {
for(int i=0;i<30;i++){
Book book = new Book("b"+Integer.toString(i));
bookTable.add(book);
System.out.println("线程"+Thread.currentThread().getId()+",添加:"+book.getName());
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
//读
new Thread(()->{
try {
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (bookTable) {
for(int i=0;i<bookTable.size();i++){
Book book = bookTable.get(i);
System.out.println("线程"+Thread.currentThread().getId()+",-------读:"+book.getName());
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
}
}
结果是先写a,再读a,最后写b;也就是说读也是有排斥性的,这不是我们想要的!
Collections工具类
在Collections工具类中提供了不少线程安全的集合
以内部类synchronizedCollection为例
synchronizedCollection源码中提供了一个信号量
final Object mutex;
方法是通过锁对象来实现线程安全的,这个mute就相当于上面例子中的bookTable
public int size() {
synchronized (mutex) {return c.size();}
}
public boolean add(E e) {
synchronized (mutex) {return c.add(e);}
}
public boolean remove(Object o) {
synchronized (mutex) {return c.remove(o);}
}
...
也就是说多线程的读写操作都必须排队,一个线程读,其他线程不能读或写。这就导致了锁级别太高了!也不是我们想要的。
Lock的读锁和写锁
以上的方法无非是用synchronized关键字锁代码块(Vector)或是锁对象(Collections),但在对数据库读写时都达不到真实效果
Lock 与 synchronized对比: synchronized 时jdk内置的关键字,是jvm级的更为重要;Lock时jdk后期扩展的线程操作的接口,使用lock()获得锁,必须使用unlock()释放锁,而且具有退出和检查机制。
ReentrantLock是一个可重入互斥Lock具有与使用synchronized
方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。
Lock接口将读写操作分开:ReadLock ,WriteLock;他们都是ReentrantReadWriteLock的静态内部类;而ReentrantReadWriteLock实现了ReadWriteLock
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
public static class ReadLock implements Lock, java.io.Serializable {}
public static class WriteLock implements Lock, java.io.Serializable {}
}
ReadWriteLock
A ReadWriteLock维护一对关联的locks ,一个用于只读操作,一个用于写入。
- read lock 允许多线程都在读(对于同一块代码,一个线程加了读锁后,别的线程还可以继续加读锁)
- write lock 是一个排它锁,与读锁和其他写锁都是排斥的
修改一下上面的ArrayListThread.java,有两个读线程和两个写线程
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ArrayListThread {
public static void main(String[] args) {
BookTable bookTable = new BookTable();
ReadWriteLock locker = new ReentrantReadWriteLock();
//写a
new Thread(()->{
locker.writeLock().lock();
for(int i=0;i<30;i++){
Book book = new Book("a"+Integer.toString(i));
bookTable.add(book);
System.out.println("线程"+Thread.currentThread().getId()+",添加:"+book.getName());
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
locker.writeLock().unlock();
}).start();
//写b
new Thread(()->{
locker.writeLock().lock();
for(int i=0;i<30;i++){
Book book = new Book("b"+Integer.toString(i));
bookTable.add(book);
System.out.println("线程"+Thread.currentThread().getId()+",添加:"+book.getName());
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
locker.writeLock().unlock();
}).start();
//读a
new Thread(()->{
try {
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
locker.readLock().lock();;
for(int i=0;i<bookTable.size();i++){
Book book = bookTable.get(i);
System.out.println("线程"+Thread.currentThread().getId()+",-------读:"+book.getName());
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
locker.readLock().unlock();
}).start();
//读b
new Thread(()->{
try {
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
locker.readLock().lock();;
for(int i=0;i<bookTable.size();i++){
Book book = bookTable.get(i);
System.out.println("线程"+Thread.currentThread().getId()+",-------读:"+book.getName());
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
locker.readLock().unlock();
}).start();
}
}
结果:先写a,再写b,然后两个读线程是同时交替地读
总结:
- 如果将上面四个线程的顺序改为写读写读,结果是相同的,这是应为WriteLock的优先级高于ReadLock;
- WriteLock与WriteLock和ReadLock都是有排斥性的,只能顺序执行;
- ReadLock是可以并发读取的;
- 想要达到顺序执行的效果,通常是读锁写锁配合使用,读操作时可以共享的,写操作时不能共享的
ThreadLocal
提供线程局部变量。 这些变量与其正常的对应方式不同,因为访问一个的每个线程(通过其get或set方法)都有自己独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或事务ID)。
比如网站上用户操作向数据库中存储数据,那么每个人的每次请求都有自己的Connection,这些局部变量都存储在ThreadLocal中,所有线程之间互不干扰。
注意实例化时一定要作为静态变量对象;get()从静态对象中把当前线程存储的S对象取出来;set()使用当前线程信息,存储S为自己的局部变量。
比如一共有10个线程,我们向10个线程中分别存放不同的局部变量:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadId {
/* AtomicInteger:在高并发的环境下,int值可以进行原子性的变化(采用CAS算法)
* int getAndIncrement() :原子上+1当前值
* int getAndDecreament() :原子上-1当前值
*/
private static final AtomicInteger nextId = new AtomicInteger(0); //初始值为0
private static final ThreadLocal<Integer> threadId =new ThreadLocal<Integer>();
public static int get() {
return threadId.get();
}
public static void set(Integer i){
threadId.set(i);
}
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
for(int i=0;i<10;i++){
pool.execute(new Runnable() {
public void run() {
ThreadId.set(nextId.getAndIncrement());
int a = ThreadId.get();
System.out.println("线程"+Thread.currentThread().getId()+",局部变量a="+a);
}
});
}
pool.shutdown();
}
}
结果如下