赞
踩
概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。
为什么要处理线程间通信:
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
如何保证线程间通信有效利用资源:
多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
什么是等待唤醒机制
这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。
就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
wait/notify 就是线程间的一种协作机制。
等待唤醒中的方法
等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:
等待唤醒机制其实就是经典的“生产者与消费者”的问题。就拿存钱取钱案例来说等待唤醒机制如何有效利用资源。
案例
夫妻小明与小红有一个共同账户:共享资源。他们有三个爸爸(亲爹,干爹,岳父)给他们账户存钱。小明和小红去取钱,如果账户有钱就取出来,等待自己,然后等待自己等他们三个爸爸去存钱;爸爸们去存钱,如果发现有钱就不存,没钱就存钱,然后等待自己。
分析:
生产者线程:3个爸爸
消费者线程:小明,小红
共享资源:账户对象
账户类:
public class Account { private String cardId; private double money; public Account() { } public Account(String cardId, double money) { this.cardId = cardId; this.money = money; } public String getCardId() { return cardId; } public void setCardId(String cardId) { this.cardId = cardId; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } public synchronized void drawMoney(int money) { try{ String name=Thread.currentThread().getName(); if(this.money>=money){ this.money -=money; System.out.println(name+"来取钱:"+money+",余额:"+this.money); //等待自己唤醒别人,锁对象调用 this.notifyAll(); this.wait(); }else { //余额不足,唤醒别人等待自己 this.notifyAll(); this.wait(); } }catch (Exception e){ e.printStackTrace(); } } public synchronized void saveMoney(int money) { try{ String name=Thread.currentThread().getName(); if(this.money> 0){ //有钱,不存,唤醒别人 this.notifyAll(); this.wait(); }else { this.money +=money; System.out.println(name+"来存钱:"+money+",余额:"+this.money); this.notifyAll(); this.wait(); } }catch (Exception e){ e.printStackTrace(); } } }
存钱线程类:
public class SaveThread extends Thread{ private Account acc; public SaveThread(Account acc, String name){ super(name); this.acc=acc; } @Override public void run() { while (true) { try { this.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } acc.saveMoney(100000); } } }
取钱线程类:
public class DrawThread extends Thread{ private Account acc; public DrawThread(Account acc, String name){ super(name); this.acc=acc; } @Override public void run() { while (true){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //取钱 acc.drawMoney(100000); } } }
主类:
public class MainTest {
public static void main(String[] args) {
//创建一个共享账户
Account acc = new Account();
//创建两个取钱线程对象代表小明和小红
new DrawThread(acc,"小明").start();
new DrawThread(acc,"小红").start();
//存钱线程
new SaveThread(acc,"亲爹").start();
new SaveThread(acc,"干爹").start();
new SaveThread(acc,"岳父").start();
}
}
结果:存取钱交替执行
总结:
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中, 有几种状态呢?在API中 java.lang.Thread.State 这个枚举中给出了六种线程状态:
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
合理利用线程池能够带来三个好处:
创建与常用方法
使用线程池中线程对象的步骤:
Runnable实现类代码:
public class ThreadPoolsTest { public static void main(String[] args) { //创建一个线程池 ExecutorService pools = Executors.newFixedThreadPool(3); //提交线程任务让线程池处理 Runnable target = new MyRunnable(); pools.submit(target);//线程池创建新线程 pools.submit(target);//线程池创建新线程 pools.submit(target);//线程池创建新线程 pools.submit(target);//复用之前的线程 pools.shutdown();//等任务执行完了就会关闭线程池 // pools.shutdownNow();//立即关闭线程池,无论是否执行完毕 } } class MyRunnable implements Runnable{ @Override public void run() { for (int i=0;i<10;i++) System.out.println(Thread.currentThread().getName()+"=>"+i); } }
设计一个必然死锁的案例:
public class Demo { //创建两个共享资源 public static Object resource1=new Object(); public static Object resource2=new Object(); public static void main(String[] args) { //创建两个线程 new Thread(new Runnable() { @Override public void run() { synchronized (resource1){ System.out.println("线程1占用资源1,请求资源2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (resource2){ System.out.println("线程1占用资源2"); } } } }).start(); new Thread(new Runnable() { @Override public void run() { synchronized (resource2){ System.out.println("线程2占用资源2,请求资源1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (resource1){ System.out.println("线程2占用资源2"); } } } }).start(); } }
小结:死锁代码形式上通常需要进行锁的嵌套访问
public class Volatiledemo extends Thread { private boolean flag= false; @Override public void run() { try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } //线程中修改flag flag =true; System.out.println("flag =true"); } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } } class Test{ public static void main(String[] args) { Volatiledemo t = new Volatiledemo(); t.start(); while (true){ if (t.isFlag()) System.out.println("主线程进行"); } } }
运行结果:
分析:
多线程访问一个共享变量,会出现一个线程修改变量值后,其他线程看不到的情况
本地内层与主内存的关系
问题分析:
不可见性原因:
每个线程都有自己的工作内存,线程都是从主内层拷贝共享变量的副本值,每个线程都是在自己的工作内存中操作共享变量
每次加锁会清空自己的工作内存,重新读取主内层中的最新值
//main方法
while (true){
synchronized (Volatiledemo.class){
if (t.isFlag())
System.out.println("主线程进行");
}
}
某一个线程进入synchronized代码块前后,执行过程如下:
使用volatile关键字修饰该变量,一旦一个线程修改了volatile关键字修饰的变量,另一个线程立即取到最新值
private volatile boolean flag;
工作原理:
原子性是指一批操作是一个整体,要么同时成功,要么同时失败,不能被干扰
volatile只能保证线程变量的可见性,不能保证变量操作的原子性
public class Demo { public static void main(String[] args) { Runnable target=new MyRunnable(); for (int i=0;i<100;i++) new Thread(target).start(); } } class MyRunnable implements Runnable{ private volatile int count; @Override public void run() { for (int i=0;i<100;i++){ count++; System.out.println("count="+count); } } }
输出结果可能不是理想的结果(10000),结果小于10000,不具有原子性
public class Demo2 { public static void main(String[] args) { Runnable target=new MyRunnable2(); for (int i=0;i<100;i++) new Thread(target).start(); } } class MyRunnable2 implements Runnable{ private volatile int count; @Override public void run() { synchronized ("a"){//即保证可见性,有保证原子性 for (int i=0;i<100;i++){ count++; System.out.println("count="+count); } } } }
加锁虽然可以保证原子性,但是性能较差,于是java提供了原子类,提高了性能
AtomicInteger
原子型Inteager,可以实现原子更新操作
public class Demo3 { public static void main(String[] args) { Runnable target=new MyRunnable3(); for (int i=0;i<100;i++) new Thread(target).start(); } } class MyRunnable3 implements Runnable{ //创建一个Integer更新的原子类AtomicInteger,初始值是0 private AtomicInteger count=new AtomicInteger(); @Override public void run() { for (int i=0;i<100;i++){ System.out.println("count="+count.incrementAndGet()); } } }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。