【学习-多线程】
学习内容:
学习产出:
为什么要有多线程
进程:进程是程序执行的基本实体(一个软件运行之后就是一个进程)
线程:线程是操作系统能够调度的最小单位,包含在进程中,是进程中的实际运作单位。
多线程可以让程序同时做多件事,已提高效率,
多线程的两个概念
并发:同一时刻,多个指令,在单个CPU上交替执行
并行:同一时刻,多个指令,在多个CPU上同时执行
多线程的实现方式
继承Thread类
Thread thread=new Thread(){@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName()+"world");}}};thread.setName("hello");thread.start();
实现Runnable接口
public class MyThread implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName()+"hellp");}}
}
利用Callable接口和Future接口方式实现
/*
* 特点:可以获取到多线程运行的结果
* 1,创建一个类实现Callable接口
* 2,重写call(有返回值,表示多线程运行的结果)
* 3,创建对象,(表示多线程执行的任务)
* 4,创建FutureTask对象(作用管理多线程运行结果)
* 5,创建Thread类对象,并启动
* */
public class MyThread implements Callable<Integer> {@Overridepublic Integer call() throws Exception {//求1-100和int res=0;for (int i = 0; i < 100; i++) {res+=i;}return res;}
}//创建对象MyThread myThread = new MyThread();//创建FutureTask对象FutureTask<Integer> futureTask=new FutureTask<>(myThread);//创建Thread类对象,并启动Thread thread=new Thread(futureTask);//获取到结果Integer res = futureTask.get();System.out.println(res);
常见的成员方法
java中是抢占式调度(是随机的),优先级越高,抢占到线程的概率越大
Thread thread1=new Thread(()->{for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName()+i);}},"飞机");Thread thread2=new Thread(()->{for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName()+i);}},"坦克");
//输出可以看到默认为5System.out.println(thread1.getPriority());System.out.println(thread2.getPriority());System.out.println(Thread.currentThread().getPriority());
//优先级为1-10thread1.setPriority(1);thread2.setPriority(10);thread1.start();thread2.start();//当其他线程结束时,守护线程也会结束Thread thread1=new Thread(()->{for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+i);}},"女神");Thread thread2=new Thread(()->{for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName()+i);}},"坦克");thread2.setDaemon(true);thread1.start();thread2.start();//出让当前Cpu的执行权
Thread.yield();
//下面代码,thread2是在线程1启动之前,但是thread1.join();表示插入到thread2线程之前执行
Thread thread1=new Thread(()->{for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+i);}},"女神");Thread thread2=new Thread(()->{try {thread1.join();} catch (InterruptedException e) {e.printStackTrace();}for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName()+i);Thread.yield();}},"坦克");thread2.start();thread1.start();
线程的生命周期
线程安全问题
同步代码块:把操作共享数据的代码锁起来
特点:
- 锁默认打开,有一个线程进去了,锁自动关闭
- 里面的所有代码执行完成,线程出来,锁自动打开
- 锁对象需要是唯一的
//格式
synchronized(锁){//操作共享数据的代码
}//买票问题while (true){synchronized(MyThread1.class){if ( num<100){num++;try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"在卖第"+num+"张票");}else {break;}}}
同步方法:把synchronized关键字加到方法上
特点
- 同步方法锁定的是方法里面的所有代码
- 锁对象不能自己指定(当前方法是非静态的,this;如果是静态的,当前类的字节码文件对象)
//格式
修饰符 synchronized 返回值 方法名(参数)//如果不会写,可以先写同步代码块 ,然后ctrl+alt+m可以抽取成一个方法
public class MyThread1 implements Runnable {static int num=0;@Overridepublic void run() {while (true){if (extracted()) break;}}private synchronized boolean extracted() {if (num<100){num++;try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"在卖第"+num+"张票");}else {return true;}return false;}
}Runnable myThread1 = new MyThread1() {};Thread thread =new Thread(myThread1,"窗口1");Thread thread2 =new Thread(myThread1,"窗口2");Thread thread3 =new Thread(myThread1,"窗口3");thread.start();thread2.start();thread3.start();
Lock锁
为了更加清晰的了解如何加锁和释放锁,jdk1.5提供了一个新的锁对象Lock
lock()获得锁
unlock()释放锁
手动上锁,手动释放锁
Lock接口不能直接实例化,采用他的实现类ReentrantLock来实例化
//这里要注意的是 在一个线程结束的时候一定要执行lock.unlock();,要不然其他的线程都结束不了
public class MyThread1 extends Thread{static int num=0;static Lock lock=new ReentrantLock();public MyThread1(String name) {super(name);}@Overridepublic void run() {while (true){lock.lock();try {if ( num<100){num++;Thread.sleep(10);System.out.println(Thread.currentThread().getName()+"在卖第"+num+"张票");}else {break;}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}}
}
死锁
相当于锁嵌套,a线程拿到a资源之后要去拿b资源,b线程拿了b资源要去拿a资源,两个线程就死锁了
生产者和消费者(等待唤醒机制)
生产者和消费者模式是一个十分经典的多线程协作模式
常见方法
void wait()//当前线程等待,直到被唤醒
void notify()//随机唤醒单个线程
void notifyAll()//唤醒所有线程//实现
public class Desk {//控制生产者和消费者的执行//是否有面条 0没有 1有public static int foodFlag=0;//总个数public static int count=10;static Object lock=new Object();
}public class Cook extends Thread{@Overridepublic void run() {while (true){synchronized(Desk.lock){if (Desk.count==0){break;}else {//判断桌子上是否有食物//如果有,就等待if(Desk.foodFlag==1){try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}else {//没有没有。制作System.out.println("厨师做了一碗");//修改做字状态Desk.foodFlag=1;//等待消费者开吃Desk.lock.notifyAll();}}}}}
}public class Foodie extends Thread{@Overridepublic void run() {/*循环* 同步代码块* 判断共享数据是否到了末尾(到了)* 没有到末尾*/while (true){synchronized(Desk.lock){if (Desk.count==0){break;}else {//判断桌子上是否有食物//没有,等待if (Desk.foodFlag==0){try {//让当前线程和锁进行绑定,唤醒的时候也是通过这个对象Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}else {//有,消费//吃的总是-1Desk.count--;System.out.println("开始消费,还能吃"+Desk.count);//消费完成,唤醒厨师Desk.lock.notifyAll();//修改桌子状态Desk.foodFlag=0;}}}}}
}
//利用阻塞队列实现
//需要注意:生产者和消费者需要使用同一个阻塞队列//创建阻塞队列对象ArrayBlockingQueue<String> queue=new ArrayBlockingQueue<>(1);//创建线程对象,并且把阻塞队列传过去Cook cook=new Cook(queue);Foodie foodie = new Foodie(queue);cook.start();foodie.start();public class Cook extends Thread{ArrayBlockingQueue<String> queue;public Cook(ArrayBlockingQueue<String> queue) {this.queue=queue;}@Overridepublic void run() {while (true){try {queue.put("面条");System.out.println("做了一份");} catch (InterruptedException e) {e.printStackTrace();}}}
}public class Foodie extends Thread{ArrayBlockingQueue<String> queue;public Foodie(ArrayBlockingQueue<String> queue){this.queue=queue;}@Overridepublic void run() {/*循环* 同步代码块* 判断共享数据是否到了末尾(到了)* 没有到末尾*/while (true){try {String take = queue.take();System.out.println(take);} catch (InterruptedException e) {e.printStackTrace();}}}
}
但是实际上JAVA中定义的只有这六种
新建:NEW
就绪状态:RUNABLE
阻塞状态:BLOCKED
无限期等待:WAITING
计时等待:TIMED_WAITING
结束状态:TERMINATED
线程池
需要线程的时候就去创建,用完就消失了
核心原理
- 创建一个池子,池子是空的
- 提交任务时,创建一个线程,任务执行完毕,线程归还池子,下回再提交任务时,不需要创建新的线程,直接复用即可
- 提交任务时,池子中没有空闲线程,也无法创建 新的线程,任务会排队等待
代码实现
//1,创建线程池
ExecutorService pool1 = Executors.newCachedThreadPool();
//有上限的线程池
ExecutorService pool2 = Executors.newFixedThreadPool(3);//2,提交任务pool1.submit(new MyThread1());
//3,所有任务执行完毕,关闭线程池
pool1.shutdown();
自定义线程池
什么时候创建临时线程,核心线程都没有空闲,且排队的任务排满。(所以不一定先提交的就会先执行)
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,//核心线程数量,不能小于06,//最大核心线程数,不能小于0,>=核心线程数60,//空闲线程最大存活时间TimeUnit.SECONDS,//时间单位new ArrayBlockingQueue<>(3),//任务队列Executors.defaultThreadFactory(),//创建线程工厂new ThreadPoolExecutor.AbortPolicy()//任务拒绝方式);
最大线程数应该是CPU核心的两倍比较合适