线程
案例:生产者和消费者(线程通信)(一)
public class Resource {
//定义布尔类型成员 ,标志位,只是线程该做什么
//false 没有 需要生产 true 需要消费
boolean flag=false;
int count;//包子计数器
}
public class Product implements Runnable{
//创建资源对象
Resource r=new Resource();
public void run() {
while(true) {
//对象资源判断
if (r.flag==true) {
//没有被消费,不能生产,等待
try {wait();} catch (InterruptedException e) {e.printStackTrace();}
}
//可以生产
r.count++;
System.out.println("生产了第"+r.count+"个包子");
r.flag=true;//可以消费
//唤醒对方线程
notify();
}
}
}
public class Customer implements Runnable {
//创建资源对象
Resource r=new Resource();
public void run() {
while(true) {
//判断标志位
if (r.flag==false) {
//需要生产,不能消费等待
try {wait();} catch (InterruptedException e) {e.printStackTrace();}
}
//可以消费了
System.out.println("消费了第"+r.count+"个");
//修改标志位,已经消费了 可以生产了
r.flag=true;
//唤醒生产线程
notify();
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
//创建对象
Runnable p=new Product();
Runnable c=new Customer();
Thread t0=new Thread(p);
Thread t1=new Thread(c);
t0.start();
t1.start();
}
}
以上程序启动线程后,抛出异常java.lang.IllegalMonitorStateException(运行时异常)异常称为无效的监视器状态异常.使用了线程的方法,来自于Object类:wait()notify(),必须出现在同步中
解决异常问题,使用同步代码块
实现消费者和生产者案例(二)
添加同步代码块的时候 ,程序停止不动
原因:notify()方法的时候,没有唤醒另一个线程,导致的生产的和消费的两个线程都处于wait()状态,两个方向的线程使用的锁不是一个
解决问题:两者使用同一个锁
public class Resource {
//定义布尔类型成员 ,标志位,只是线程该做什么
//false 没有 需要生产 true 需要消费
boolean flag=false;
int count;//包子计数器
}
public class Product implements Runnable{
//创建资源对象
Resource r;
public Product( Resource r) {
this.r=r;
}
public void run() {
synchronized(r) {
while(true) {
//对象资源判断
if (r.flag==true) {
//没有被消费,不能生产,等待
try {r.wait();} catch (InterruptedException e) {e.printStackTrace();}
}
//可以生产
r.count++;
System.out.println("生产了第"+r.count+"个包子");
r.flag=true;//可以消费
//唤醒对方线程
r.notify();
}
}
}
}
public class Customer implements Runnable {
//创建资源对象
Resource r;
public Customer( Resource r) {
this.r=r;
}
public void run() {
while(true) {
//判断标志位
synchronized(r) {
if (r.flag==false) {
//需要生产,不能消费等待
try {r.wait();} catch (InterruptedException e) {e.printStackTrace();}
}
//可以消费了
System.out.println("消费了第"+r.count+"个");
//修改标志位,已经消费了 可以生产了
r.flag=false;
//唤醒生产线程
r.notify();
}
}
}
}
public static void main(String[] args) {
Resource r=new Resource();
//创建对象
Runnable p=new Product(r);
Runnable c=new Customer(r);
Thread t0=new Thread(p);
Thread t1=new Thread(c);
t0.start();
t1.start();
}
线程的方法问题
Object类的方法wait(),Threaad类的方法sleep()
- 问题 :为什么等待唤醒的方法,定义在Object类中,而不是Thread
- 同步锁导致的,任何对象都能作为所锁,保证任何一个锁对象都能调用等待和唤醒
- wait()和sleep()方法的区别
- wait()只能出现在同步中,必须是锁独享调用
- sleep()方法可以随时使用,不依赖同步
- wait()方法释放同步锁,被唤醒,重写获取锁
- sleep()方法不释放锁
实现消费者和生产者案例(三)
私有修饰成员变量,提供方法访问
public class Resource {
//定义布尔类型成员 ,标志位,只是线程该做什么
//false 没有 需要生产 true 需要消费
private boolean flag=false;
private int count;//包子计数器
//提供方法,生产
public synchronized void proruct() {
//判断变量=true;不能生产,等待
if (flag=true) {
try {
this.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
count++;
System.out.println("生产了第"+count+"个");
//修改标志类
flag=true;
//唤醒消费线程
this.notify();
}
//提供方法消费
public synchronized void customer() {
//判断变量=false,不能消费,等待
if (flag==false) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费了第"+count+"个");
//修改标志位
flag=false;
//唤醒生产线程
this.notify();
}
}
public class Product implements Runnable{
//创建资源对象
private Resource r;
public Product( Resource r) {
this.r=r;
}
public void run() {
while(true){
r.proruct();
}
}
}
public class Customer implements Runnable {
//创建资源对象
private Resource r;
public Customer( Resource r) {
this.r=r;
}
public void run() {
while(true) {
r.customer();;
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Resource r=new Resource();
//创建对象
Runnable p=new Product(r);
Runnable c=new Customer(r);
Thread t0=new Thread(p);
Thread t1=new Thread(c);
t0.start();
t1.start();
}
}
多生产者和多消费者
notify()方法唤醒的时候往往是最先等待的
我们需要什么效果 :生产线程唤醒消费线程,消费线程唤醒生产线程,现在唤醒的可能是本方线程(NO)
但是,线程是一个独立的方法栈,CPU去里面数据运行,线程之间不认识谁是本方,谁是对方
使用Object类的方法notifyAll()唤醒所有的线程
线程全部唤醒后,依然没有达到需要的结果,因为线程唤醒后直接就运行了,不会判断flag标志是什么,线程已经判断过了
线程全部唤醒后也不能立即执行,再次判断flag,允许生产再生产,如果不允许,继续等
public class Resource {
//定义布尔类型成员 ,标志位,只是线程该做什么
//false 没有 需要生产 true 需要消费
private boolean flag=false;
private int count;//包子计数器
//提供方法,生产
public synchronized void proruct() {
//判断变量=true;不能生产,等待
while (flag==true) {
try {
this.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
count++;
System.out.println("生产了第"+count+"个......");
//修改标志类
flag=true;
//唤醒消费线程
this.notifyAll();
}
//提供方法消费
public synchronized void customer() {
//判断变量=false,不能消费,等待
while (flag==false) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费了第"+count+"个");
//修改标志位
flag=false;
//唤醒生产线程
this.notifyAll();
}
}
多生产者与消费者问题分析
- notifyAll()唤醒的全部的线程.资源浪费
- 能不能只唤醒对方一个
- wait(,notifyo.notifyAll()都是本地方法,C++编写方法,和操作系统交互
- 通知操作系统,将线程挂起不要允许,通知操作系统,让线程继续允许
- 操作系统找CPU来实现线程的等待和唤醒
- 频繁的和操作系统交互,降低了程序的效率
多生产者和多消费者(线程通信)改进
JDK1.5出现接口Lock,方法lock(),unlock()
接口出现目的取代synchronized
Lock接口
接口中定义方法:
- condition newcondition()返回一个Condition的接口类型
- Condition接口取代object类的监视器方法
Condition接口
作用︰线程的阻塞队列,本身是一个容器,存储的是线程.
进入到队列的线程,释放同步锁
一个锁Lock接口对象,可以产生出多个线程的阻塞队列.让线程释放同步锁后,进入到队列
需要唤醒线程的时候,指定唤醒哪一个队列中的线程
好处:不会全部唤醒,不会和操作系统进行交互,提高效率
Condition接口方法:
void await()
线程释放锁,并进去到线程的阻塞队列,取代了Object类的方法wait()void singal()
线程从阻塞队列出来,获取锁再运行,取代了Object类的方法notify()
/*
* 多生产和多消费的案例改进
* Lock接口为锁使用
* Condition接口,做为线程的阻塞队列体改效率节约资源
*/
public class Resource {
//定义布尔类型成员 ,标志位,只是线程该做什么
//false 没有 需要生产 true 需要消费
private boolean flag=false;
private int count;//包子计数器
//创建Lock接口的实现类对象,作为锁,去掉synchronized
private Lock lock=new ReentrantLock();
//通过这个锁对象,产生两个存储线程的容器(阻塞队列)
//lock接口方法newCondition
Condition pc=lock.newCondition();//线程的阻塞队列 ,生产队列
Condition cc=lock.newCondition();//线程的阻塞队列 ,消费队列
//提供方法,生产
public void proruct() {
//获取锁
lock.lock();
//判断变量=true;不能生产,等待
while (flag==true) {
//线程释放锁,道阻塞队列中待着
try {pc.await();} catch (InterruptedException e) {e.printStackTrace();
}}
count++;
System.out.println("生产了第"+count+"个......");
//修改标志类
flag=true;
//唤醒对方的线程
cc.signal();
//释放锁
lock.unlock();
}
//提供方法消费
public void customer() {
//获取锁
lock.lock();
//判断变量=false,不能消费,等待
while (flag==false) {
//线程等待 ,释放锁,进入队列
try {cc.await();} catch (InterruptedException e) {e.printStackTrace();}
}
System.out.println("消费了第"+count+"个");
//修改标志位
flag=false;
//唤醒生产线程
pc.signal();
lock.unlock();
}
}
线程池
概念:缓冲池是为了提高效率使用的,线程池也是缓冲池的一种(连接池,对象池)
为什么要出现线程池功能,创建线程是要和操作系统进行交互,线程运行完毕(run()结束)频繁创建线程,大量的浪费操作系统的资源,为了解决资源销毁和体改效率问题,人们设计出了线程池
线程池的思想,创建一些线程,存储在一个容器中,不要让线程销毁,需要的时候拿一个线程出来,线程执行完任务的时候,放回到容器中,存储线程的容器,线程池
JDK内置线程池
java.util.concurrent.Executors
工厂类,创建对象,创建线程池对象
创建线程对象的方法:
Executorservice newFixedThreadPoo1(int n)
创建具有固定个数的线程池,返回的是接口类型,实现类就是创建的线程对象
java.util.ExecutorService
接口的方法submit( Runnable r)提交线程执行的任务
java.util.concurrent.callable
接口,视为Runnable接口的扩展
接口的抽象方法:call()具有返回值的方法,而且还能抛出异常,此方法作为线程的任务使用,可以取到方法的返回值
线程池提交线程任务方法submit(Callable c)传递接口实现类,提交任务的方法submit有返回值
返回的是Future的接口类型,接口的实现类,就是线程运行的结果返回值
public class MyCallable implements Callable<String> {
public String call()throws Exception {
return "执行结果";
}
}
public class ThreadDemo {
public static void main(String[] args)throws Exception {
ExecutorService es=Executors.newFixedThreadPool(2);
//提交线程任务submit,接收submit的方法返回值接口类型
Future<String>f=es.submit(new MyCallable());
System.out.println(f.get());
}
}
public class ThreadDemo {
public static void main(String[] args)throws Exception {
//创建两个固定数的线程
ExecutorService es=Executors.newFixedThreadPool(2);
//提交任务
Future<Integer>f= es.submit(new MyCallable(100));
System.out.println(f.get());
Future<Integer>f1= es.submit(new MyCallable(200));
System.out.println(f1.get());
}
}
public class MyCallable implements Callable<Integer>{
private int a;
public MyCallable(int a) {
this.a=a;
}
public Integer call() {
int sum=0;
for (int i = 0; i <=a; i++) {
sum=sum+i;
}
return sum;
}
}
ConcurrentHashMap
java.uti1.concurrent.ConcurrentHashMap
实现Map接口,是键值对的集合
集合特点:
- 底层哈希表结构
- 保证键的唯一性,键对象重写hashCode和equals
- 是线程安全的集合,半安全
- 不更改集合中的元素,不会锁定(synchronized)
- 改变的元素,使用同步锁
- 操作的是哪个数组的索引,锁定当前数组索引
- 不会出现并发修改异常
- 不能存储null值,null键
public class ThreadDemo {
public static void main(String[] args) {
//HashMap<String, String> map=new HashMap<String, String>();
ConcurrentHashMap<String, String> map=new ConcurrentHashMap<String, String>();
map.put("a", "1");
map.put("b", "2");
map.put("c", "3");
Set<String> set=map.keySet();
Iterator<String> it=set.iterator();
while(it.hasNext()) {
String key=it.next();
map.put("d", "4");
System.out.println(key+map.get(key));
}
}
}
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
// HashMap<Integer, Integer> map=new HashMap<Integer, Integer>();
ConcurrentHashMap<Integer, Integer> map=new ConcurrentHashMap<Integer, Integer>();
//集合储存2000个元素
for (int i = 1; i <=2000; i++) {
map.put(i, 0);
}
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <=500; i++) {
map.remove(i);
}
}
}).start();
//线程,删除Map集合501-1000
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 501; i <=1000; i++) {
map.remove(i);
}
}
}).start();
Thread.sleep(3000);
System.out.println(map.size());
}
}
原子操作
原子操作就是不可分割的操作
i++非原子操作,出现线程的安全问题
Atomiclnteger
整数的原子类,实现对整数的操作保证线程的安全