网站建设属于软件开发,怎样做一个公司网站,wordpress多站点版,海城整站优化目录初识多线程多线程的实现方式常见的成员方法线程安全的问题死锁生产者和消费者线程池自定义线程池初识多线程
什么是多线程? 线程 线程是操作系统能够进行运算调度的最小单位。线程被包含在进程之中#xff0c;是进程中的实际运作单位。 简单理解:应用软件中互相独立是进程中的实际运作单位。 简单理解:应用软件中互相独立可以同时运行的功能 那什么是进程呢 进程 进程是程序的基本执行实体比如打开任务管理器可以看到其中有很多的进程
多线程作用提高效率多线程的两个概念
并发 并发:在同一时刻有多个指令在单个CPU上交替执行
并行 并行:在同一时刻有多个指令在多个CPU上同时执行 多线程的实现方式
①继承Thread类的方式进行实现
多线程的第一种启动方式:
1自己定义一个类继承Thread 2重写run方法 3创建子类的对象并启动线程 public class MyThread extends Thread{Overridepublic void run() {//书写线程要执行代码for (int i e; i 100; i) {system.out.println(getName( ) Helloworld );}}} public static void main( String[] args) {MyThread t1 new MyThread();MyThread t2 new MyThread() ;t1.setName(线程1);t2.setName(线程2);t1.start();t2.start();} ②实现Runnable接口的方式进行实现
多线程的第二种启动方式: 1.自己定义一个类实现Runnable接口2.重写里面的run方法 3.创建自己的类的对象 4.创建一个Thread类的对象并开启线程 public static void main(String[] args) {//创建实现Runnable接口的类的对象RunnableInf run new RunnableInf();//创建线程对象Thread t1 new Thread(run);Thread t2 new Thread(run);//给线程设置名字t1.setName(线程一);t2.setName(线程二);//开启线程t1.start();t2.start();}public class RunnableInf implements Runnable{Overridepublic void run() {//书写线程执行代码for (int i 0; i 100; i){System.out.println(Thread.currentThread().getName()hello world);}}
} ③利用Callable接口和Future接口方式实现
多线程的第三种实现方式: 特点:可以获取到多线程运行的结果 步骤如下 1创建一个类MyCallable实现callable接口 2重写call (是有返回值的表示多线程运行的结果) 3创建MyCallable的对象表示多线程要执行的任务) 4创建FutureTask的对象作用管理多线程运行的结果 5创建Thread类的对象并启动表示线程) public static void main(String[] args) throws ExecutionException, InterruptedException {//创建Mycallable的对象表示多线程要执行的任务)MyCallable mc new MyCallable();//创建FutureTask的对象作用管理多线程运行的结果)FutureTaskInteger ft new FutureTask(mc ) ;//创建线程的对象Thread t1 new Thread(ft);//启动线程t1.start();//获取线程运行结果Integer result ft.get();System.out.println(result);}public class MyCallable implements CallableInteger {Overridepublic Integer call() throws Exception {//求100之间的和int sum 0;for (int i 0; i 100; i ){sum i;}return sum;}
} 三种实现方式的对比 常见的成员方法 方法细节点 void setName( string name) 设置线程的名字(构造方法也可以设置名字 细节: 1、如果我们没有给线程设置名字线程也是有默认的名字的 格式:Thread-X(X序号从0开始的 2、如果我们要给线程设置名字可以用set方法进行设置也可以构造方法设置记得继承Thread类的构造方法快捷键AltIns static Thread currentThread( ) 获取当前线程的对象 细节: 当JVM虚拟机启动之后会自动的启动多条线程其中有一条线程就叫做main线程 他的作用就是去调用main方法并执行里面的代码在以前我们写的所有的代码其实都是运行在main线程当中 static void sleep( long time) 让线程休眠指定的时间单位为毫秒 细节: 1、哪条线程执行到这个方法那么哪条线程就会在这里停留对应的时间 2、方法的参数:就表示睡眠的时间单位毫秒1秒 1000毫秒 3、当时间到了之后线程会自动的醒来继续执行下面的其他代码 线程调度两种方式
抢占式调度 最大特点是随机根据优先级来抢占cpu资源优先级越高抢占概率越大非抢占式调度 特点你一次我一次有规律
优先级是0档到10档默认优先级都是5 从Thread源码中可以看到 final void setDaemon( boolean on) 设置为守护线程细节: 当其他的非守护线程执行完毕之后守护线程会陆续结束不是立刻结束
通俗易懂照下图案例 当女神线程结束了那么备胎也没有存在的必要了
女神线程 备胎线程
main方法中
最后结果就是当线程1循环10次结束后守护线程陆续结束不会循环完也不会立刻停止 守护线程使用场景 比如QQ聊天传输文件 出让线程解释 插入线程演示 public static void main(String[] args) throws InterruptedException {//创建实现Runnable接口的类的对象RunnableInf run new RunnableInf();//创建线程对象Thread t1 new Thread(run);//Thread t2 new Thread(run);//给线程设置名字t1.setName(线程一); //开启线程t1.start(); //把t1线程插入到当前线程之前t1.join(); //这个代码运行在哪个线程上就插入在哪个线程前for (int i 0; i 10; i){System.out.println(main线程i);}}线程的生命周期 线程安全的问题
由于线程抢占cpu资源是随机的所以在执行业务操作时可能同一个业务多条线程并行执行而造成数据错乱或丢失比如买票的经典案例有可能当一个线程进去执行买票逻辑时另一个线程也进去了结果导致卖出了两张同样编号的票。经典问题就是超卖一票多卖有票没卖。
解决方法自然就是给执行的业务代码加上锁保证原子性 方法之一同步代码块 把操作共享数据的代码锁起来 特点1:锁默认打卉有一个线程进去了锁自动关闭
特点2:里面的代码全部执行完毕,线程出来锁自动打开同步代码块使用如下卖100张票的案例
public class MyThread extends Thread{//表示这个类的所有对象共享ticket数据static int ticket 0;//创建锁对象一定要唯一static Object object new Object();Overridepublic void run() {while (true){//同步代码块synchronized (object){if (ticket 100){ticket;System.out.println(getName()正在卖第 ticket 张票);}else {break;}}}}
}
public class Treaddemo {public static void main(String[] args) {MyThread t1 new MyThread();MyThread t2 new MyThread();t1.setName(窗口1);t2.setName(窗口2);t1.start();t2.start();}
}方法之二同步方法 就是把synchronized关键字加到方法上
特点1:同步方法是锁住方法里面所有的代码
特点2:锁对象不能自己指定 非静态: this 静态: 当前类的字节码文件对象 卖100张票的案例 public class MyRunnable implements Runnable{int ticket 0;//由于MyRunnalbe方法只new了一个对象可以不用把ticket设置为共享变量Overridepublic void run() {while (true){if (method()) break;}}private synchronized boolean method() {synchronized (MyRunnable.class){if ( ticket 100 ){return true;}else {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket;System.out.println(Thread.currentThread().getName() 正在卖第 ticket 张票);}}return false;}
}public class Threaddemo {public static void main(String[] args) {MyRunnable mr new MyRunnable();Thread t1 new Thread(mr);Thread t2 new Thread(mr);Thread t3 new Thread(mr);t1.setName(窗口一);t2.setName(窗口二);t3.setName(窗口三);t1.start();t2.start();t3.start();}
} 方法之三同步方法 为了更清晰的表达如何加锁和释放锁JDK5以后提供了一个新的锁对象Lock手动上锁、手动释放锁 Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作 Lock中提供了获得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁Lock是接口不能直接实例化通常采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法
ReentrantLock():创建一个ReentrantLock的实例
还是以买100张票为案例使用锁的方式代码如下
public class LockThread extends Thread{static int ticket 0;static Lock lock new ReentrantLock();public LockThread() {}public LockThread(String name) {super(name);}Overridepublic void run() {while (true){//这次换用锁的方式lock.lock();//上锁try {if (ticket 100){Thread.sleep(100);ticket;System.out.println(getName()正在卖第 ticket 张票);}else {break;}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}}}
}
public class MainThread {public static void main(String[] args) {LockThread t1 new LockThread(窗口一);LockThread t2 new LockThread(窗口二);LockThread t3 new LockThread(窗口三);t1.start();t2.start();t3.start();}
}死锁
通俗点说就是锁嵌套了线程1拿了A锁就在线程1拿A锁时线程2入了B锁而线程1中间的操作还要再入B锁才能执行业务并结束而线程2中间操作还要再入A锁才能执行业务并结束。最终二者都在等待对方释放锁造成了死锁情况 生产者和消费者
等待唤醒机制 等待唤醒机制会让两个线程轮流执行标准的你一次我一次 细节点notify()是随机唤醒一个线程不容易控制一般使用的是notifyAll()方法
下面是经典的消费者和生产者案例代码消费者是食客生产者是厨师中间控制是桌子控制线程执行
中间控制者桌子
public class Desk {/*** 控制消费者和生产者的执行*///是否有面条 0没有面条 1有面条public static int foodFlag 0;//总个数,也就是消费者需要的总个数public static int count 10;//锁对象public static Object lock new Object();
}食客
public class Eater extends Thread{/*** 1.循环* 2.同步代码块* 3.判断共享数据是否到了末尾到了末尾执行的逻辑* 4.判断共享数据是否到了末尾没到末尾执行的逻辑*/Overridepublic void run() {while (true){if (Desk.count 0){break;}synchronized (Desk.lock){if (Desk.foodFlag 0){//先看是否有产品没有就等待try {Desk.lock.wait();//让当前线程跟锁绑定} catch (InterruptedException e) {throw new RuntimeException(e);}}else {Desk.count--;System.out.println(消费者正在消费产品还需要消费数量 Desk.count);//消费完唤醒生产者继续做Desk.lock.notifyAll();//修改桌子状态Desk.foodFlag 0;}}}}
}
厨师
public class Cooker extends Thread{Overridepublic void run() {while (true){if (Desk.count 0){break;}synchronized (Desk.lock){if (Desk.foodFlag 1){try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else {//修改桌上食物状态Desk.foodFlag 1;System.out.println(生产者生产了面条);//唤醒消费者Desk.lock.notifyAll();}}}}
}main方法
public class TestDemo {public static void main(String[] args) {Eater e new Eater();Cooker c new Cooker();e.setName(消费者);c.setName(生产者);e.start();c.start();}
} 注意点 Desk. lock. wait(); //让当前线程跟锁进行绑定 Desk.Lock.notifyAll(); //唤醒跟这把锁绑定的所有线程 这里之所以用锁对象调用是避免notifyAll()方法唤醒所有线程包括方法外的比如系统线程等用对象调用可以指明唤醒的是哪个线程 这里用Desk类中的lock对象调用是应为lock对象是唯一的只是在Desk类中new了一次 线程池 用线程池作为容器存放线程 ①创建一个池子池子中是空的
②提交任务时池子会创建新的线程对象任务执行完毕线程归还给池子下回再次提交任务时不需要创建新的线程直接复用已有的线程即可
③但是如果提交任务时池子中没有空闲线程也无法创建新的线程任务就会排队等待
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。 自定义线程池 线程池工作流程它会有核心线程数量临时线程数量队列长度空闲时间这个几个因素影响决定 当任务数量超过核心线程数量时就会让多余任务在队列排队 当任务数量超过核心线程数量且队列排满的情况才会使用临时线程来处理任务 当任务数量超过核心线程数量最大排队数量以及临时线程数量的总和全负载时多余任务会根据线程池的拒绝策略来丢弃或处理
临时线程的空闲时间在超过线程池初始化规定的时间就会销毁 而核心线程只有在销毁线程池时才会销毁 创建线程池会发现有7个参数 最后任务拒绝策略有以下几种 创建自定义线程池代码如下 最后感谢您的阅览希望这篇文章能为您解除疑惑