目录
线程
线程执行方式
实现runnable接口实现多线程的好处:
多线程
并发与并行
进程和线程
线程调度
解决线程安全问题
等待唤醒机制
案例:生产者消费者(吃包子案例)
线程的状态6种
线程池:
引用:https://blog.csdn.net/weixin_41891854/article/details/81265772
//实现runnable接口 public class RunableIml implements Runnable{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+i); } } } //使用 public class TestRunnable { public static void main(String[] args) { RunableIml run = new RunableIml(); Thread t = new Thread(run); t.start(); for (int i = 0; i < 200; i++) { System.out.println(Thread.currentThread().getName()+i); } } }
1、避免单继承的局限性,继承Thread类后就不能继承其他的类了,实现runnable接口还可以继承其他的类。
2、实现runnable接口增强了程序的扩展性,降低了程序的耦合性,实现runnable把设置线程任务与开启线程进行了分离。
重写的run方法来设置线程任务,创建线程Thread对象来开启新线程
new Thread时,传递不同的接口实现类完成不同的线程任务
class RunableIml1 implements Runnable{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+i); } } } class RunableIml2 implements Runnable{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("hello "+i); } } } public class TestRunnable { public static void main(String[] args) { Thread t1 = new Thread(new RunableIml1()); t1.start(); Thread t2 = new Thread(new RunableIml2()); t2.start(); for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+i); } } }好处:简化代码
把子类继承父类,重写父类方法,创建子类对象一步完成
把实现类实现接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,这个对象没名字
格式:new 父类/接口{
重写的方法
}
实现代码:
package testmap; public class NiMingClass { public static void main(String[] args) { //使用Thread父类 new Thread(){ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+i); } } }.start(); //使用runnable接口 Runnable r = new Runnable(){ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("hello "+i); } } }; Thread t = new Thread(r); t.start(); //使用runnable接口更加简化 Thread t2 = new Thread(new Runnable(){ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("word "+i); } } }); t2.start(); } }进程:
线程:
主线程:
单线程存在弊端:
代码实现
package testmap; /** * 多线程: * 实现步骤: * 1、创建一个Thread的子类 * 2、在Thread的子类中重写Thread类中的run方法,设置线程任务 * 3、创建Thread的子类对象 * 4、调用Thread类中的start方法,开启新的线程,执行run方法 * 结果是两个程序并发的执行,当前线程和另一个线程。 * 多次启动一个线程是非法的。特别是当一个线程已经执行后,不能重新启动 */ class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("run "+i); } } } public class MultiThread { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.run(); for (int i = 0; i < 20; i++) { System.out.println("main "+i); } } }
单线程不会出现线程安全问题
多线程不访问共享数据也不会有安全问题
只有多线程访问了共享数据才会出现安全问题
线程不安全的卖票案例:
package testmap; /** * 实现卖票案例 * * 通过代码块中的锁对象,保证多个线程使用的锁是同一个 * 锁对象的作用:把同步代码块锁住,只让线程在同步代码块中执行 */ class RunnableImpl implements Runnable{ private int tickets = 100; //创建一个锁对象,锁可以是任意的 Object obj = new Object(); /** * 卖票 */ @Override public void run() { while(true){ // synchronized (obj) { if (tickets > 0) { try { //提高线程不安全,卖票出错的可能性 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在卖票: " + tickets); tickets--; } // } } } } /** * 模拟卖票,创建三个线程,同时卖票 */ public class Tickets { public static void main(String[] args) { RunnableImpl run = new RunnableImpl(); Thread t1 = new Thread(run); Thread t2 = new Thread(run); Thread t3 = new Thread(run); t1.start(); t2.start(); t3.start(); } }改进后对的卖票
同步代码块原理:
1、synchronized同步
2、while轮询
3、wait/notify机制
4、管道通信
waiting状态不会浪费CPU也不会竞争锁,等待的线程需要notify来唤醒它
代码:包子类
package baozi; /** * 包子类 */ public class Baozi { public String pi; public String xian; boolean flag; }包子铺
package baozi; /** *包子铺 */ public class BaoZiPu extends Thread { private Baozi baozi; public BaoZiPu(Baozi baozi) { this.baozi = baozi; } @Override public void run() { int count = 0; while(true){ synchronized (baozi){ if(baozi.flag){ //有包子就等待 try { baozi.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //被唤醒后开始生产包子 if(count%2==0){ baozi.pi = "薄皮"; baozi.xian = "三鲜馅"; }else{ baozi.pi = "冰皮"; baozi.xian = "牛肉馅"; } count++; System.out.println("包子铺正在生产"+baozi.pi+baozi.xian+"的包子"); //3秒生产包子 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //修改包子的状态为有 baozi.flag = true; //唤醒吃货进程 baozi.notify(); // System.out.println("包子铺已经生产好了"+baozi.pi+baozi.xian+"的包子吃货可以吃了"); } } } }吃货类
package baozi; /** * 吃货 */ public class ChiHuo extends Thread{ private Baozi baozi; public ChiHuo(Baozi baozi){ this.baozi = baozi; } @Override public void run() { //while死循环,让吃货一直吃包子、 while(true){ synchronized (baozi){ if(!baozi.flag){ //没包子,吃货等待 try{ baozi.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //被唤醒之后,开始吃包子 System.out.println("吃货正在吃"+baozi.pi+baozi.xian+"包子"); baozi.flag = false; //唤醒包子铺线程 baozi.notify(); System.out.println("吃货吃完了"+baozi.pi+baozi.xian+"包子,包子铺开始生产"); } } } }测试类
package baozi; public class TestBaozi { public static void main(String[] args) { Baozi baozi = new Baozi(); new BaoZiPu(baozi).start(); new ChiHuo(baozi).start(); } }锁.wait(),不带时间参数
锁.sleep()计时等待期间,与锁无关,锁资源不被释放,等待时间后只需要抢夺cpu。
锁.wait(时间参数),带时间参数,会释放锁资源,案例如下
package notify_wait; public class WaitDaiCanDemo2 { public static void main(String[] args) { Object obj = new Object(); new Thread(){ @Override public void run() { synchronized (obj){ try { System.out.println("线程1 开始 wait(1000)"); obj.wait(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); new Thread(){ @Override public void run() { synchronized (obj) { System.out.println("线程2开始执行"); System.out.println(obj); } } }.start(); } }1、sleep(毫秒值):毫秒值结束就会自动醒来。
2、wait(毫秒值):如果,wait在毫秒值结束之前一直没被唤醒,那么他就会自动醒来
案例代码:
package notify_wait; public class WaitDaiCan { public static void main(String[] args) { //创建锁对象 Object obj = new Object(); //创建一个顾客线程(消费者) new Thread(){ @Override public void run() { while (true){ synchronized (obj){ System.out.println("顾客告知老板要的包子种类和数量"); //调用wait进入无限等待状态 try { obj.wait(1000); //线程被唤醒后,会继续执行wait之后的代码 } catch (InterruptedException e) { e.printStackTrace(); } } } } }.start(); } }Blocked状态:一个正在阻塞等待锁对象的线程处于这一状态。
唤醒wait()线程的两种方式:
1、notify():随机唤醒一个正在等待这个锁的线程
2、notifyAll():唤醒所有正在等待这个锁的线程。
还是吃包子的案例:
package notify_wait; public class WaitAndNotify { public static void main(String[] args) { //创建锁对象 Object obj = new Object(); //创建一个顾客线程(消费者) new Thread(){ @Override public void run() { while (true){ synchronized (obj){ System.out.println("顾客告知老板要的包子种类和数量"); //调用wait进入无限等待状态 try { obj.wait(); //线程被唤醒后,会继续执行wait之后的代码 } catch (InterruptedException e) { e.printStackTrace(); } //线程被唤醒后,会继续执行wait之后的代码 System.out.println("顾客开始吃包子"); } } } }.start(); //创建一个老板线程(生产者) new Thread(){ @Override public void run() { while(true){ System.out.println("老板开始做包子,需要5秒"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj){ //老板花5s做包子 obj.notify(); System.out.println("老板做好了包子,告知顾客"); } } } }.start(); } }合理利用线程池的三个好处:
/** * 线程池的使用步骤 * 1、使用线程池的工厂类Executor里面提供的newFixedThreadPool生产一个指定线程数量的线程池 * 2、创建一个雷,实现Runnable接口,重写run方法 * 3、调用ExecutorService中的submit方法,传递线程(实现类),开启线程,执行run方法 * 4、调用ExecutorService中的shutdown方法,销毁线程池(不建议,线程池就是为了重复使用) */
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 创建线程实现类,实现Runnable接口,重写run方法,设置线程任务 */ class RunnableImplement implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"创建了一个新的线程"); } } public class ThreadPoll { public static void main(String[] args) { //使用线程工厂类Ex而粗铜仁市里边提供的静态方法,创建指定线程个数的线程池 ExecutorService executorService = Executors.newFixedThreadPool(4); executorService.submit(new RunnableImplement()); executorService.submit(new RunnableImplement()); executorService.submit(new RunnableImplement()); executorService.submit(new RunnableImplement()); executorService.submit(new RunnableImplement()); executorService.submit(new RunnableImplement()); executorService.submit(new RunnableImplement()); executorService.submit(new RunnableImplement()); executorService.shutdown(); } }线程池用完会归还,会使用多次
