List的线程安全问题
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @ClassName VectorTest
* @Author xsshuai
* @Date 2022/3/3 3:28 PM
**/
public class VectorTest {
public static void main(String[] args) {
//线程不安全
//List<String> list = new ArrayList<>();
//使用Vector创建线程安全List
//List<String> list = new Vector<>();
//调用Collections的静态方法创建线程安全List
//List<String> list = Collections.synchronizedList(list);
//使用CopyOnWriteArrayList创建线程安全List
List<String> list = new CopyOnWriteArrayList<>();
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();
}
}
}
Set的线程安全问题
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @ClassName VectorTest
* @Author xsshuai
* @Date 2022/3/3 3:28 PM
**/
public class VectorTest {
public static void main(String[] args) {
//线程不安全
Set<String> set = new HashSet<>();
//使用CopyOnWriteArraySet创建线程安全set
//Set<String> 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();
}
}
}
Map的线程安全问题
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassName VectorTest
* @Author xsshuai
* @Date 2022/3/3 3:28 PM
**/
public class VectorTest {
public static void main(String[] args) {
//线程不安全
//Map<String, String> map = new HashMap<>();
//使用ConcurrentHashMap创建线程安全Map
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
String key = String.valueOf(i);
new Thread(()->{
map.put(key, UUID.randomUUID().toString().substring(0,8));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}
synchronized关键字的说明
synchronized是实现同步的基础,Java中的每一个对象都可以作为锁,具体表现为以下3种形式。
1、对于普通同步方法,锁是当前类的实例对象
2、对于静态同步方法,锁是当前类的class对象
3、对于同步代码块,锁是synchronized括号里配置的对象
公平锁和非公平锁
非公平锁:可能有的线程没有机会运行,出现线程“饿死”的情况,但效率高
公平锁:所有线程都有机会被执行,但是效率低
可重入锁
synchronized和Lock都是可重入锁,synchronized是隐式的,Lock是显式的,可重入锁的特点是线程一旦获得可重入锁,就可以在同步代码块中畅通执行,不需要在去获得锁。
死锁
两个或两个以上的线程执行过程中,一个线程已经持有一部分资源而等待其他线程释放另一部分资源,形成互相等待的现象,如果没有外力干涉,死锁不能自己结束。
产生死锁的原因:1、系统的资源不足2、线程的推进顺序不当3、资源分配不当
死锁演示
import java.util.concurrent.TimeUnit;
/**
* @ClassName DeadLock
* @Author xsshuai
* @Date 2022/3/7 1:25 PM
**/
public class DeadLock {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
new Thread(()-> {
synchronized (a) {
System.out.println("线程" + Thread.currentThread().getName() + "持有资源a,并试图换取资源b");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println("线程" + Thread.currentThread().getName() + "获取到资源b");
}
}
}, "A").start();
new Thread(()-> {
synchronized (b) {
System.out.println("线程" + Thread.currentThread().getName() + "持有资源b,并试图换取资源a");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
System.out.println("线程" + Thread.currentThread().getName() + "获取到资源a");
}
}
}, "B").start();
}
}
死锁的发现
使用jps -l命令可以发现程序所有进程的运行情况,是否产生死锁
使用jstack 进程号 可以发现具体死锁产生的原因
并发编程辅助类
CountDownLatch减少计数
等待所有线程执行完,在进行某项操作
import java.util.concurrent.CountDownLatch;
/**
* @ClassName CountDownLatchDemo
* @Author xsshuai
* @Date 2022/3/7 4:07 PM
**/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(()-> {
System.out.println(Thread.currentThread().getName() + "号同学离开教室");
//count计数减1
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//count计数没有到0则等待
countDownLatch.await();
System.out.println("所有同学离开,班长锁门");
}
}
CyclicBarrier循环栅栏
一组线程相互等待,等所有线程执行完,在执行某项操作。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @ClassName CyclicBarrierDemo
* @Author xsshuai
* @Date 2022/3/7 4:26 PM
**/
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()->{
System.out.println("7龙珠已集齐,神龙召唤成功,你可以许一个愿望");
});
for (int i = 1; i <= 7; i++) {
new Thread(()->{
System.out.println("恭喜你已找到" + Thread.currentThread().getName() + "星龙珠");
try {
//到达屏障值执行Runnable中操作,否则一直等待
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
Semaphore信号灯,多个线程抢占有限的资源
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* @ClassName SemaphoreDemo
* @Author xsshuai
* @Date 2022/3/7 4:53 PM
**/
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3, true);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
try {
//抢占一个车位
semaphore.acquire();
System.out.println("车辆" + Thread.currentThread().getName() + "抢占到车位");
//设置随机停车时间
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println("车辆" + Thread.currentThread().getName() + "---------释放车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放当前车位
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
锁的分类
乐观锁和悲观锁:
乐观锁:线程获取数据时会同时获取数据的版本号,线程提交修改数据时会将之前获取版本号和现在的版本号对比,如果版本一致,修改成功,如果版本不一致,则重新修改再提交,乐观锁可以并发执行,效率高。
悲观锁:线程获取数据时会先加锁,线程操作完其他线程才能获取并操作,悲观锁不能病发执行,效率低。
表锁和行锁
表锁:表锁会对整个表加锁,其他线程无法对整张表进行操作,表锁不会产生死锁
行锁:只对某行数据加锁,其他线程可以操作其它行数据,行锁会产生死锁
读锁和写锁
读锁:共享锁,会产生死锁
写锁:独占锁,会产生死锁
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @ClassName ReadWriteLockDemo
* @Author xsshuai
* @Date 2022/3/7 7:08 PM
**/
class MyCache {
private volatile Map<String, Object> cache = new HashMap<>();
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "正在写入数据 " + key);
TimeUnit.MILLISECONDS.sleep(300);
cache.put(key, value);
System.out.println(Thread.currentThread().getName() + "已写入数据 " + key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwLock.writeLock().unlock();
}
}
public Object get(String key) {
rwLock.readLock().lock();
Object value = null;
try {
System.out.println(Thread.currentThread().getName() + "正在读数据 " + key);
TimeUnit.MILLISECONDS.sleep(300);
value = cache.get(key);
System.out.println(Thread.currentThread().getName() + "读到数据 " + value);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwLock.readLock().unlock();
}
return value;
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i <= 5; i++) {
String s = String.valueOf(i);
new Thread(()->{
myCache.put(s, s);
}, "写进程" + String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
String s = String.valueOf(i);
new Thread(()->{
myCache.get(s);;
},"读进程" + String.valueOf(i)).start();
}
}
}
读写锁:一个资源可以被多个读线程访问,或者可以背一个写线程访问,但是不能同时存在读写线程,读写线程是互斥的,读读线程是共享的。
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
/**
* @ClassName ForkJoinDemo
* @Author xsshuai
* @Date 2022/3/7 10:41 PM
**/
class MyTask extends RecursiveTask<Integer> {
private static final int VALUE = 10;
private int begin;
private int end;
private int result;
public MyTask(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
protected Integer compute() {
if (end - begin <= VALUE) {
for (int i = begin; i <= end; i++) {
result = result + i;
}
}else {
int middle = (begin + end) / 2;
MyTask task1 = new MyTask(begin, middle);
MyTask task2 = new MyTask(middle + 1, end);
task1.fork();
task2.fork();
result = task1.join() + task2.join();
}
return result;
}
}
public class ForkJoinDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyTask myTask = new MyTask(1, 100);
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(myTask);
Integer result = forkJoinTask.get();
System.out.println(result);
forkJoinPool.shutdown();
}
}