多线程-线程同步(五)
-
多线程的形成条件:队列+锁(为了安全)
-
线程同步安全,我们先来看下线程的不安全
-
线程不安全的例子一:
//不安全的买票 //线程不安全,有负数 public class UnsafeBuyTicket { public static void main(String[] args) { BuyTicket station=new BuyTicket(); new Thread(station,"苦逼的我").start(); new Thread(station,"牛逼的你们").start(); new Thread(station,"可恶的黄牛党").start(); } } class BuyTicket implements Runnable{ //票 private int ticketNums=10; boolean flag=true;//外部停止方式 @Override public void run() { //买票 while (flag){ try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } private void buy() throws InterruptedException { //判断是否有票 if (ticketNums<=0){ flag =false; return; } //模拟延时 Thread.sleep(100); //买票 System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--); } }
-
线程不安全的例子一,出现-1的情况原理图:
-
-
线程不安全的例子二:
//不安全的取钱 //两个人去银行取钱,账户 public class UnsafeBank { public static void main(String[] args) { //账户 Account account=new Account(100,"结婚基金"); Drawing you=new Drawing(account,50,"你"); Drawing girlFriend=new Drawing(account,100,"girlFriend"); you.start(); girlFriend.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; } //sleep可以放大问题的发生性 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //卡内余额= 余额-你取的钱 account.money = account.money-drawingMoney; //你手里的钱 nowMoney = nowMoney+drawingMoney; System.out.println(account.name+"余额为:"+account.money); //Thread.currentThread().getName()=this.getName() //获取线程名字 System.out.println(this.getName()+"手里的钱:"+nowMoney); } }
-
线程不安全的例子二,出现余额-50的情况原理图:
-
线程不安全的例子三:
import java.util.ArrayList; import java.util.List; //线程不安全的集合 public class UnsafeList { public static void main(String[] args) { List<String> list=new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ //不安全的原因:因为2个线程同一个瞬间操作了同一个位置,把两个数组添加了同一个位置就把它覆盖掉了,覆盖掉了,元素就会少,少的元素就是这么来的 list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(3000);//让他睡3秒,放大问题的发生性,还是打印不到1w个线程 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size());//线程的大小没能够按照理论的打印1w个线程 } }
-
线程不安全的原因:
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。说白了,就是线程的内存都是各自的,每个线程其实都会出现系统的内存有多少,就会出现内存为负的情况,这也就导致了线程的不安全。
-
解决线程不安全的问题是:用同步方法
-
我们针对这个方法提出了一套机制,这套机制就是synchronized关键字,他包括两种方法:synchronized方法和synchronized块
同步方法:public synchronized void method(int args){}
-
synchronized方法控制对"对象"的访问,每个对象对应一把锁,每个synchronized方法都必须调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法申明为synchronized将会影响效率
-
- 同步块:synchronized(Obj){}
- Obj称之为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class[反射中讲解]
- 同步监视器的执行过程:和synchronized方法的原理是一样的
-
解决线程不安全的例子一,在buy()方法那里加synchronized关键字,把他变成synchronized同步方法。
//不安全的买票 //线程不安全,有负数 public class UnsafeBuyTicket { public static void main(String[] args) { BuyTicket station=new BuyTicket(); new Thread(station,"苦逼的我").start(); new Thread(station,"牛逼的你们").start(); new Thread(station,"可恶的黄牛党").start(); } } class BuyTicket implements Runnable{ //票 private int ticketNums=10; boolean flag=true;//外部停止方式 @Override public void run() { //买票 while (flag){ try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } //synchronized 同步方法,锁的是this private synchronized void buy() throws InterruptedException { //判断是否有票 if (ticketNums<=0){ flag =false; return; } //模拟延时 Thread.sleep(100); //买票 System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--); } }
-
解决线程不安全的例子二,锁的是account的一个同步块才能解决。
//不安全的取钱 //两个人去银行取钱,账户 public class UnsafeBank { public static void main(String[] args) { //账户 Account account=new Account(100,"结婚基金"); Drawing you=new Drawing(account,50,"你"); Drawing girlFriend=new Drawing(account,100,"girlFriend"); you.start(); girlFriend.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; } //取钱 //synchronized 默认锁的是this @Override public void run() { synchronized (account){ //这里只有锁的是account的同步块才有用,锁run()方法是没用的 //判断有没有钱 if (account.money-drawingMoney<0){ System.out.println(Thread.currentThread().getName()+"钱不够,取不了"); return; } //sleep可以放大问题的发生性 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //卡内余额= 余额-你取的钱 account.money = account.money-drawingMoney; //你手里的钱 nowMoney = nowMoney+drawingMoney; System.out.println(account.name+"余额为:"+account.money); //Thread.currentThread().getName()=this.getName() //获取线程名字 System.out.println(this.getName()+"手里的钱:"+nowMoney); } } }
-
解决线程不安全的例子三,用一个同步块锁list。
import java.util.ArrayList; import java.util.List; //线程不安全的集合 public class UnsafeList { public static void main(String[] args) { List<String> list=new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ //不安全的原因:因为2个线程同一个瞬间操作了同一个位置,把两个数组添加了同一个位置就把它覆盖掉了,覆盖掉了,元素就会少,少的元素就是这么来的 synchronized (list){ list.add(Thread.currentThread().getName()); } }).start(); } try { Thread.sleep(3000);//让他睡3秒,放大问题的发生性,还是打印不到1w个线程 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size());//线程的大小没能够按照理论的打印1w个线程 } }
-
补充:JUC安全类型的集合,也能解决一些线程安全的问题。
import java.util.concurrent.CopyOnWriteArrayList;
//测试JUC安全类型的集合
public class TestJUC {
public static void main(String[] args) {
//concurrent并发包下写好的,安全的ArrayList
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());
}
}