目录
线程同步机制
线程同步形成条件:队列+锁 synchronized
存在的问题:
1,降低性能
2,优先级高的等待一个优先级低的线程释放锁,就会导致性能倒置
三大不安全案例
买票案例
public class BuyTicket implements Runnable{
boolean flag=true;
private int ticketNum=10;//定义票数
@Override
public void run() {
while (true){
buy();
}
}
public static void main(String[] args) {
BuyTicket testThread1=new BuyTicket();
//多个线程使用一个,第二个参数,可以给线程命名
new Thread(testThread1,"张三").start();
new Thread(testThread1,"李四").start();
new Thread(testThread1,"王麻子").start();
}
private void buy(){
//如果票没了,就停止
if(ticketNum<=0){
flag=false;
return;
}
//模拟一个延时,否则太快,看不出来同步执行
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程名字Thread.currentThread().getName()
System.out.println(Thread.currentThread().getName()+"买到了第"+ticketNum--+"张票");
}
}
运行结果
银行取钱
public class UnsafeBank {
public static void main(String[] args) {
//帐户
Account account=new Account(1000,"基金");
Drawing you=new Drawing(account,500,"你");
Drawing friend=new Drawing(account,1000,"朋友");
you.start();
friend.start();
}
}
//帐户
class Account{
int money;//余额
String name;//卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟取钱
class Drawing extends Thread{
Account account;//取钱帐户
//取了多少钱
int drawingMoney;
//现在手里多少钱
int nowMoney;
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱操作
@Override
public void run() {
//判断有不有钱,够不够
if(account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够");
return;
}
//放大问题
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//取钱后,帐户的余额
account.money=account.money-drawingMoney;
//现在手里有的钱
nowMoney=nowMoney+drawingMoney;
//打印帐户余额
System.out.println(account.name+"余额:"+account.money);
//打印手里的钱 this.getName()=Thread.currentThread().getName()
System.out.println(this.getName()+"手里的钱:"+nowMoney);
}
}
线程不安全集合ArrayList
同时操作会覆盖
public class UnsafeList {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
同步方法及同步块
synchronized同步方法,加到方法上,锁的是this
买票案例
同步块synchronized(Obj){}
Obj称之为同步监视器
1,Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
2,同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this(本身),或者class
同步监视器执行过程
1,第一个线程访问,锁定同步监视器,执行其中代码
2,第二个线程访问,发现同步监视器被锁定,无法访问
3,第一个线程访问完毕,解锁同步监视器
4,第二个线程访问、发现同步监视器没有锁,然后锁定并访问
银行取钱案例
锁不安全的量,增删改的数据
线程不安全集合案例
线程不安全集合,以及解决
线程不安全的集合主要包括ArrayList、LinkedList、HashMap、HashSet、TreeSet、TreeMap等。这些集合在多线程环境下使用时,可能会出现数据不一致的问题,因为它们没有提供任何同步机制来确保数据的一致性和完整性。例如,当多个线程同时修改这些集合时,可能会出现并发修改异常(java.util.ConcurrentModificationException),导致数据修改异常。
为了解决这些问题,可以采取以下几种策略:
-
使用线程安全的集合:Java的
java.util.concurrent
包提供了一些并发容器,如ConcurrentHashMap
、CopyOnWriteArrayList
、ConcurrentLinkedQueue
和BlockingQueue
等,这些并发容器提供了线程安全的实现,适用于多线程环境。 -
使用同步集合:通过
Collections.synchronizedList(new ArrayList<>())
或Collections.synchronizedMap()
等方法对普通集合进行包装,使其成为线程安全的集合。但这种方法可能会导致性能下降,因为每次访问都需要获取锁。 -
使用写时复制的集合:如
CopyOnWriteArrayList
,它在修改时复制一份新的数据结构进行操作,从而保证了读操作的线程安全。这种方法的优点是读操作不需要加锁,适用于读多写少的场景。 -
避免在多线程环境中直接操作这些集合:如果可能,应尽量避免在多线程环境中直接操作这些非线程安全的集合,而是通过单线程处理或使用其他同步机制来确保数据的一致性。
综上所述,选择合适的并发容器或采取适当的同步措施是确保多线程环境下集合操作正确性的关键
测试JUC安全类型的集合
import java.util.concurrent.CopyOnWriteArrayList;
//测试JUC线程安全类型的集合
public class TestThread14JUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<String>();
for (int i= 0; i< 10000; i++) {
new Thread(()->list.add(Thread.currentThread().getName())).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
原学习视频