java多线程
进程、线程与多线程
- 进程是执行程序的一次执行过程,是一个动态的概念,是系统支援分配的单位
- 通常一个进程可以包含一个或多个线程。线程是CPU调度和执行的单位
- 线程就是独立执行的路径,由cpu调度
- 线程会带来额外的开销,如cpu调度时间,并发控制开销
- 每个线程在自己的工作内存中交互,内存控制不当会造成数据的不一致
- main()称之为主线程,为系统的入口,用于执行整个程序
- 程序运行时,即使没有自己创建线程,后台也会有多个线程如主线程,gc线程
- 很多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即只有一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以有了同时执行的错觉
线程的三种创建方式
- Thread class:继承Thread类
- Runnable接口:实现Runnable接口
- Callable接口:实现Callable接口
通过继承Thread创建线程
注意:Thread类实现了Runnable接口
流程:- 1. 自定义线程类继承Thread类
- 2. 重写其run()方法,编写程序执行体
- 3. 创建线程对象,调用start()方法启动线程
复制代码- public class MyThread extends Thread {
- @Override
- public void run() {
- //run方法线程体
- for (int i = 0; i < 5; i++) {
- System.out.println("run方法线程--"+i);
- }
- }
- public static void main(String[] args) {
- //run方法线程
- MyThread myThread = new MyThread();
- //启动线程,另外线程只能启动一次死亡之后不能重新启动
- myThread.start();
- //主线程
- for (int i = 0; i < 5; i++) {
- System.out.println("主线程--"+i);
- }
- }
- }
- /*out:
- 主线程--0
- run方法线程--0
- 主线程--1
- run方法线程--1
- 主线程--2
- run方法线程--2
- 主线程--3
- run方法线程--3
- 主线程--4
- run方法线程--4
- 注意:输出结果不唯一,每次执行结果不一样
- 线程开启不一定立即执行,是由cpu调度执行
复制代码 实现Runnable接口创建线程
- 1. 自定义线程类实现Runnable接口
- 2. 重写其run()方法,编写程序执行体
- 3. 创建该实现类对象,创建Thread线程对象(或者创建Thread对象),并且向Thread对象丢入Runnable实现类(代理方法)
复制代码- public class MyThread implements Runnable {
- @Override
- public void run() {
- //run方法线程体
- for (int i = 0; i < 5; i++) {
- System.out.println("run方法线程--"+i);
- }
- }
- public static void main(String[] args) {
- //run方法线程
- MyThread myThread = new MyThread();
- //静态代理
- new Thread(myThread).start();
- //主线程
- for (int i = 0; i < 5; i++) {
- System.out.println("主线程--"+i);
- }
- }
- }
- /*
- 主线程--0
- run方法线程--0
- 主线程--1
- run方法线程--1
- 主线程--2
- 主线程--3
- run方法线程--2
- 主线程--4
- run方法线程--3
- run方法线程--4
复制代码 相比于前一种更推荐本方法:避免单继承局限,灵活方便,方便一个对象被多个线程使用
例如:- public class MyThread implements Runnable {
- private int number=10;
- @Override
- public void run() {
- while (true){
- if(number<=0){
- break;
- }
- //线程休眠,防止cpu速度太快瞬间把票抢完
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- //当前线程名字
- System.out.println(Thread.currentThread().getName()+"--"+number--);
- }
- }
- public static void main(String[] args) {
- //run方法线程
- MyThread myThread = new MyThread();
- //第二个参数为线程名字
- new Thread(myThread,"李").start();
- new Thread(myThread,"马").start();
- new Thread(myThread,"王").start();
- }
- }
- /*
- 马--9
- 王--10
- 李--10
- 马--8
- 李--8
- 王--8
- 马--7
- 王--6
- 李--6
- 王--5
- 马--4
- 李--3
- 李--0
- 马--2
- 王--1
- 注意:至于为什么出现两个人得到同一个数字,或者出现0,则是一些经典的线程同步问题
复制代码 线程的五大状态
线程方法
停止线程
- 可以但不推荐使用JDK提供的stop()、destroy()方法,已经废弃
- 推荐线程自己停下来
- 建议使用一个标志位进行终止变量,当flag=false终止线程运行
- //<>中填写返回值类型(下面call方法的返回值)
- public class MyThread implements Callable<Boolean> {
- //重写call方法,返回值保持和<>中的一样
- @Override
- public Boolean call() throws Exception {\
- //具体线程操作写在这里
- return false;
- }
- public static void main(String[] args) throws ExecutionException, InterruptedException {
- MyThread myThread1 = new MyThread();
- MyThread myThread2 = new MyThread();
- MyThread myThread3 = new MyThread();
- //创建执行服务:newFixedThreadPool线程池,参数为线程池大小
- ExecutorService ser = Executors.newFixedThreadPool(3);
- //提交执行:<>中类型就是call的返回类型
- Future<Boolean> r1 = ser.submit(myThread1);
- Future<Boolean> r2 = ser.submit(myThread2);
- Future<Boolean> r3 = ser.submit(myThread3);
- //获取结果:call()返回值
- Boolean rs1 = r1.get();
- Boolean rs2 = r2.get();
- Boolean rs3 = r3.get();
- //关闭服务
- ser.shutdownNow();
- }
- }
复制代码 线程休眠
- sleep()指定当前线程阻塞的毫秒数
- 运行状态转为阻塞状态
- sleep存在异常InterruptedException
- sleep时间到达后进入就绪状态
- 其可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
没啥可说的固定写法:Thread.sleep()
线程礼让
- 即让当前正在执行的线程暂停,但不阻塞
- 把线程从运行转为就绪状态
- 让cpu重新调度,礼让不一定成功,看cpu心情
- 固定写法:Thread.yield();
- //MyThread是Runnable接口的实现类
- MyThread myThread = new MyThread();
- //静态代理
- new Thread(myThread).start();
复制代码 线程强制执行
- Join合并线程,待此线程完成后,才去执行其他线程
- 可以看作插队
- public interface MyInterface {
- void lambda();
- }
- class main {
- public static void main(String[] args) {
- // 无参且一行
- MyInterface myInterface =() -> System.out.println("hello world");
- // 有参数a
- // MyInterface myInterface =a -> System.out.println("hello world");
- // 多行
- // MyInterface myInterface1 = ()->{
- // System.out.println();
- // System.out.println()
- // };
-
- // 上述代码完全类似下面的匿名内部类
- // MyInterface myInterface1 = new MyInterface() {
- // @Override
- // public void lambda() {
- // System.out.println("hello world");
- // }
- // };
- }
- }
- //out:hello world
复制代码 线程状态观测
- public class MyThread implements Runnable {
- private boolean flag = true;
- @Override
- public void run() {
- int i = 0;
- while (flag){
- System.out.println("run........Thread"+i++);
- }
- }
- public void stop(){
- this.flag = false;
- System.out.println("线程已经停止");
- }
- public static void main(String[] args) {
- MyThread myThread = new MyThread();
- new Thread(myThread).start();
- for (int i = 0; i < 10; i++) {
- //不加这一句的话循环速度太快以至于线程还没运行
- System.out.println("main---"+i);
- if(i==5){
- //调用自己写的stop方法
- myThread.stop();
- }
- }
- }
- }
- /*
- main---0
- run........Thread0
- main---1
- run........Thread1
- main---2
- run........Thread2
- main---3
- run........Thread3
- main---4
- run........Thread4
- main---5
- run........Thread5
- 线程已经停止
- main---6
- main---7
- main---8
- main---9
复制代码 线程优先级
- 线程优先级用数字表示:Thread.MIN_PRIORITY=1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
上面为最小最大正常优先级,均为常量
- 可以使用以下方式改变或者获取优先级:
1. .getPrioriyt
1. .setPriority(int xxx)
- 另外,优先级高并不是一定先执行,而是相对来说权重更高,到底先还是后看cpu调度
- public class MyThread implements Runnable {
- private boolean flag = true;
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName()+"开始执行");
- Thread.yield();
- System.out.println(Thread.currentThread().getName()+"完成执行");
- }
- public static void main(String[] args) {
- MyThread myThread = new MyThread();
- MyThread myThread1 = new MyThread();
- new Thread(myThread,"a").start();
- new Thread(myThread1,"b").start();
- }
- }
- /*
- a开始执行
- b开始执行
- b完成执行
- a完成执行
复制代码 守护线程
- daemon
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程完毕
- 守护线程如后台记录操作日志、监控内存、垃圾回收等待
- 守护线程:字面意思守护用户线程,虚拟机无视是否还有守护线程存在,当用户线程结束时虚拟机关闭
- public class MyThread implements Runnable {
- @Override
- public void run() {
- for (int i = 0; i < 5; i++) {
- System.out.println("VIP驾到通通闪开!"+i);
- }
- }
- public static void main(String[] args) throws InterruptedException {
- MyThread myThread1 = new MyThread();
- Thread thread1 = new Thread(myThread1, "vip");
- //主线程
- for (int i = 0; i < 10; i++) {
- if(i==5){
- thread1.start();
- thread1.join();
- }
- System.out.println("我是一个主线程"+i);
- }
- }
- }
- /*
- 我是一个主线程0
- 我是一个主线程1
- 我是一个主线程2
- 我是一个主线程3
- 我是一个主线程4
- VIP驾到通通闪开!0
- VIP驾到通通闪开!1
- VIP驾到通通闪开!2
- VIP驾到通通闪开!3
- VIP驾到通通闪开!4
- 我是一个主线程5
- 我是一个主线程6
- 我是一个主线程7
- 我是一个主线程8
- 我是一个主线程9
复制代码 线程同步
- 就是多个线程操作同一个资源
- 线程同步其实就是一个等待机制,多个需要同时访问对象的线程进入对象等待池形成队列
- 并发:同一个对象被多个线程操作。如买票
- 队列和锁保障线程安全性
- 为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized
- 与此同时当一个线程获得对象的排它锁,独占资源,其他线程必须等待使用后释放锁即可,可能存在以下问题:
线程同步操作多种不安全问题,具体可以看上面实现Runnable接口创建线程的例子
synchronized(锁)
- 针对上面所说的问题:我们可以用锁来解决,即synchronized关键字,它包括方法和块两大用法
- 同步方法,synchronized方法控制对“对象”的访问,每个对象对应一把锁。每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则阻塞,方法一旦执行就会独占该锁,直到该方法返回。缺陷:若将一个大方法申明为synchronized将会影响效率
- 同步块:synchronized(obj){} obj称之为同步监视器,其可以是任何对象,但是推荐共享资源作为同步监视器。另外同步方法不用同步监视器因为其同步监视器就是this,就是这个对象本身,或者class
- 同步监视器执行过程:
1. 第一个线程访问,锁定同步监视器
2. 第二个线程访问,发现已经被锁定,无法访问
3. 第一个线程访问完毕,解锁同步监视器
4. 第二个线程访问,发现同步监视器没有锁,然后锁定并且访问
使用同步方法解决问题:- public static void main(String[] args) throws InterruptedException {
- Thread thread = new Thread(() -> {
- for (int i = 0; i < 5; i++) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- System.out.println("////////");
- });
- //观测状态
- Thread.State state = thread.getState();
- System.out.println(state);
- // 启动线程
- thread.start();
- state = thread.getState();
- System.out.println(state);
- // 只要线程不终止一直输出状态
- while (state != Thread.State.TERMINATED){
- Thread.sleep(1000);
- state = thread.getState();
- System.out.println(state);
- }
- }
- /*
- NEW
- RUNNABLE
- RUNNABLE
- TIMED_WAITING
- TIMED_WAITING
- RUNNABLE
- TIMED_WAITING
- ////////
- TERMINATED
复制代码 锁块:- public class MyThread implements Runnable {
- @Override
- public void run() {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName());
- }
- public static void main(String[] args) throws InterruptedException {
- //主线程优先级:主线程优先级无法修改
- System.out.println(Thread.currentThread().getName()+"-----"+Thread.currentThread().getPriority());
- MyThread myThread = new MyThread();
- Thread t1 = new Thread(myThread,"t1");
- Thread t2 = new Thread(myThread,"t2");
- Thread t3 = new Thread(myThread,"t3");
- Thread t4 = new Thread(myThread,"t4");
- Thread t5 = new Thread(myThread,"t5");
- // 一定要注意:先设置优先级再启动否则没用
- t1.start();
- t2.setPriority(1);
- t2.start();
- //设置为最大优先级
- t3.setPriority(Thread.MAX_PRIORITY);
- t3.start();
- }
- }
- /*
- main-----5
- t3
- t2
- t1
复制代码 CopyOnWriteArrayList
- Thread thread = new Thread();
- //默认是false代表用户线程,设置为true表示守护线程
- thread.setDaemon(true);
复制代码 死锁
多个线程各自占有一些共享资源,同时互相等待对方占有资源,从而导致两个或多个线程都在等待对方释放资源停止执行的情况。
某一个同步块同时拥有两个以上对象的锁,就可能出现死锁- public class MyThread implements Runnable {
- private int number=10;
- private boolean flag = true;
- @Override
- public void run() {
- while (flag) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- buy();
- }
- }
- //上锁,run方法也可以锁
- public synchronized void buy() {
- if (number < 1) {
- flag=false;
- return;
- }
- System.out.println(Thread.currentThread().getName() + "--" + number--);
- }
- public static void main(String[] args) {
- MyThread myThread = new MyThread();
- new Thread(myThread,"李").start();
- new Thread(myThread,"马").start();
- new Thread(myThread,"王").start();
- }
- }
- /*
- 李--10
- 王--9
- 马--8
- 马--7
- 李--6
- 王--5
- 马--4
- 李--3
- 王--2
- 马--1
复制代码 产生死锁的必要条件
Lock(锁)
- JDK5.0开始,java提供更加强大的线程同步机制--通过显式定义同步锁对象来实现同步。同步锁使用Lock充当
- 每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应当先获得Lock锁
- ReentranLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁
- public class MyThread implements Runnable {
- private int number=10;
- private boolean flag = true;
- @Override
- public void run() {
- while (flag) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- //同步块,()填变化的量必须是引用类型,锁定的就是传入参数
- synchronized (Integer.valueOf(number)){
- if (number < 1) {
- flag=false;
- }
- else {
- System.out.println(Thread.currentThread().getName() + "--" + number--);
- }
- }
- }
- }
- public static void main(String[] args) {
- MyThread myThread = new MyThread();
- new Thread(myThread,"李").start();
- new Thread(myThread,"马").start();
- new Thread(myThread,"王").start();
- }
- }
- /*
- 马--10
- 王--9
- 李--8
- 李--7
- 王--6
- 马--5
- 马--4
- 李--3
- 王--2
- 马--1
复制代码 线程协作--生产者消费者问题
生产者消费者问题:生产者消费者共享一个资源,并且生产者和消费者之间相互依赖,互为条件,操作系统学过的不想多说具体如下:
下面两个解决办法后面有具体演示
解决方法1:
解决方法二:信号灯法
java提供了几个方法解决线程之间的通信问题:
注意:以下均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常,另外sleep不会释放锁,wait会释放锁
解决方法一:管程法
- //线程安全的arraylist
- CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
- for (int i = 0; i < 1000; i++) {
- new Thread(()->{
- list.add(Thread.currentThread().getName());
- }).start();
- }
- Thread.sleep(3000);
- System.out.println(list.size());
- //1000
复制代码 上面实现的存在一定问题,但是要传达的思想就这样
解决方法二:信号灯法
- //当mirror和lipstick都只有一份时
- //获得mirror
- synchronized (mirror){
- // 获得了mirror,此时lipstick被另外一个线程占据等待
- // 其释放,与此同时占据lipstick的线程也在等待它释放mirror
- // 于是死锁发生了
- synchronized (lipstick){
-
- }
- }
复制代码 线程池
- 经常创建和销毁、使用量大的资源,对性能影响很大
- 思路:提前创建好多个线程,放入线程池,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
- 好处:
- 提高响应速度
- 降低资源消耗
- 便于线程管理
- corePoolSize:核心池大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多少时间后终结
- 相关api:
- ExecutorService :真正的线程池接口。常见子类ThreadPoolExecutor
- 相关方法:
- Executors:工具类,用于创建并返回不同类型的线程池
- public class MyThread implements Runnable {
- private static Integer number=10;
- private boolean flag = true;
- //定义lock锁
- private final ReentrantLock lock = new ReentrantLock();
- @Override
- public void run() {
- while (flag) {
- try {
- Thread.sleep(100);
- lock.lock();//加锁,上锁区域就是lock到unlock区域
- if (number < 1) {
- flag=false;
- }
- else {
- System.out.println(Thread.currentThread().getName() + "--" +number--);
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- //解锁
- lock.unlock();
- }
- }
- }
- public static void main(String[] args) {
- MyThread myThread = new MyThread();
- new Thread(myThread,"李").start();
- new Thread(myThread,"马").start();
- new Thread(myThread,"王").start();
- }
- }
- /*
- 马--10
- 王--9
- 李--8
- 李--7
- 王--6
- 马--5
- 王--4
- 李--3
- 马--2
- 马--1
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |