JUC-java.util.concurrent学习(一)
1、、并发问题
public class SelaPhone {
public static void main(String[] args) {
Phone phone=new Phone();
new Thread(()->{
for (int i = 0; i < 25; i++) {
phone.sela();
}
}).start();
new Thread(()->{
for (int i = 0; i < 25; i++) {
phone.sela();
}
}).start();
new Thread(()->{
for (int i = 0; i < 25; i++) {
phone.sela();
}
}).start();
new Thread(()->{
for (int i = 0; i < 25; i++) {
phone.sela();
}
}).start();
}
}
class Phone{
public int number=100;//最开始有100部手机
public void sela(){
System.out.println("卖了一部手机,还剩下"+(--number)+"部手机");
}
}
总共有100台手机,4个线程同时执行,发现顺序这写不是我们想要的
卖了一部手机,还剩下45部手机
卖了一部手机,还剩下44部手机
卖了一部手机,还剩下43部手机
卖了一部手机,还剩下58部手机
卖了一部手机,还剩下67部手机
卖了一部手机,还剩下41部手机
卖了一部手机,还剩下39部手机
卖了一部手机,还剩下42部手机
卖了一部手机,还剩下38部手机
卖了一部手机,还剩下37部手机
卖了一部手机,还剩下40部手机
卖了一部手机,还剩下36部手机
最简单的解决办法,加synchronized关键字做同步处理
class Phone{
public int number=200;//最开始有100部手机
public synchronized void sela(){
System.out.println("卖了一部手机,还剩下"+(--number)+"部手机");
}
}
结果就肯定是按顺序的了
还有一种办法就是用lock
//jdk的推荐使用方式
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
//使用lock做同步处理
class Phone2{
public int number=200;//最开始有100部手机
Lock lock = new ReentrantLock();//使用lock锁
public synchronized void sela(){
lock.lock();
try {
System.out.println("卖了一部手机,还剩下"+(--number)+"部手机");
} finally {
lock.unlock();
}
}
}
效果个使用synchronized是一样的,使用lock可以达到细粒度的同步
synchronized默认使用的也是非公平锁
公平锁:顾名思义,十分公平,先来后到
非公平锁:十分的不公平,可以插队(默认)
为什么默认使用非公平锁呢,因为如果一个线程执行10分钟,一个线程执行5秒钟,而5秒钟的线程排到了10分钟后面,会导致5秒钟的线程必须等到10分钟的线程执行完,这样其实才是不公平的,所以java里面默认使用的是非公平锁
2、synchronized和lock的区别
1、synchronized是关键字,lock是类
2、synchronized是全自动的,不可以获取到锁的状态,lock可以获取到锁的状态
3、synchronized可以自动释放锁,lock是手动释放锁
4、synchronized如果一个线程拿到锁了并且阻塞了,另一个锁就会一直等,而lock就不一定会一直等待了
5、synchronized可重入,不可以中断,非公平,lock可重入,可以中断,非公平(可以自己设置)
6、synchronized适合锁少量的代码块,lock适合锁大量的同步代码
3、生产者和消费者
执行生产操作时,先判断数量,如果数量不为0,则等待,为0则执行+1操作,然后唤醒其他线程
执行消费操作时,先判断数量,如果数量为0,则等待,不为0则执行-1操作,然后唤醒其他线程
使用synchronized
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()-> {for (int i = 0; i <10 ; i++) phone.increment();}).start();
new Thread(()-> {for (int i = 0; i <10 ; i++) phone.decrement();}).start();
}
}
class Phone{
private int number=1;
public synchronized void increment() {
while (number!=0){
//等待消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;
System.out.println("生产了一个手机,还剩下"+number+"个手机");
this.notifyAll();
}
public synchronized void decrement() {
while (number==0){
//等待生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number--;
System.out.println("消费了一个手机,还剩下"+number+"个手机");
this.notifyAll();
}
}
消费了一个手机,还剩下0个手机
生产了一个手机,还剩下1个手机
消费了一个手机,还剩下0个手机
生产了一个手机,还剩下1个手机
消费了一个手机,还剩下0个手机
生产了一个手机,还剩下1个手机
消费了一个手机,还剩下0个手机
生产了一个手机,还剩下1个手机
消费了一个手机,还剩下0个手机
生产了一个手机,还剩下1个手机
消费了一个手机,还剩下0个手机
生产了一个手机,还剩下1个手机
消费了一个手机,还剩下0个手机
这里要注意虚假唤醒问题
官网都推荐我们使用while做判断而不是使用if
使用lock
synchronized方式话就是配合wait()方法和notify()方法实现
而lock方式就是使用Condition的await()方法和signal()方法实现
public class Test2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()-> {for (int i = 0; i <10 ; i++) phone.increment();},"A").start();
new Thread(()-> {for (int i = 0; i <10 ; i++) phone.decrement();},"B").start();
new Thread(()-> {for (int i = 0; i <10 ; i++) phone.increment();},"C").start();
new Thread(()-> {for (int i = 0; i <10 ; i++) phone.decrement();},"D").start();
}
}
class Phone2{
private int number=1;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void increment() {
//这里不能使用if做判断,如果使用if做判断,现在同时有两个线程执行这个方法,会导致虚假唤醒问题
try {
lock.lock();
while (number!=0){
//等待消费
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;
System.out.println(Thread.currentThread().getName()+"生产了一个手机,还剩下"+number+"个手机");
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() {
try {
lock.lock();
while (number==0){
//等待生产
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number--;
System.out.println(Thread.currentThread().getName()+"消费了一个手机,还剩下"+number+"个手机");
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
使用Condition做精准唤醒
现在有4个线程ABCD,我们想A执行完了唤醒B,B执行完了唤醒C,C执行完了唤醒D,D执行完了唤醒A
public class Test3 {
public static void main(String[] args) {
Phone3 phone = new Phone3();
new Thread(() -> {
for (int i = 0; i < 10; i++)
phone.printA();
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++)
phone.printB();
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++)
phone.printC();
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++)
phone.printD();
}, "D").start();
}
}
class Phone3 {
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
Condition condition4 = lock.newCondition();
private int num = 1;
public void printA() {
try {
lock.lock();
while (num != 1) {
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num = 2;
System.out.println(Thread.currentThread().getName() + "A");
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
try {
lock.lock();
while (num != 2) {
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num = 3;
System.out.println(Thread.currentThread().getName() + "B");
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
try {
lock.lock();
while (num != 3) {
try {
condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num = 4;
System.out.println(Thread.currentThread().getName() + "C");
condition4.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printD() {
try {
lock.lock();
while (num != 4) {
try {
condition4.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num = 1;
System.out.println(Thread.currentThread().getName() + "D");
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
AA
BB
CC
DD
AA
BB
CC
DD
AA
BB
CC
DD
AA
BB
CC
DD
8锁现象
1、问题1
一个对象,用A线程去发短信,然后休息1秒,再用B线程去打电话,问输出的结果
public class TestA {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->phone.send(),"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->phone.call(),"B").start();
}
}
class Phone{
public synchronized void send(){
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
结果是:发短信–》打电话
synchronized修饰的方法锁的是调用者,也就是phone,谁先拿到谁先执行
2、问题2
一个对象,用A线程去发短信(发短信的方法里面休息3秒),然后休息1秒,再用B线程去打电话,问输出的结果
public class TestB {
public static void main(String[] args) throws InterruptedException {
Phone2 phone = new Phone2();
new Thread(()->phone.send(),"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->phone.call(),"B").start();
}
}
class Phone2{
public synchronized void send(){
System.out.println("发短信");
try {
TimeUnit.SECONDS.sleep(3);//方法里面休息3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void call(){
System.out.println("打电话");
}
}
结果是:发短信–》打电话
synchronized修饰的方法锁的是调用者,也就是phone,谁先拿到谁先执行
3、问题3
Phone增加一个普通的方法,线程B来调用这个方法,问谁先打印
public class TestC {
public static void main(String[] args) throws InterruptedException {
Phone3 phone = new Phone3();
new Thread(()->phone.send(),"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->phone.print(),"B").start();
}
}
class Phone3{
public synchronized void send(){
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
public void print(){
System.out.println("打印方法");
}
}
结果是:打印方法–》发短信
没有加synchronized,不受同步影响
4、问题4
现在有两个Phone,问执行结果
public class TestD {
public static void main(String[] args) throws InterruptedException {
Phone4 phone = new Phone4();
Phone4 phone1 = new Phone4();
new Thread(()->phone.send(),"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->phone1.call(),"B").start();
}
}
class Phone4{
public synchronized void send(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
结果是:打电话–》发短信
synchronized锁的是调用者,这里有两个phone,有两把锁,
5、问题5
一个对象,增加了两个静态同步方法,问输出结果
public class TestE {
public static void main(String[] args) throws InterruptedException {
Phone5 phone = new Phone5();
new Thread(()->phone.send(),"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->phone.call(),"B").start();
}
}
class Phone5{
public static synchronized void send(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
结果是:发短信–》打电话
使用了static,就是在类一加载就有了,所以静态方法锁的是class,class全局唯一,所以只有一把锁
6、问题6
两个对象,增加了两个静态同步方法,问输出结果
public class TestF {
public static void main(String[] args) throws InterruptedException {
Phone6 phone = new Phone6();
Phone6 phone1 = new Phone6();
new Thread(()->phone.send(),"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->phone1.call(),"B").start();
}
}
class Phone6{
public static synchronized void send(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
结果是:发短信–》打电话
加了static,锁的是class,所以只有一把锁
7、问题7
一个对象,一个静态同步方法,一个普通同步方法,问输出结果
public class TestG {
public static void main(String[] args) throws InterruptedException {
Phone7 phone = new Phone7();
new Thread(()->phone.send(),"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->phone.call(),"B").start();
}
}
class Phone7{
//静态的同步方法
public static synchronized void send(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通的同步方法
public synchronized void call(){
System.out.println("打电话");
}
}
结果是:打电话–》发短信
send方法加了static,锁的是class,而call方法锁的是phone对象,是两把锁
8、问题8
一个对象,一个静态同步方法,一个普通同步方法,问输出结果
public class TestH {
public static void main(String[] args) throws InterruptedException {
Phone8 phone = new Phone8();
Phone8 phone1 = new Phone8();
new Thread(()->phone.send(),"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->phone1.call(),"B").start();
}
}
class Phone8{
//静态的同步方法
public static synchronized void send(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通的同步方法
public synchronized void call(){
System.out.println("打电话");
}
}
结果是:打电话–》发短信
send方法加了static,锁的是class,而call方法锁的是phone对象,是两把锁
总结:static修饰锁的是class,普通的同步方法锁的是对象
常用的数组集合
ArrayList
public class ArrayListaTest {
public static void main(String[] args) {
List<String> list =new ArrayList();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add("434343");
System.out.println(list);
}).start();
}
}
}
多次执行,发现可能会报ConcurrentModificationException异常,并发修改异常
可以使用
List<String> list =Collections.synchronizedList(new ArrayList<>());
List<String> list =new CopyOnWriteArrayList<>();
List<String> list =new Vector<>();
HashSet
public class HashSetTest {
public static void main(String[] args) throws InterruptedException {
//Set<String> list = Collections.synchronizedSet(new HashSet<>());
//Set<String> list =new CopyOnWriteArraySet<>();
//Set<String> list =new HashSet<>();
Set<String> list =new CopyOnWriteArraySet<>();
for (int i = 0; i < 20; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,4));
System.out.println(list);
}).start();
}
}
}
hashset的add方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();不变的
HashMap
public class HasMapTest {
public static void main(String[] args) throws InterruptedException {
//Map<String,String> list = Collections.synchronizedMap(new HashMap<>());
//Set<String> list =new HashMap<>();
Map<String,String> list = new ConcurrentHashMap<>();
for (int i = 0; i < 20; i++) {
new Thread(()->{
list.put(UUID.randomUUID().toString().substring(0,4),"23434");
System.out.println(list);
}).start();
}
}
}
在java.util,concurrent包下面有很多的线程安全的