> 文章列表 > 【学习-多线程】

【学习-多线程】

【学习-多线程】

学习内容:

  1. 为什么要有多线程
  2. 线程的两个概念
  3. 多线程的实现方式
  4. 常见的成员方法
  5. 线程的生命周期
  6. 线程安全问题
  7. Lock锁
  8. 死锁
  9. 生产者和消费者
  10. 线程池

学习产出:

为什么要有多线程

进程:进程是程序执行的基本实体(一个软件运行之后就是一个进程)
线程:线程是操作系统能够调度的最小单位,包含在进程中,是进程中的实际运作单位。

多线程可以让程序同时做多件事,已提高效率,


多线程的两个概念

并发:同一时刻,多个指令,在单个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();

线程的生命周期

【学习-多线程】

线程安全问题

同步代码块:把操作共享数据的代码锁起来
特点:

  1. 锁默认打开,有一个线程进去了,锁自动关闭
  2. 里面的所有代码执行完成,线程出来,锁自动打开
  3. 锁对象需要是唯一的
//格式
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关键字加到方法上
特点

  1. 同步方法锁定的是方法里面的所有代码
  2. 锁对象不能自己指定(当前方法是非静态的,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. 创建一个池子,池子是空的
  2. 提交任务时,创建一个线程,任务执行完毕,线程归还池子,下回再提交任务时,不需要创建新的线程,直接复用即可
  3. 提交任务时,池子中没有空闲线程,也无法创建 新的线程,任务会排队等待

代码实现

//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核心的两倍比较合适