线程同步机制
当多个线程需要同时访问和修改同一个对象时,需要创建一个线程池,形成等待队列,逐个依次访问,来避免并发问题
在访问时加入
(锁机制),可以使一个线程访问此对象时获得排他锁,拒绝其他线程对此对象的访问。sychronize
线程不安全
下为一个线程不安全的例子
public class testUnsafe { public static void main(String[] args) throws InterruptedException { you u = new you(); u.HP = 100;// 初始生命值 attack atk = new attack(u,"ctf");//来自ctf的攻击 Thread t = new Thread(atk); t.start(); attack atk1 = new attack(u,"awd");//来自awd的攻击 Thread t1 = new Thread(atk1); t1.start(); } } class you { public int HP;//玩家生命值 } class attack implements Runnable { you u; String attacker; public attack(you u,String atk){ //设置被攻击者和攻击者 this.u = u; this.attacker = atk; } @Override public void run() { if(u.HP <= 60){ System.out.println("You die!"); System.exit(0); } try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); }//模拟延迟来放大问题 System.out.printf("%s攻击了你。\n",attacker); u.HP-=60; System.out.printf("还剩%d点HP\n",u.HP); } }
在1s的延迟结束后,两个线程同时访问对象,访问到的都是HP为100的you对象,因此绕过了判断死亡的限制,线程不安全。
sychronize
:同步锁,在直接修饰方法的时候默认锁的是this,在修饰代码块时,锁括号内的对象:sychronize(obj){code}sychronize
在锁定对象时:被修饰方法或代码块内的所有代码线程都对锁定对象,都有队列访问机制,即一个线程访问对象时对象被上锁,被sychronize修饰的线程拿到此锁并访问,其他线程不得同时访问。
上面代码加上同步锁:
public class testUnsafe { public static void main(String[] args) throws InterruptedException { you u = new you(); u.HP = 100;// 初始生命值 attack atk = new attack(u,"ctf");//来自ctf的攻击 Thread t = new Thread(atk); t.start(); attack atk1 = new attack(u,"awd");//来自awd的攻击 Thread t1 = new Thread(atk1); t1.start(); } } class you { public int HP;//玩家生命值 } class attack implements Runnable { you u; String attacker; public attack(you u,String atk){ //设置被攻击者和攻击者 this.u = u; this.attacker = atk; } @Override public void run() { synchronized (u){ //为对象u上锁 if(u.HP <= 60){ System.out.println("You die!"); System.exit(0); } try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); }//模拟延迟来放大问题 System.out.printf("%s攻击了你。\n",attacker); u.HP-=60; System.out.printf("还剩%d点HP\n",u.HP); } } }
线程死锁
当两个进程互相等待对方释放自己需要的资源,而自己掌握着对方需要的资源锁时,会导致永远互相等待,即进程死锁
例子:
public class DeadLock { public static void main(String a[]){ Thread t1 = new Thread(new change(0,"小明")); Thread t2 = new Thread(new change(1,"小红")); t1.start(); t2.start(); } } class incar{ //在车里 } class outcar{ //在车外 } class change implements Runnable{ static final incar incar = new incar(); static final outcar outcar = new outcar(); int choice ; String name; public change(int ch,String na){ this.choice = ch; this.name = na; } public void changepos(){ if(choice == 1){ synchronized (incar){ System.out.printf("现在%s在车上,想下车\n",name); synchronized (outcar){ System.out.printf("现在%s成功下车\n",name); } } } else { synchronized (outcar){ System.out.printf("现在%s在车下,想上车\n",name); synchronized (incar){ System.out.printf("现在%s成功上车\n",name); } } } } @Override public void run() { changepos(); } }
解释:如果只有上车了车里的人才能下车,下车了车外的人才能上车,那么小明永远在等待小红下车,小红永远在等待小明上车,程序卡死
解决方案:不要把对方的锁放到自己的锁块内:
synchronized (incar){ System.out.printf("现在%s在车上,想下车\n",name); } synchronized (outcar){ System.out.printf("现在%s成功下车\n",name); }
Lock:显式定义同步锁
对象被创建在需要锁定的对象的类中。lock方法锁定对象,unlock解锁。ReentrantLock
import java.util.concurrent.locks.ReentrantLock; public class testUnsafe { public static void main(String[] args) throws InterruptedException { you u = new you(); u.HP = 100;// 初始生命值 attack atk = new attack(u,"ctf");//来自ctf的攻击 Thread t = new Thread(atk); t.start(); attack atk1 = new attack(u,"awd");//来自awd的攻击 Thread t1 = new Thread(atk1); t1.start(); } } class you { public final ReentrantLock lock = new ReentrantLock(); //创建锁 public int HP;//玩家生命值 } class attack implements Runnable { you u; String attacker; public attack(you u,String atk){ //设置被攻击者和攻击者 this.u = u; this.attacker = atk; } @Override public void run() { u.lock.lock(); //上锁 if(u.HP <= 60){ System.out.println("You die!"); System.exit(0); } try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); }//模拟延迟来放大问题 System.out.printf("%s攻击了你。\n",attacker); u.HP-=60; System.out.printf("还剩%d点HP\n",u.HP); u.lock.unlock(); //解锁 } }
线程协作:生产者消费者问题
方法名 | 作用 |
---|---|
wait() | 此线程进入等待,直到被其他线程唤醒,与sleep不同,wait()会释放线程锁 |
wait(long timeout) | 线程等待timeout毫秒 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒此对象创建的所有线程 |
以上都是Object类的方法,都只能在同步方法或同步代码块中使用。
生产者消费者问题
生产者线程生产商品,消费者线程消费购买商品。
生产者在生产完商品后要通知消费者购买商品,消费者购买完商品后要通知生产者生产商品。
生产者:负责生成数据的模块
消费者:负责处理数据的模块
1.管程法
生产者将生产好的数据放入缓冲区,消费者负责从缓冲区拿出数据
public class testSynC { public static void main(String arg[]){ SynContainer con = new SynContainer(); Thread pro = new Thread(new Productor(con)); Thread cons = new Thread(new Consumer(con)); pro.start(); cons.start(); } } class Productor implements Runnable { SynContainer con; public Productor(SynContainer con){ this.con = con; } @Override public void run() { for(int i = 0 ; i<1000 ; i++){ System.out.printf("生产了第%d只鸡\n",i); try { con.push(new chicken(i)); //生产 } catch (InterruptedException e) { throw new RuntimeException(e); } } } } class Consumer implements Runnable{ SynContainer con; public Consumer(SynContainer con){ this.con = con; } @Override public void run() { for(int i = 0 ; i<1000 ; i++){ try { int chickenid = con.pop().id; System.out.printf("消费了第%d只鸡 %d\n",chickenid,i); //消费 } catch (InterruptedException e) { throw new RuntimeException(e); } } }} class chicken{ int id; public chicken(int id){ this.id = id; } } class SynContainer{ int c = 0; private chicken[] chickens = new chicken[10]; public synchronized void push(chicken chicken) throws InterruptedException { if(c == chickens.length){ this.wait(); //如果缓冲区满,则等待消费者消费 } chickens[c++] = chicken; this.notifyAll(); //生产出一只鸡,通知消费者消费 } public synchronized chicken pop() throws InterruptedException { if(c == 0 ){ this.wait(); //如果缓冲区没有鸡,等待生产者生产 } chicken chik = chickens[--c]; //消费一只鸡,通知生产者生产 this.notifyAll(); return chik; } }
上述代码中,生产者与消费者线程都在不断地进行生产和消费,生产者会在缓冲区满的时候等待消费者消费,消费者会在缓冲区为0时等待生产者生产
2.信号灯法
public class testFlag { public static void main(String arg[]){ flagHandler con = new flagHandler(); Thread pro = new Thread(new Kitchen(con)); Thread cons = new Thread(new Eater(con)); pro.start(); cons.start(); } } class Kitchen implements Runnable { flagHandler con; public Kitchen(flagHandler con){ this.con = con; } @Override public void run() { for(int i = 0 ; i<1000 ; i++){ try { con.push(); System.out.printf("生产了第%d只鱼\n",i); //消费 } catch (InterruptedException e) { throw new RuntimeException(e); } } } } class Eater implements Runnable{ flagHandler con; public Eater(flagHandler con){ this.con = con; } @Override public void run() { for(int i = 0 ; i<1000 ; i++){ try { con.pop(); System.out.printf("消费了第%d只鱼\n",i); //消费 } catch (InterruptedException e) { throw new RuntimeException(e); } } }} class flagHandler { boolean flag = false;//ture:鱼做好了,等顾客吃 ; false:鱼没做好,等厨师做 public synchronized void push() throws InterruptedException { if(flag){ wait(); } flag=!flag; this.notifyAll(); } public synchronized void pop() throws InterruptedException { if(!flag){ wait(); } flag=!flag; this.notifyAll(); } }
较为简单,用flag区分哪个进程应该等待,哪个进程应该执行
线程池
池化思想:减少资源消耗提高运行效率
用
来实现:Callable
将管程法的main函数改为:
public static void main(String arg[]){ SynContainer con = new SynContainer(); ExecutorService ser = Executors.newFixedThreadPool(2); //创建容量为2个线程的线程池 Future<Boolean> s1 = ser.submit(new Consumer(con)); //提交线程对象到池 Future<Boolean> s2 = ser.submit(new Productor(con)); ser.shutdown(); //关闭线程池 }
实现线程池。
我的疑惑
在缓冲区实现线程交互的例子中,push的逻辑为:
if(c == chickens.length){ this.wait(); //如果缓冲区满,则等待消费者消费 } chickens[c++] = chicken; this.notifyAll(); //生产出一只鸡,通知消费者消费
为什么改成如下代码时:
if(c < chickens.length){ chickens[c++] = chicken; this.notifyAll(); } else { this.wait(); }
会出现生产者线程已经结束(即生产者已经全部生产完毕)而消费者线程仍然在运行的情况呢?
Comments NOTHING