
JUC是java.util.concurrent工具包,在java5时出现,是操作线程的工具包
1.2 线程与进程 1.2.1 区分进程:指系统中正在运行的应用程序,进程就是运行了的程序,进程是资源分配的最小单位
线程:系统分配处理器时间资源的基本单元,线程是程序执行的最小单元
串行:多个任务一个一个执行
并行:多个任务同时执行
并发:同一时刻多个线程访问同一个资源(抢票)
Monitor 监视器 java中也叫锁
是一种同步机制,保证同一时间只有一个线程在操作资源
jvm中的同步基于进入和退出,是使用管程对象实现的
private ReentrantLock lock = new ReentrantLock();
......
lock.lock();
try {
......
} finally {
lock.unlock();
}
......
2.4 synchronized与Lock的区别
class ANum{
private int num = 0;
public synchronized void add() {
//判断
if (num !=0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//do something
num++;
System.out.println(Thread.currentThread().getName() + "== value::" + num);
this.notify();
}
public synchronized void reduce() {
if(num != 1) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println(Thread.currentThread().getName() + "== value::" + num);
//通知其他
this.notify();
}
}
public class ThreadCommuniaction {
public static void main(String[] args) {
ANum aNum = new ANum();
new Thread(() ->{
for (int i = 0; i < 10; i++){
aNum.add();
}
},"AA").start();
new Thread(() ->{
for (int i = 0; i < 10; i++){
aNum.reduce();
}
},"BB").start();
}
}
再增加两个线程,作加和减操作,可能会出现类似的错误
AA== value::1 BB== value::0 AA== value::1 BB== value::0 AA== value::1 DD== value::0 BB== value::-1 DD== value::-2
wait的虚假唤醒:
线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:
synchronized (obj) {
while ()
obj.wait(timeout);
... // Perform action appropriate to condition
}
class Share{
private int num = 0;
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void add() {
//判断
lock.lock();
try {
if (num !=0){
condition.await();
}
//do something
num++;
System.out.println(Thread.currentThread().getName() + "== value::" + num);
//通知其他线程
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void reduce() {
lock.lock();
try {
if (num !=1){
condition.await();
}
//do something
num--;
System.out.println(Thread.currentThread().getName() + "== value::" + num);
//通知其他线程
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ThreadCommuniaction {
public static void main(String[] args) {
ANum aNum = new ANum();
Share share = new Share();
new Thread(() ->{
for (int i = 0; i < 10; i++){
share.add();
}
},"AA").start();
new Thread(() ->{
for (int i = 0; i < 10; i++){
share.reduce();
}
},"BB").start();
}
}
3.1 线程间的定制通信
三个线程A、B、C ,让线程A、线程B、线程C按照顺序输出,多次循环。
class ShareResource {
//标志位
private Integer flag = 1;
private ReentrantLock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5(int loop) {
lock.lock();
try {
while (flag != 1) {
c1.await();
}
for (int i = 0; i < 5; i++) {
System.out.println("===="+i+"===="+Thread.currentThread().getName()+" loop:"+loop);
}
//调整标志位
flag = 2;
//唤醒线程
c2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10(int loop) {
lock.lock();
try {
while (flag != 2) {
c2.await();
}
for (int i = 0; i < 10; i++) {
System.out.println("####"+i+"####"+Thread.currentThread().getName()+" loop:"+loop);
}
flag = 3;
c3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15(int loop) {
lock.lock();
try {
while (flag != 3) {
c3.await();
}
for (int i = 0; i < 15; i++) {
System.out.println("****"+i+"****"+Thread.currentThread().getName()+" loop:"+loop);
}
flag = 1;
c1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class CustomizationThread {
public static void main(String[] args) {
ShareResource resource = new ShareResource();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.print5(i);
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.print10(i);
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.print15(i);
}
},"C").start();
}
}
4、集合的线程安全
4.1 ArrayList
问题,ArrayList的add方法并不是线程安全的,多线程操作时可能会出现错误
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(() ->{
list.add(UUID.randomUUID().toString());
//在输出的时候,可能会有线程修改list,报ConcurrentModificationException错误
System.out.println(list);
}).start();
}
}
4.1.1 Vector
java.util.Vector 是线程安全的因为它的方法上修饰了synchronized
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
4.1.2 Collections
使用Collections得到一个线程安全的list,内部也是使用synchronized修饰
//获得一个线程安全的list List4.1.3 CopyonWriteArrayListlist = Collections.synchronizedList(new ArrayList<>());
CopyOnWriteArrayList4.2 HashMap与HashSet 4.2.1 CopyonWriteArraySetlist = new CopyOnWriteArrayList<>(); //CopyOnWriteArrayList内部add()方法 private transient volatile Object[] array; public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { //将上面定义的array赋值给elements Object[] elements = getArray(); int len = elements.length; //将elements中的内容复制到新的数组 这个数组长度比elements长度加一 //这样线程的写入操作和其他线程的读取操作的对象是不同的,不会报出异常 Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; //将newElements赋值给定义的array setArray(newElements); return true; } finally { lock.unlock(); } }
HashSet在多线程操作时也会出现ConcurrentModificationException错误
在CopyOnWriteArraySet中持有着CopyOnWriteArrayList实例,CopyOnWriteArraySet的操作是调用CopyOnWriteArrayList的方法。
private final CopyOnWriteArrayList4.2.2 ConcurrentHashMapal;
其实HashSet的底层就是使用HashMap保存数据的,所以HashMap也不是线程安全的
使用ConcurrentHashMap来替代HashMap在线程不安全的情景的使用