## 线程 Thread 进程包含多个线程 至少有一个线程 线程一定属于某一个进程 java多线程语言 主线程+垃圾回收线程(守护线程) ### 逻辑 CPU 的每一个时间片段 分配给每一个线程 同一时间片段 只能处理某一个线程的任务 多个线程快速的切换 多核处理器 提升了CPU每一个时间片段的使用效率 分配原则: 分时调度 平均分配给每个线程 抢占式调度 根据线程优先级 提高获取CPU使用权的概率(不靠谱) ### 安全性 【多个线程】 同时【更新】【同一个数据】 极大概率出现数据异常 称为线程安全问题 数据更新速度 赶不上 线程切换速度 ### 创建线程 #### 方案一:extends Thread 继承自Thread的类 变为线程类 (1.本身就是线程对象 2.内部特定方法run()为线程任务) 小线程默认名称:Thread-0 Thread-1 。。。 \`\`\`java //1.继承自Thread 成为线程类 public class Tickets extends Thread { //2.编写线程任务的run() String name; //@Override public void run() { // TODO Auto-generated method stub //super.run(); //System.out.println("我是小线程任务"); Thread thread = Thread.currentThread();//获取当前执行的线程对象 name = thread.getName(); for(int i =0;i\<100;i++){ System.out.println(name+"买到"+(i+1)+"张票"); } //show(); } //public void show(){ //} } //测试类中 创建线程对象 启动线程 //3.创建线程对象 Tickets t = new Tickets();//绑定了线程任务的线程对象 t.setName("甲"); //4.调用线程启动方法 start() 启动线程任务 //t.run(); //并没有开启小线程 依然是单线程 t.start(); Tickets t1 = new Tickets(); t1.setName("乙"); t1.start(); public class User{ String name; public User(String name){ this.name = name; } public String toString(){ return name; } } User u1 = new User("张三"); syso(u1.toString()); User u2 = new User("李四"); syso(u2.toString()); \`\`\` 线程优先级 priority属性 MAX_PRIORITY 10 MIN_PRIORITY 1 NORM_PRIORITY 5 默认线程优先级都为5 \`\`\`java Tickets t = new Tickets();//绑定了线程任务的线程对象 t.setPriority(Thread.MIN_PRIORITY); //将t线程优先级降低到1 System.out.println(t.getPriority()); //1 \`\`\` #### 练习 要求1.使用两个线程完成 甲 买20只狗 乙买30只猫 穿插展示购买记录 要求2.使用一个线程任务完成上述任务 #### 案例 1.一个人买100张票 2.两个人 各自买各自的100张票 3.两个人 共同抢100张票 \`\`\`\`java public class Ticket extends Thread{ static int num = 100;//继承写法中 如果需要某属性所有线程共享 则使用static public void run(){ String name = Thread.currentThread().getName(); while(num\>0){ syso(name+"抢到第"+(101-num)+"张票"); num--; } } } //测试类中 Ticket t1 = new Ticket(); t1.setName("张三"); t1.start(); Ticket t2 = new Ticket(); t2.setName("李四"); t2.start(); //以上代码 实现了两个人共同抢100张票 但是 属于 多个线程同时更新同一个数据 ==》线程安全问题 \`\`\`\` #### 方案二:implements Runnable 实现自接口 只是实现线程任务run() 没有线程对象 需要额外绑定线程对象 才能启动 1.实现自接口 比 继承自父类 自由度更大 2.可以实现线程任务和线程对象的分离 就可以使用线程池 3.前提 线程任务只创建一次 全局变量 自动所有线程对象共享 run()内的变量 每个线程独有 \`\`\`java 编写一个类 实现Runnable 中的 run() 主要表述线程任务 本身不是线程对象 不能直接start() public class Tickets implements Runnable{ public void run(){ String name = Thread.currentThread().getName(); syso(name); } } //测试类中 Tickets t = new Tickets();//只是线程任务 需要额外提供线程对象 //t.start();//报错 Thread t1 = new Thread(t,"甲");//创建线程对象 绑定任务t 并给该线程对象命名为甲 t1.start();//启动线程任务 //可以直接在测试类中 使用局部内部类技术 编写线程任务(Runnable的实现类) Runnable r = new Runnable() { //int num =0;//所有线程对象 共享 @Override public void run() { // TODO Auto-generated method stub int num = 100; //每个线程有自己的num属性 String name = Thread.currentThread().getName(); while(num\>0) { System.out.println(name+"买到第"+(101-num)+"张票"); num--; } } }; //创建线程对象 绑定线程任务ticket Thread t0 = new Thread(r,"甲"); Thread t1 = new Thread(r,"乙"); t0.start(); t1.start(); //测试类中 Runnable r = new Runnable(){ int num = 100; //因为只创建了唯一一个任务对象r 因此成员变量就是将来所有线程共享 public void run(){ //int num = 100;//将来每一个线程对象 都有属于自己的run()内的资源 String name = Thread.currentThread().getName(); while(num\>0){ syso(name+"抢到第"+(101-num)+"张票"); num--; } } } Thread t1 = new Thread(r,"张三"); Thread t2 = new Thread(r,"李四"); t1.start(); t2.start(); //以上代码 实现了两个人共同抢100张票 但是 属于 多个线程同时更新同一个数据 ==》线程安全问题 \`\`\` #### 线程池 1.线程数量固定的线程池 当申请超出了线程数量时 则任务等待 使用完线程将归还线程池 有一定的限流作用 ExecutorService es = Executors.newFixedThreadPool(int); //参数为初始线程数量 pool-1-thread-1 pool-1-thread-2 ... 2.不固定数量的线程池 线程池只负责创建和销毁线程 ExecutorService es = Executors.newCachedThreadPool(); pool-1-thread-1 pool-1-thread-2 ... \`\`\`java Runnable r = new Runnable() { //int num = 10; @Override public void run() { // TODO Auto-generated method stub int num = 10; String name = Thread.currentThread().getName(); // //pool-1-thread-1 // while(num\>0) { // System.out.println("从窗口"+name.substring(name.lastIndexOf("-")+1)+"买到第"+(11-num)+"票"); // num--; // } System.out.println(name + "获得了线程 开始工作"); try { // 会让出CPU Thread.sleep(new Random().nextInt(5000)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(name + "释放了线程"); } }; Thread t0 = new Thread(r,"甲"); t0.start(); Thread t1 = new Thread(r,"乙"); t1.start(); // //创建只有两个线程对象的线程池Executors // ExecutorService es = Executors.newFixedThreadPool(2); //不控制线程数量 随时创建线程 回收线程 删除线程 ExecutorService es = Executors.newCachedThreadPool(); for (int i = 0; i \< 5; i++) { es.submit(r);// 获取线程对象 同时开启线程任务 } es.shutdown();// 关闭线程池 \`\`\` #### 方案三:implements Callable\\ 只能使用线程池提供线程对象 #### 案例 每个线程任务为 返回该线程的名字 \`\`\`java Callable cs = new Callable() { @Override public String call() throws Exception { // TODO Auto-generated method stub String name = Thread.currentThread().getName(); System.out.println(name); //Thread.sleep(3000); return name; } }; //必须通过线程池提供线程对象 ExecutorService es = Executors.newCachedThreadPool(); //for(int i =0;i\<5;i++) { Future future1 = es.submit(cs); Future future2 = es.submit(cs); Future future3 = es.submit(cs); Future future4 = es.submit(cs); Future future5 = es.submit(cs); System.out.println(future1.get());//获取当前线程任务的返回值 System.out.println(future2.get()); System.out.println(future3.get()); System.out.println(future4.get()); System.out.println(future5.get()); Future fs = new Future\[5\]; for(int i =0;i 0) { try { Thread.sleep(10);// 如果把休眠写在同步代码块内部 则会 抱着锁睡觉 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 当内部有线程执行时 其他线程被阻塞在此处 synchronized (o) { // 同步代码块 内部的代码 同时只会有一个线程进入执行 if (num \> 0) { System.out.println(Thread.currentThread().getName() + "买到第" + (101 - num) + "张票"); num--; } } // 当内部线程执行完毕 退出同步代码块 会归还锁对象 } } }; Thread t0 = new Thread(r, "张三"); Thread t1 = new Thread(r, "李四"); t0.start(); t1.start(); \`\`\` #### 练习 三个人共同买100张票 买完为止 三个人各自买100张票 只要有一人买到第100张票则 所有人都停止买票 #### 方案2:同步方法 在设计方法时 添加synchronized关键字 ===》方法体变为单线程业务 \`\`\`java public ... main(String\[\] args){ Runnable r = new Runnable() { Object o = new Object(); // String name = ""; Integer num = 100; @Override public void run() { xxx xxx //其他线程被阻塞在此处 show(); xxx xxx } public synchronized void show(){ //内部代码变为单线程业务 } }; } \`\`\` #### 方案3:方法中的同步代码块 任何方法中都可以添加同步代码块结构 \`\`\`java public void show(){ //外部代码可以多线程 //其他线程阻塞在此 synchronized (锁对象) { //内部业务变为单线程业务 } //外部代码依然多线程 } \`\`\` #### 方案4:Lock锁 \`\`\`java Runnable r = new Runnable() { Integer num = 100; //创建Lock锁对象 Lock lock = new ReentrantLock(); @Override public void run() { // TODO Auto-generated method stub while (num \> 0) { // 当内部有线程执行时 其他线程被阻塞在此处 try { Thread.sleep(10);// 抱着锁睡觉 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //synchronized (o) { lock.lock(); // 同步代码块 内部的代码 同时只会有一个线程进入执行 if (num \> 0) { System.out.println(Thread.currentThread().getName() + "买到第" + (101 - num) + "张票"); num--; } //} lock.unlock(); //如果想要提前释放锁 必须执行该代码 // 当内部线程执行完毕 退出同步代码块 会归还锁对象 } } }; Thread t0 = new Thread(r, "张三"); Thread t1 = new Thread(r, "李四"); t0.start(); t1.start(); \`\`\` #### 死锁 1.互斥原则:一个资源(对象锁) 同时只能被一个线程使用 2.请求与保持:一个线程请求资源 另一个线程保留资源不释放 3.不剥夺原则:线程已经获取的锁对象 未使用完之前 不可被强制收回 4.循环等待:若干线程之间形成一种首尾相接 互相等待资源的关系 \`\`\`java Runnable r = new Runnable() { Object o1 = new Object(); Object o2 = new Object(); @Override public void run() { // TODO Auto-generated method stub String name = Thread.currentThread().getName(); //乙 synchronized (o1) { System.out.println(name+"获得了锁1"); //Thread.sleep(10); synchronized (o2) { System.out.println(name+"获得了锁2"); // System.out.println(name+"释放了锁2"); } System.out.println(name+"释放了锁1"); } //甲 synchronized (o2) { System.out.println(name+"获得了锁2"); //Thread.sleep(10); synchronized (o1) { System.out.println(name+"获得了锁1"); // System.out.println(name+"释放了锁1"); } System.out.println(name+"释放了锁2"); } } }; new Thread(r,"甲").start(); new Thread(r,"乙").start(); \`\`\` ### 常用方法 void sleep(long) 当前线程休眠 参数毫秒数 之后自动唤醒 让出CPU 但是不会让出锁 Thread类 void interrupt() 打断某个线程中可以被打断的业务 比如休眠 该线程后续任务正常执行 当前线程处于被打断过的状态 可以使用线程对象.isInterrupted() true/false void yield() 线程礼让 只在那一刻让出CPU 马上进入就绪状态 争抢CPU 不一定让得出去 void join() 强制执行 并且保证强制执行的新线程必须执行完毕后 原线程才可继续 等待 void wait() 任何对象都有的等待方法 必须通过锁对象去等待 让出CPU 让出锁 Object类 唤醒 void notify() 使用【同一把锁】对象去唤醒 因该锁而等待的线程 而且该方法 不会放出锁 void notifyAll() 使用【同一把锁】去唤醒所有 因该锁而等待的线程 #### 生产者模式 #### 案例 买包子 卖包子 \`\`\`java //\*\*\*等待唤醒 依靠全局同一个锁对象关联 Object o = new Object(); //消费者线程 Runnable r = new Runnable() { @Override public void run() { // TODO Auto-generated method stub String name = Thread.currentThread().getName(); System.out.println(name.equals("甲")?"老板 来笼包子":"老板来十笼包子"); synchronized (o) {//\*\*\*\*获取锁对象 //等待环节 try { o.wait();//让出CPU 让出锁 //唤醒后 必须先拿到锁 从等待位置向后执行 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println(name.equals("甲")?"包子真好吃 最好不要钱":"什么 还要钱"); } }; //生产者线程 Runnable rr = new Runnable() { @Override public void run() { // TODO Auto-generated method stub System.out.println("等等 我去买面"); try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //唤醒小线程 先获得锁对象 synchronized (o) { //o.notify(); //只唤醒一个因为这把锁等待的线程 o.notifyAll(); //唤醒所有因为这把锁而等待的线程 System.out.println("包子好了 来吃"); } } }; //\*\*\*\*先启动消费者 Thread t0 = new Thread(r,"甲"); t0.start(); Thread t1 = new Thread(r,"乙"); t1.start(); Thread.sleep(10);//主线程休眠一下 保证生产者后启动 Thread tt = new Thread(rr); tt.start(); \`\`\` #### 练习 甲到了 吃瓜子 乙到了 喝茶 老板到了 大家开始吃饭吧 ### 其他线程控制方案 1.信号量==》固定线程数量的线程池 信号量:分配了线程 只是在某个环节进行线程数量的约束 固定数量线程池:直接约束提供的线程数量 其他业务只能等待获取线程 #### 案例 只有三个窗口买票 显式一出一进的效果 \`\`\`java Runnable r = new Runnable() { //设置信号量 全局变量 Semaphore s = new Semaphore(3); //如果控制最大线程数量为1 则等同于同步代码块 @Override public void run() { // TODO Auto-generated method stub //判定信号量 多余线程在此处阻塞 try { s.acquire(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } String name = Thread.currentThread().getName(); System.out.println(name+"进入窗口 开始办理业务"); try { Thread.sleep(new Random().nextInt(3000)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(name+"办理完业务 离开窗口"); //更新信号量 s.release(); } }; for(int i =0;i\<10;i++) { new Thread(r,"第"+(i+1)+"人").start(); } \`\`\` 2.同步屏障 ==》需要足够的线程触发后续任务 #### 案例 甲到了喝杯茶 乙到了吃会儿瓜 老板到了 开饭 甲说 吃点菜 乙说 喝点酒 老板说 吃吃喝喝 集体大合照 \`\`\`\`java //设置线程数量 只有执行的线程达到这个数量 才会执行共同任务和各线程后续任务 CyclicBarrier cb = new CyclicBarrier(3, new Runnable() { @Override public void run() { // TODO Auto-generated method stub try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("人员起了 来个大合照"); } }); Runnable r = new Runnable() { @Override public void run() { // TODO Auto-generated method stub String n = Thread.currentThread().getName(); if(n.equals("甲")) { System.out.println("喝口茶"); }else if(n.equals("乙")) { System.out.println("吃会儿瓜"); }else { System.out.println("开饭开饭"); } try { //线程走到此处 开始判定 cb.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (BrokenBarrierException e) { // TODO Auto-generated catch block e.printStackTrace(); } if(n.equals("甲")) { System.out.println("吃点菜"); }else if(n.equals("乙")) { System.out.println("喝点酒"); }else { System.out.println("吃吃喝喝"); } } }; new Thread(r,"甲").start(); new Thread(r,"乙").start(); Thread.sleep(2000); new Thread(r,"老板").start(); \`\`\`\`