java 线程

进程与线程:

什么是进程?
进程的定义,一直以来没有完美的标准。
进程是程序的一次执行。应用程序以进程的形式,运行于操作系统之上,享受操作系统提供的服务。

什么是Java程序的进程?
Java编写的程序都运行在Java虚拟机(JvM)中,
每当使用java命令启动一个Java应用程序时,就会启动一个JVM进程。在这个JVM进程内部,所有Java程序代码的运行都是以线程来运行的。
什么是线程?
线程是指"进程代码段”的一次的顺序执行流程。线程演进完成后,线程是cpu调度的最小单位。一条进程可以有一个或多个线程,各个线程之间共享进程的内存空间、系统资源,进程仍然是操作系统资源分配的最小的单位。

Java线程和os线程的关系:一对一模型

线程的四种实现方式

  • 继承Thread类,重写run方法;
  • 实现Runnable接口类,实现run方法;以及变形写法,使用lambda表达式实现
  • 使用callable和FutureTask创建异步任务,然后创建线程实例
  • 通过线程池实现

继承Thread类及实现Runnable接口类示例:


public class T01 {
    public static class MyThread extends Thread {
        public void run() {
            System.out.println("MyThread");
        }
    }
    
    public static class MyRunnable implements Runnable{
        @Override
        public void run() {
            System.out.println("MyRunnable");
        }
    
    }
    
    public static void main(String[] args) {
        new MyThread().start();
        new Thread(new MyRunnable()).start();
        new Thread(() -> {
            System.out.println("线程测试");
        }).start();
    }
}

继承Thread类或实现Runnable接口 实现异步任务的问题:

  • 不能获取异步执行目标的结果
  • 不能取消异步执行的任务

解决方法:
使用"可以进行管理的异步任务"相关类:Future接口和FutureTask类型

总结:三种写法最终都是实现Runnable类,实现run方法。

线程的调度模型
目前主要分为两种调度模型:分时调度模型、抢占式调度模型。

  •         分时调度模型

平均分配cpu时间片,每个线程占有的cpu时间片长度一样,平均分配,一切平等

  •         抢占式调度模型

哪个线程的优先级比较高,抢到的cpu时间片的概率就大。java采用的就是抢占式调度模型。

线程的常用方法

  1. sleep方法,线程睡眠;当前线程结束一段时间将cpu让给其他线程运行;

  1. yield方法,线程返回就绪状态,进入等待队列;

  1. join方法,调用其他线程,将其他线程加入的当前线程中运行;


static void sleep() {
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("A" + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    static void yield() {
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("AA" + i);
                if (i % 10 ==0) {
                    Thread.yield();
                }
            }
        }).start();
    }

    static void join() {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("A" + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
           for (int i = 0; i < 10; i++) {
               System.out.println("B" + i);
           }
        });
        t2.start();
    }

    public static void main(String[] args) {
//        sleep();
//        yield();
        join();
    }

synchronized

  • 锁的是对象,而不是代码;

  • 尽量不使用String常量, Integer, Long

  • 当synchronized不指定对象时锁定的是this对象,静态锁定时锁的xx.class;

  • 锁定方法和非锁定方法可同时执行;

  • 当加锁程序发生异常时,应捕获异常;若未捕获异常,synchronized失效,其他线程可立即执行当前程序

  • 锁升级

  • 偏向锁(记录最初运行的线程id,当存在线程争用时,升级为自旋锁)

  • 自旋锁(在cpu上循环等待,当循环等待一定次数时(默认10次),升级为重量级锁)

  • 重量级锁(就绪队列)

加锁代码执行时间短,线程数少,用自旋;执行时间长,线程数多,用重量级

volatile

  • 线程自己的可见性

  • 禁止指令重排序

线程自己的可见性验证


/**
 * 当变量run不加volatile修饰时,main中改变run变量为false,线程t1得到的run为true,while循环不结束
 * 当变量run加volatile修饰时,main中改变run变量为false,线程t1得到的run为false,while循环结束
 */

public class T11_volatile {
    // boolean run = true;
    volatile boolean run = true;
    public void m() {
        System.out.println("m start");
        while (run) {

        }
        System.out.println("m end");
    }
    public static void main(String[] args) throws InterruptedException {
        T11_volatile t11 = new T11_volatile();
        new Thread(t11::m, "t1").start();
        TimeUnit.SECONDS.sleep(1);
        t11.run = false;
    }
}

测试结果:当变量run不加volatile修饰时,main中改变run变量为false,线程t1得到的run为true,while循环不结束;当变量run加volatile修饰时,main中改变run变量为false,线程t1得到的run为false,while循环结束

原因:不加volatile时,main在自己工作区改变run变量,立即返回非堆;而t1未立即从堆中获取最新的run变量,仍使用自己工作区的run变量值。

禁止指令重排序:

在编译器编译之后的指令会分成三步 1.给指令申请内存 2.给成员变量初始化 3.是把这块内存的内容赋值给变量。而在极端高并发的情况下有可能照成1、3、2的指令顺序,所以需要volatile禁止指令重排序。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值