多线程
1、创建多线程
* 方式1:创建Thread子类
*步骤
1、创建一个Thread类的子类
2、在Thread类的子类中重写Thread类中的run()方法
3、创建Thread的子类对象
4、调用Thread类的start()开启线程
*方式2:实现Runnable接口
*步骤
1、创建一个Runnable接口的实现类
2、在实现类中重写Runnable接口的run()方法
3、创建一个Runnable实现类对象
4、创建Thread类对象,构造方法传入Runnable实现类对象
5、调用Thread类中的start()方法开启线程
*两种方式的区别:
1、实现Runnable接口避免了单继承的局限性
2、增强了程序扩展性,降低了程序的耦合性(Runnable实现任务,Thread开启线程)
线程安全
1、同步代码块
*格式:
synchronized(锁对象){
可能出现线程安全的代码
}
*注意:
1、通过代码块中的锁对象,可以使用任意的对象
2、但必须保证多个线程使用的锁对象是同一个
3、锁对象的作用把同步代码块锁住,只让一个线程在同步代码块中执行
*步骤:
创建锁对象:Object obj = new Object();
synchronized(obj){
可能出现线程安全的代码
}
*原理:
使用了一个锁对象,这个锁对象叫同步锁,同步中的线程没有执行完毕不会释放锁,
同步外的线程没有锁不能进入同步块中
2、同步方法
*步骤
1、把访问共享数据的代码抽取出来,放到一个方法中
2、在方法上添加synchronized修饰符
修饰符 synchronized 返回值类型 方法名(参数列表){
可能出现线程安全的代码
}
3、静态同步方法
修饰符 static synchronized 返回值类型 方法名(参数列表){
可能出现线程安全的代码
}
锁对象是class
4、Lock
* 步骤
1、在成员位置创建一个ReentrantLock对象
2、在可能出现安全问题的代码前调用Lock接口的lock()获取锁
3、在可能出现安全问题的代码后调用Lock接口的unlock()释放锁
Lock l = new ReentrantLock();
l.lock();
可能出现线程安全的代码
l.unlock();
等待唤醒机制
1、为什么处理线程间通信?
多线程并发执行时,在默认情况下CPU随机切换线程,当我们需要多个线程共同完成一个任务,
并且我们希望它们由规律的执行,那么多线程需要一些协调通信,以此来帮助我们达到多线程
共同操作同一份数据
2、wait()和notify()注意的细节?
*wait()和notify()必须由同一锁调用
*wait()和notify()都属于Object中的方法
*wait()和notify()必须在同步代码块或同步函数中使用
3、案例
资源类:
public class Resource {
private String name;
private int num = 1;
private boolean flag = false;
Lock lock = new ReentrantLock();//创建锁对象
Condition producter = lock.newCondition();//获取监视生产者的锁
Condition consumer = lock.newCondition();//获取监视消费者的锁
public void setKind(String name){
lock.lock();//获取锁
try{
while(flag) { // 有烤鸭,生产者等待
try {
producter.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + num;
num++;
System.out.println("生产者"+ this.name);
flag = true;
consumer.signal();
}finally{
lock.unlock();//释放锁
}
}
public void out(){
lock.lock();
try{
while(!flag) {//没有烤鸭,消费者处于等待状态
try {
consumer.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者"+ this.name);
flag = false;
producter.signal();
}finally{
lock.unlock();
}
}
}
生产者类
public class Producter implements Runnable{
Resource r;
public Producter(Resource r){
this.r = r;
}
@Override
public void run() {
while(true){
r.setKind("烤鸭");
}
}
}
消费者类
public class Consumer implements Runnable{
Resource r;
public Consumer(Resource r){
this.r = r;
}
@Override
public void run() {
while (true){
r.out();
}
}
}
测试类
public class Demo {
public static void main(String[] args) {
Resource r = new Resource();
Producter a = new Producter(r);
Consumer b = new Consumer(r);
Thread t1 = new Thread(a);
Thread t2 = new Thread(a);
Thread t3 = new Thread(b);
Thread t4 = new Thread(b);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
线程池
1、原理
*当程序第一次启动时,创建多个线程保存到一个集合中当我们想要使用线程时,就可以从集合把
线程取出来
Thread t = arrayList.remove() //从集合中取出
Thread t = linkedList.removeFirst()
*当使用完毕把线程归还到线程池,JDK1.5之后,JDK内置了线程池
arrayList.add(t)
linkedList.addLast(t)
2、优点
* 降低资源消耗,降低了创建和销毁线程的次数
* 提高响应速度,不需要等待创建线程
* 提高线程的可管理性
3、用到的类
* java.util.concurrent.Executors; //线程池的工厂类,用来生产线程池
* Executors类中的静态方法
static ExecutorService newFixedThreadPool(int nThread)
创建一个可重用固定线程数的线程池
返回值:ExecutorService接口类型,返回ExecutorService实现类对象
ExecutorService为线程池接口,用来从线程池获取线程
调用start()执行线程任务
submit(Runnable task)提交一个Runnable任务用于执行
4、步骤
1、使用线程池工厂类Executors里提供的静态方法newFixedThreadPool生产一个线程池
2、创建一个类实现Runnable接口,重写run()方法设置线程任务
3、调用ExecutorService中的submit(),传递线程任务(实现类),开启线程
4、调用ExecutorService中的shutdown销毁线程池(不建议执行)
ExecutorService es = Executors.newFixedThreadPool(10);
//线程池会一值开启,使用完了线程,会自动把线程归还给线程池
es.submit(new RunnableImpl());