两个线程,一个线程打印1-52,另一个打印字母A-Z打印顺序为12A34B...5152Z,
要求用线程间通信
1.synchronized实现
package com.liuyuanyuan.thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.omg.IOP.Codec;
class ShareDataOne//资源类
{
private int number = 0;//初始值为零的一个变量
public synchronized void increment() throws InterruptedException
{
//1判断
if(number !=0 ) {
this.wait();
}
//2干活
++number;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//3通知
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException
{
// 1判断
if (number == 0) {
this.wait();
}
// 2干活
--number;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3通知
this.notifyAll();
}
}
/**
*
* @Description:
*现在两个线程,
* 可以操作初始值为零的一个变量,
* 实现一个线程对该变量加1,一个线程对该变量减1,
* 交替,来10轮。
* @author liuyuan
*
* * 笔记:Java里面如何进行工程级别的多线程编写
* 1 多线程变成模板(套路)-----上
* 1.1 线程 操作 资源类
* 1.2 高内聚 低耦合
* 2 多线程变成模板(套路)-----下
* 2.1 判断
* 2.2 干活
* 2.3 通知
*/
public class NotifyWaitDemoOne
{
public static void main(String[] args)
{
ShareDataOne sd = new ShareDataOne();
new Thread(() -> {
for (int i = 1; i < 10; i++) {
try {
sd.increment();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i < 10; i++) {
try {
sd.decrement();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "B").start();
}
}
/*
* *
* 2 多线程变成模板(套路)-----下
* 2.1 判断
* 2.2 干活
* 2.3 通知
* 3 防止虚假唤醒用while
*
*
* */
换成4个线程会导致错误,虚假唤醒
原因:在java多线程判断时,不能用if,程序出事出在了判断上面,
突然有一添加的线程进到if了,突然中断了交出控制权,
没有进行验证,而是直接走下去了,加了两次,甚至多次
如上飞机,突然说了飞机上有炸弹,下飞机,安置住宿,当再次登机的时候还是需要进行安检的。
如何解决呢 ?
解决虚假唤醒:查看API,java.lang.Object
中断和虚假唤醒是可能产生的,所以要用loop循环,if只判断一次,while是只要唤醒就要拉回来再判断一次。if换成while
以上是用的synchronized来实现的
下面我们用jdk1.8的更好的实现方式
思路很简单
接下来是具体的代码实现
Condition:查看API,java.util.concurrent
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
2、线程间定制化调用通信
1、有顺序通知,需要有标识位
2、有一个锁Lock,3把钥匙Condition
3、判断标志位
4、输出线程名+第几次+第几轮
5、修改标志位,通知下一个
以上可能比较晦涩,接下来看具体的代码实现
package com.liuyuanyuan.thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareResource
{
private int number = 1;//1:A 2:B 3:C
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5(int totalLoopNumber)
{
lock.lock();
try
{
//1 判断
while(number != 1)
{
//A 就要停止
c1.await();
}
//2 干活
for (int i = 1; i <=5; i++)
{
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoopNumber: "+totalLoopNumber);
}
//3 通知
number = 2;
c2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10(int totalLoopNumber)
{
lock.lock();
try
{
//1 判断
while(number != 2)
{
//A 就要停止
c2.await();
}
//2 干活
for (int i = 1; i <=10; i++)
{
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoopNumber: "+totalLoopNumber);
}
//3 通知
number = 3;
c3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15(int totalLoopNumber)
{
lock.lock();
try
{
//1 判断
while(number != 3)
{
//A 就要停止
c3.await();
}
//2 干活
for (int i = 1; i <=15; i++)
{
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoopNumber: "+totalLoopNumber);
}
//3 通知
number = 1;
c1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
/**
*
* @Description:
* 多线程之间按顺序调用,实现A->B->C
* 三个线程启动,要求如下:
*
* AA打印5次,BB打印10次,CC打印15次
* 接着
* AA打印5次,BB打印10次,CC打印15次
* ......来10轮
* @author liuyuan
*
*/
public class ThreadOrderAccess
{
public static void main(String[] args)
{
ShareResource sr = new ShareResource();
new Thread(() -> {
for (int i = 1; i <=10; i++)
{
sr.print5(i);
}
}, "AA").start();
new Thread(() -> {
for (int i = 1; i <=10; i++)
{
sr.print10(i);
}
}, "BB").start();
new Thread(() -> {
for (int i = 1; i <=10; i++)
{
sr.print15(i);
}
}, "CC").start();
}
}
3.NotSafeDemo
需求:请举例说明集合类是不安全的
java.util.ConcurrentModificationException
ArrayList在迭代的时候如果同时对其进行修改就会
抛出java.util.ConcurrentModificationException异常
并发修改异常
List list = new ArrayList<>();
for (int i = 0; i <30 ; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
看ArrayList的源码
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
没有synchronized线程不安全
如何解决呢 ?
List list = new Vector<>();
看Vector的源码
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
有synchronized线程安全
List
Collections提供了方法synchronizedList保证list是同步线程安全的
那HashMap,HashSet是线程安全的吗?也不是
所以有同样的线程安全方法
终极大boss出来了:写时复制
List
接下来介绍一下写时复制
写时复制为了解决:不加锁性能提升出错误,加锁数据一致性能下降问题
接下来介绍CopyOnWriteArrayList
A thread-safe variant of ArrayList in which all mutative operations (add, set, and so on) are implemented by making a fresh copy of the underlying array.
CopyOnWriteArrayList是arraylist的一种线程安全变体,
其中所有可变操作(add、set等)都是通过生成底层数组的新副本来实现的。
复制了一份,往里追加写
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,
而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后向新的容器Object[] newElements里添加元素。
添加元素后,再将原容器的引用指向新的容器setArray(newElements)。
这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
对于set和map有写时复制技术么?
当然是有的
Set set = new HashSet<>();//线程不安全
Set set = new CopyOnWriteArraySet<>();//线程安全
HashSet底层数据结构是什么?
HashMap ?
但HashSet的add是放一个值,而HashMap是放K、V键值对
public HashSet() {
map = new HashMap<>();
}
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
Map map = new HashMap<>();//线程不安全
Map map = new ConcurrentHashMap<>();//线程安全
package com.liuyuanyuan.gmall.jucdemo;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 请举例说明集合类是不安全的
*/
public class NotSafeDemo {
public static void main(String[] args) {
Map map = new ConcurrentHashMap<>();
for (int i = 0; i <30 ; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
System.out.println(map);
},String.valueOf(i)).start();
}
}
private static void setNoSafe() {
Set set = new CopyOnWriteArraySet<>();
for (int i = 0; i <30 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(set);
},String.valueOf(i)).start();
}
}
private static void listNoSafe() {
// List list = Arrays.asList("a","b","c");
// list.forEach(System.out::println);
//写时复制
List list = new CopyOnWriteArrayList<>();
// new CopyOnWriteArrayList<>();
//Collections.synchronizedList(new ArrayList<>());
//new Vector<>();//new ArrayList<>();
for (int i = 0; i <30 ; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
/**
* 写时复制
CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,
而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后向新的容器Object[] newElements里添加元素。
添加元素后,再将原容器的引用指向新的容器setArray(newElements)。
这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
*
*
*
*
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
*/