JUC-java.util.concurrent学习(一)

本文围绕JUC-java.util.concurrent展开学习。探讨了并发问题及解决办法,对比了synchronized和lock的区别,介绍了生产者和消费者问题的不同实现方式,包括使用synchronized和lock,还分析了8锁现象,最后提及常用数组集合在并发场景下的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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包下面有很多的线程安全的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值