技术点:
多线程模型,安全锁机制,等待与唤醒
public class Test1 {
public static void main(String[] args) {
Store store = new Store();
new Producter(store).start();
new Customer(store).start();
//多个生产者和消费者线程
new Producter(store).start();
new Customer(store).start();
}
}
-----生产者-----
public class Producter extends Thread {
private Store store;
public Producter(Store store){ //库存要存进来
this.store = store;
}
@Override
public void run() {
while(true) {
try {
store.push();
} catch (InterruptedException e) {
e.printStackTrace();
} //生产者负责生产
}
}
}
----消费者----
public class Customer extends Thread {
private Store store;
public Customer(Store store) {
this.store = store;
}
@Override
public void run() {
while(true) {
try {
store.pop();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} //消费者负责消费
}
}
}
---库存---
public class Store {
private int num; //单位万
public void push() throws InterruptedException { //库存生产
synchronized (this) {
//生产20件货,就满了,停止生产
while(num>=20) {
this.wait();
}
num++;
System.out.println("生产者已经生产了第"+num+"件货");
this.notify(); //唤醒等待的线程
}
}
public void pop() throws InterruptedException { //库存消费
synchronized (this) { //
while(num<=0) {
this.wait(); //等待消费,等待时,相当于把自身资源交出去了
}
System.out.println("消费者已经消费了第"+num+"件货");
num--;
this.notify(); //唤醒等待的线程
}
}
}
2.线程池
概念:可以认为是线程对象的容器,预先创建多个线程对象,然后根据这些对象可以实现复用
好处:减少创建和销毁线程的数目,提升性能
复用机制: 用完了线程对象后,重新回收到线程池中
class Task implements Runnable{
@Override
public void run() {
for(int i=1;i<=10;i++) {
System.out.println(Thread.currentThread().getName()+"===>"+i);
}
};
}
public class Test1 {
public static void main(String[] args) {
//创建一个单个线程的线程池对象
//ExecutorService es = Executors.newSingleThreadExecutor();
//创建一个带缓冲区的线程池对象,灵活控制线程对象,有多少任务,我就创建多少个线程对象
//ExecutorService es = Executors.newCachedThreadPool();
//创建固定个数的线程池对象,如果任务过多,则用回收线程对象去处理多余任务
ExecutorService es = Executors.newFixedThreadPool(2);
Task task = new Task();
es.submit(task); //类似,线程.start()
es.submit(task);
es.submit(task); //谁先用完并回收,就谁去执行新任务
es.shutdown(); //关闭线程池
}
}
3.Callable接口与Future接口
Callable接口: 和Runnable类似,也是处理任务的接口
区别: Callable是带返回值的接口,可以将线程中处理完的数据返回Future接口,该对象接收submit的返回值,可以求出call返回值
//案例: 使用两个线程计算1~50,51~100的和,并进行汇总
public class Test2 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(2);
Future future = es.submit(new Callable() {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=1;i<=50;i++) {
sum += i;
}
return sum;
}
});
Future future2 = es.submit(new Callable() {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=51;i<=100;i++) {
sum += i;
}
return sum;
}
});
//get方法类似线程基础的join,在主线程中阻塞
int sum = future.get()+future2.get();
System.out.println("两个线程的和:"+sum);
}
}
4.重入锁与读写锁
4.1重入锁
重入锁:Lock接口 实现类:ReentrantLock
与synchronized类似,用于加锁的
提供了两个方法: lock()-获取锁 unlock()-释放锁
class MyList{
String[] ss = {"A","B","",""};
int index = 2;
Lock lock = new ReentrantLock();
public void add(String value) {
//有多个线程可以调用add方法
lock.lock(); //加锁
try {
ss[index] = value;
index++;
} finally {
lock.unlock();
}
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException {
MyList list = new MyList();
Thread th1 = new Thread(new Runnable() {
@Override
public void run() {
list.add("hello");
}
});
th1.start();
Thread th2 = new Thread(new Runnable() {
@Override
public void run() {
list.add("world");
}
});
th2.start();
th1.join(); th2.join();
System.out.println(Arrays.toString(list.ss));
}
}
4.2读写锁
读写锁:一般用在读远远高于写的情况,可以提升线程执行的性能
读写锁,在读与读之间是不互斥,不阻塞的,所以性能会提升
class MyClass{
//加入读写锁
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
ReadLock rl = readWriteLock.readLock();
WriteLock wl = readWriteLock.writeLock();
Integer value;
public Integer getValue() {
try {
rl.lock();
return value;
} finally {
rl.unlock();
}
}
public void setValue(Integer value) {
try {
wl.lock();
this.value = value;
} finally {
wl.unlock();
}
}
}
public class Test2 {
public static void main(String[] args) {
MyClass myClass = new MyClass();
Runnable rt = new Runnable() {
@Override
public void run() {
myClass.getValue();
}
};
Runnable wt = new Runnable() {
@Override
public void run() {
myClass.setValue(1);
}
};
ExecutorService es = Executors.newFixedThreadPool(20);
long start = System.currentTimeMillis();
for(int i=0;i<18;i++) {
es.submit(rt); //18个线程用于读操作
}
for(int i=0;i<2;i++) {
es.submit(wt); //2个线程用于写操作
}
es.shutdown();
while(!es.isTerminated()){} //阻塞,确保所有线程结束后,才往下走
System.out.println(System.currentTimeMillis()-start);
}
}
5.线程安全的集合
5.1Collections提供的安全集合
//Collections工具类中提供了针对List,Set,Map的安全集合
//其安全机制性能方面有比较大的影响,一般使用并发安全集合
public class Test1 {
public static void main(String[] args) {
List list = Collections.synchronizedList(new ArrayList());
//多个线程进入add方法
list.add(2);
}
}
5.2CopyonWriteArrayList
CopyOnWriteArrayList: ArrayList的安全集合(读写并发)
内部进行了加锁,且性能方面比Collestions提供的安全集合更高
该安全集合具有读写分离的特性
读无锁,写有锁,读写之间不互斥,不阻塞,在内部拷贝了一份副本存数据
往往也是用在读多写上的情况
public class Test2 {
public static void main(String[] args) {
List list = new CopyOnWriteArrayList();
for(int i=0;i<5;i++) {
final int temp = i;
new Thread(new Runnable() {
@Override
public void run() {
//T1 T2 T3
//读写并发执行---执行效率很高
list.add(Thread.currentThread().getName()+"-->"+temp);
System.out.println(list); //读
}
}).start();
}
}
}
5.3CopyonWriteArraySet
CopyOnWriteArraySet: 安全的set集合,底层实现通过CopyOnWriteArrayList
该类也是读写分离的set集合类,该类具备存储唯一性
public class Test3 {
public static void main(String[] args) {
Set set = new CopyOnWriteArraySet();
set.add("苹果");
set.add("香蕉");
set.add("苹果");
System.out.println(set);
}
}
5.4 线程安全的HashMap
ConcurrentHashMap: 并发的HashMap(前提是安全)
Coolections中也提供了线程安全的Map,只不过锁的是整个hash表
public class Test1 {
public static void main(String[] args) {
Map map = new ConcurrentHashMap();
for(int i=0;i<16;i++) {
final int temp = i;
new Thread(new Runnable() {
@Override
public void run() {
map.put(Thread.currentThread().getName(), "value:"+temp);
System.out.println(map);
}
}).start();
}
}
}
6.队列
6.1Queue队列接口
Queue:队列的接口,特点:先进先出
linkedList实现类就实现了该接口
public class Test1 {
public static void main(String[] args) {
Queue queue = new linkedList();
//如果队列没有元素,则返回null--推荐
queue.offer(1);
queue.offer(3);
queue.offer(2);
System.out.println(queue.peek()); //取元素不移除
System.out.println(queue.poll()); //取元素并移除
System.out.println(queue);
}
}
6.2ConcurrentlinkedQueue实现类
ConcurrentlinkedQueue: 在多线程中,性能最高的并发的队列
内部采用了CAS无锁交换算法进行存储
有3个参数: V(要改变的变量) E(预期值) N(新值)
执行过程中,如果V=E,那么N就可以改变V变量的值了
public class Test2 {
public static void main(String[] args) throws InterruptedException {
Queue queue = new ConcurrentlinkedQueue();
Thread th1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=5;i++) {
queue.offer(i);
}
}
});
th1.start();
Thread th2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=6;i<=10;i++) {
queue.offer(i);
}
}
});
th2.start();
th1.join(); th2.join();
for(int i=1;i<=10;i++) {
System.out.println(queue.poll());
}
}
}
6.3阻塞队列
BlockingQueue:阻塞队列的接口
实现类有: 有界队列ArrayBlockingQueue(推荐),无界队列linkedBlockingQueue
有界队列是有长度限制 无界队列可以认为没有
有两个阻塞方法: put,take ,该阻塞方法的使用类似于生产者消费者用法
put:一般不能超过有界队列长度,超过了阻塞 take,没有值时阻塞
public class Test3 {
public static void main(String[] args) throws InterruptedException {
BlockingQueue queue = new ArrayBlockingQueue(3);
queue.put("苹果");
queue.put("香蕉");
queue.put("橘子");
System.out.println(queue.take()); //取出并移除
queue.put("西瓜"); //限制存3个,第4个阻塞了
System.out.println(queue.take()); //取出并移除
System.out.println(queue);
}
}
6.4有界队列应用
//通过阻塞队列模拟生产者消费者模型
//案例:生产者和消费者个打印1~100,每次最多生产或消费4个
public class Test4 {
public static void main(String[] args) {
BlockingQueue queue = new ArrayBlockingQueue(4);
//负责生产的线程
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=100;i++) {
try {
queue.put(i);
System.out.println("已经生产了第"+i+"件货");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=100;i++) {
try {
System.out.println("已经消费了第"+i+"件货");
queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}



