线程同步
-
并发:同一个对象被多个线程同时操作。例如车站抢票,银行取钱。
-
队列:处理多线程问题时,多个线程访问同一个对象。并且某些线程还想修改这个对象。这时候我们就需要线程同步,线程同步就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
-
锁:每个对象都有一个锁,由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问的正确性,在访问时加入了锁机制synchronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即刻,存在以下问题
-
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多个线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。
-
线程不安全的的三大示例
package com.cnblo.www.synchron;public class UnSafeBuyTickety {public static void main(String args[]) { Tickest cz=new Tickest();Thread t1=new Thread(cz,"小明");Thread t2=new Thread(cz,"小红");Thread t3=new Thread(cz,"小强");t1.start();t2.start();t3.start();}
}class Tickest implements Runnable {private int tickets=10;boolean flags=true;@Overridepublic void run() { while(flags) { try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}buy();}}private void buy() {if(tickets<=0) {flags=false; return;} System.out.println(Thread.currentThread().getName()+"购买了第"+tickets--+"张票!!!");}}
/*小红购买了第10张票!!!
小明购买了第8张票!!!
小强购买了第9张票!!!
小红购买了第7张票!!!
小强购买了第6张票!!!
小明购买了第6张票!!!
小强购买了第5张票!!!
小红购买了第5张票!!!
小明购买了第4张票!!!
小强购买了第3张票!!!
小红购买了第2张票!!!
小明购买了第1张票!!!*/
package com.cnblo.www.synchron;public class UnsafeBank {public static void main(String args[]) {Accunt ac=new Accunt("农行",5000);Drawing a=new Drawing(ac,1000,"小红");Drawing b=new Drawing(ac,4500,"小明");a.start();b.start();}
}
//账户class Drawing extends Thread {Accunt ac;int Drawingmoney;int nowmoney;public Drawing(Accunt ac, int Drawingmoney,String name) {super(name);this.ac = ac;this.Drawingmoney = Drawingmoney; }@Overridepublic void run() {// TODO Auto-generated method stubif(ac.count-Drawingmoney<0){ System.out.println(this.getName()+"取"+Drawingmoney+"失败越不够");return;}System.out.println(this.getName()+"取"+Drawingmoney+"成功");ac.count=ac.count-Drawingmoney;nowmoney=nowmoney+Drawingmoney; System.out.println(ac.name+"账号余额为"+ac.count);System.out.println(this.getName()+"现持有"+nowmoney);}
}
class Accunt{String name; //账号名称int count ; //余额public Accunt(String name, int count) {this.name = name;this.count = count;}
}
/*小明取4500成功
小红取1000成功
农行账号余额为-500
农行账号余额为500
小明现持有4500
小红现持有1000*/
package com.cnblo.www.synchron;import java.util.ArrayList;
//ArrayList是不安全的
public class UnSafeList {public static void main(String args[]) {ArrayList al=new ArrayList();for (int i=0;i<1000;i++) {new Thread( ()->{al.add(Thread.currentThread().getName());}).start();}System.out.println(al.size());System.out.println(al.get(0));}
}
/*994
Thread-1
*/
同步方法
- 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要争对方法提出一套机制。这套机制就是synchronized,它包括两种用法synchronized方法和synchronized块。
- synchronized方法控制对"对象"的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞。方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程得到这个锁,继续执行
//同步方法
public synchronized void method(int arg){}
注意:若将一个方法声明为synchronized将会影响效率;
方法里面需要修改的内容才需要锁,锁的太多浪费资源
同步块(隐式的锁):synchronized(obj){}
- obj称为同步监视器
-
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器就是this,就是这个对象本身,或者是class[反射中讲解]
- 同步监视器的执行过程
-
- 第一个线程访问,锁定同步监视器,执行其中的代码。
- 第二个线程访问,发现同步监视器被锁定,无法访问。
- 第一个线程访问完毕,解锁同步监视器。
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
package com.cnblo.www.synchron;public class safeBank {public static void main(String args[]) {Accunt ac=new Accunt("农行",5000);Drawing a=new Drawing(ac,1000,"小红");Drawing b=new Drawing(ac,4500,"小明");a.start();b.start();}
}
//账户class Drawing extends Thread {Accunt ac;int Drawingmoney;int nowmoney;public Drawing(Accunt ac, int Drawingmoney,String name) {super(name);this.ac = ac;this.Drawingmoney = Drawingmoney; }@Overridepublic synchronized void run() {// TODO Auto-generated method stubsynchronized(ac) { ///不能用来锁方法run方法是,因为锁的还是对象是this,run方法对应的Drawing这个类,并不是Accunt;所以只能锁对象//锁的是变化的量if(ac.count-Drawingmoney<0){ System.out.println(this.getName()+"取"+Drawingmoney+"失败越不够");return;}System.out.println(this.getName()+"取"+Drawingmoney+"成功");ac.count=ac.count-Drawingmoney;nowmoney=nowmoney+Drawingmoney; System.out.println(ac.name+"账号余额为"+ac.count);System.out.println(this.getName()+"现持有"+nowmoney);}}
}
class Accunt{String name; //账号名称int count ; //余额public Accunt(String name, int count) {this.name = name;this.count = count;}
}
package com.cnblo.www.synchron;import java.util.ArrayList;
//ArrayList是不安全的,加锁后
public class SafeList {public static void main(String args[]) {ArrayList al=new ArrayList();for (int i=0;i<1000;i++) {new Thread( ()->{synchronized(al) {al.add(Thread.currentThread().getName());}}).start();}System.out.println(al.size());System.out.println(al.get(0));}
}
/*994
Thread-1
*/
package com.cnblo.www.synchron;
import java.util.concurrent.CopyOnWriteArrayList;//测试JUC安全的集合,CopyOnWriteArrayList 属于java下面并发编程的一个安全的稽核,测试
public class TestJUC {public static void main(String args[]) throws InterruptedException {CopyOnWriteArrayList <String> cal=new CopyOnWriteArrayList <String>();for(int i=0;i<1000;i++){new Thread(()->{cal.add(Thread.currentThread().getName());}).start();Thread.sleep(10);}System.out.println(cal.size());System.out.println(cal.get(999));}}
死锁
-
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程在等待对方释放资源,都停止执行的情形。某一个同步代码块同时拥有两个以上的对象的锁时(感觉就是同步看块中还有同步块锁了其他对象),就可能发生死锁的问题
-
产生死锁的四个必要条件:
-
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之前形成一种头尾相接的循环等待资源关系
public class DeadLock {public static void main(String args[]) {Daba d1=new Daba(1,"小明");Daba d2=new Daba(2,"小红");d1.start();d2.start();}
}
class Gans
{
}
class Zidan
{
}
class Daba extends Thread
{static private Gans gan=new Gans();static private Zidan zd=new Zidan();int choces=0;public Daba(int choces,String name) {super(name);this.choces = choces;}public Gans getGan() {return gan;}public void setGan(Gans gan) {this.gan = gan;}public Zidan getZd() {return zd;}public void setZd(Zidan zd) {this.zd = zd;}@Overridepublic void run() {Dabading();}public synchronized void Dabading(){ if(choces==1) {synchronized (gan) {System.out.println(this.getName()+"获取枪的锁");System.out.println(this.getName()+"等待子弹的锁");synchronized (zd) {System.out.println(this.getName()+"获取子弹的锁");}}}else if(choces==2) {synchronized (zd) {System.out.println(this.getName()+"获取子弹的锁");System.out.println(this.getName()+"等待枪的锁");synchronized (gan) {System.out.println(this.getName()+"获取枪的锁");}}}System.out.println(this.getName()+"打靶完成");}
}
/*小明获取枪的锁
小红获取子弹的锁
小明等待子弹的锁
小红等待枪的锁
*/
注意:个人感觉就是synchronzied块中还有其他的同步块锁的其他对象。会造成死锁
///解决方法把里面的同步块拿出来即可
public synchronized void Dabading(){ if(choces==1) {synchronized (gan) {System.out.println(this.getName()+"获取枪的锁");System.out.println(this.getName()+"等待子弹的锁");}synchronized (zd) {System.out.println(this.getName()+"获取子弹的锁");}}else if(choces==2) {synchronized (zd) {System.out.println(this.getName()+"获取子弹的锁");System.out.println(this.getName()+"等待枪的锁");}synchronized (gan) {System.out.println(this.getName()+"获取枪的锁");}}System.out.println(this.getName()+"打靶完成");}
}
/*小明获取枪的锁
小红获取子弹的锁
小明等待子弹的锁
小红等待枪的锁
小红获取枪的锁
小明获取子弹的锁
小红打靶完成
小明打靶完成*/
Lock(显示的锁) 锁
- 从jdk5.0开始,Java提供了更强大的线程同步机制——通过显式的定义同步锁对象来实现同步,同步锁使用Lock对象充当
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前,应先获得Lock对象
- ReentrantLock (可重入锁)类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁
reentrantlock.lock();try {} finally {reentrantlock.lock();}
public class TestLock {public static void main(String args[]) {Tickets t=new Tickets();new Thread(t,"小红").start();new Thread(t,"小明").start();new Thread(t,"老师").start();
}
}
class Tickets implements Runnable {private int tickets=10; private ReentrantLock relock=new ReentrantLock(); @Overridepublic void run(){while(true) {System.out.println(Thread.currentThread().getName()+tickets--);try {relock.lock();Thread.currentThread().sleep(100);if(tickets>0) { }else {break;}} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {relock.unlock();}}}
}
/*小红10
小明9
老师8
小红7
小明6
老师5
小红4
小红3
小明2
老师1
*/
-
synchronized 和Lock的对比
-
- Lock是显示锁(手动开启和关闭锁,别忘记关闭锁)synchronized 是隐式锁,除了作用域自动释放
- Lock只有代码块锁,synchronized有代码块和方法锁
- 使用Lock锁,JVM将话费较少时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序:
-
- Lock >同步块(已进入了方法体,分配了相应资源)>同步方法(在方法体之外)