1.线程基础

1. 线程基础

1.1 进程、线程

进程是程序运行资源分配的最小单位,资源包括:CPU、内存、IO。
线程是 CPU 调度的最小单位,必须依赖于进程存在,与同进程内的其他线程共享该进程的全部资源。

1.2 CPU 核心数、线程数

本来,CPU 核心数 = 线程数,Intel 使用超线程技术,使线程数翻倍,即下图的逻辑处理器。
在这里插入图片描述

1.3 CPU 时间片轮转

把 CPU 时间切片,分配给正在运行的线程,分配时发生上下文切换,时间大约为 20000 个 CPU 周期。

1.4 并行、并发

在这里插入图片描述

1.5 高并发好处、注意事项

好处:

  1. 充分利用 CPU 资源
  2. 加快用户响应时间
  3. 便于代码模块化、异步化、简单化

注意事项:

  1. 线程安全
  2. 线程死锁
  3. 线程太多会造成服务器资源耗宕机。Linux 限制一个进程总线程数 1000 个,Windows 限制一个进程总线程数 2000 个。

2. Java 线程基础

2.1 创建线程

有 2 种方式:Thread、Runnable。
Thread 是 Java 中对线程的抽象,Runnable 是对任务的抽象。

public class UserThread extends Thread {
    @Override
    public void run() {
        System.out.println("new Thread");
    }

    public static void main(String[] args) {
        new UserThread().start();
    }
}

class UserRunnable implements Runnable {
    public void run() {
        System.out.println("new Thread");
    }

    public static void main(String[] args) {
        UserRunnable userRunnable = new UserRunnable();
        new Thread(userRunnable).start();
    }
}

2.2 中止

stop 终结线程时不保证线程的资源正常释放,不建议使用。

interrupt:发起中断信号,即给线程置标志位。
isInterrupted:判断线程是否被中断。
Thread.interrupted:判断线程是否被中断,并清空中断标志位。

此处说明 JDK 中线程是协作式,而非抢占式。

public class EndThread {
    private static class UseThread extends Thread {

        public UseThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " interrupt flag =" + isInterrupted());
            while (!isInterrupted()) {
                System.out.println(threadName + " is running");
            }
            System.out.println(threadName + " interrupt flag =" + isInterrupted());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread endThread = new UseThread("endThread");
        endThread.start();
        Thread.sleep(20);
        endThread.interrupt();//中断线程,其实设置线程的标识位true
    }

}

中断判断 while 循环中如果是 sleep,需要自己重新置中断标志位。

public class HasInterrputException {

    private static class UseThread extends Thread {

        public UseThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            while (!isInterrupted()) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName() + "interrupt flag is " + isInterrupted());
                    // 此时需要自己置中断标志位,否则不会跳出 while
                    interrupt();
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " I am extends Thread.");
            }
            System.out.println(Thread.currentThread().getName() + " interrupt flag is " + isInterrupted());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread endThread = new UseThread("HasInterruptEx");
        endThread.start();
        Thread.sleep(500);
        endThread.interrupt();
    }

}

不建议使用 cancle 变量的形式。

public class EndThread {
    private static class UseThread extends Thread {

        public UseThread(String name) {
            super(name);
        }

        private boolean cancle;

        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            while (isCancle()) {
                System.out.println(threadName + " is running");
                // wake(); 如果在此处 wake 了,没人唤醒,就不会判断 cancel,进而中断
            }
            System.out.println(threadName + " interrupt flag =" + isInterrupted());
        }

        public boolean isCancle() {
            return cancle;
        }

        public void setCancle(boolean cancle) {
            this.cancle = cancle;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        UseThread endThread = new UseThread("endThread");
        endThread.start();
        Thread.sleep(20);
        endThread.setCancle(true);
    }

}

2.3 线程同步

1. synchronized

synchronized 有对象锁、类锁。

2. volatile

最轻量的同步机制:

  1. 保证可见性
  2. 不保证原子性

3. ThreadLocal

为每个线程提供一个变量副本,使数据实现了线程的隔离。
Spring 在实现事务时使用了 ThreadLocal。
get、set、remove

public class UseThreadLocal {
    private static final ThreadLocal<Integer> intLocal
            = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

    /**
     * 运行 3 个线程
     */
    public void StartThreadArray() {
        Thread[] runs = new Thread[3];
        for (int i = 0; i < runs.length; i++) {
            runs[i] = new Thread(new TestThread(i));
        }
        for (int i = 0; i < runs.length; i++) {
            runs[i].start();
        }
    }

    /**
     * 线程任务
     */
    public static class TestThread implements Runnable {
        int id;

        public TestThread(int id) {
            this.id = id;
        }

        public void run() {
            System.out.println(Thread.currentThread().getName() + ":start");
            Integer s = intLocal.get();
            s = s + id;
            intLocal.set(s);
            System.out.println(Thread.currentThread().getName() + ":" + intLocal.get());
            //intLocal.remove();
        }
    }

    public static void main(String[] args) {
        UseThreadLocal test = new UseThreadLocal();
        test.StartThreadArray();
    }
}
1. ThreadLocal 原理

ThreadLocal 中定义了静态内部类 ThreadLocalMap,Thread 中有个成员变量,变量类型就是这个 ThreadLocalMap。
ThreadLocalMap 中定义了 Entry 数组,每个 Entry 中保存 key(ThreadLocal)、value。
因为可定义多个 ThreadLocal 变量,也就是线程有多个 ThreadLocal 变量,所以存在 Entry 数组,通过 ThreadLocal 的值拿到 value。

2. ThreadLocal 引发内存泄露

强>软>弱>虚
软引用:如果垃圾回收完,发现内存还是不够用,就回收软引用。
弱引用:发生垃圾回收,就回收弱引用。

ThreadLocal 是弱引用,只要发生垃圾回收就会回收变量。
get、set 会有一定机会清除 ThreadLocal 为 null 的 Value。

public class ThreadLocalOOM {
    private static final int TASK_LOOP_SIZE = 500;

    final static ThreadPoolExecutor poolExecutor
            = new ThreadPoolExecutor(5, 5,
            1,
            TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());

    static class LocalVariable {
        private byte[] a = new byte[1024 * 1024 * 5];/*5M大小的数组*/
    }

    final static ThreadLocal<LocalVariable> localVariable
            = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        /** 5*5 */
        for (int i = 0; i < TASK_LOOP_SIZE; ++i) {
            poolExecutor.execute(new Runnable() {
                public void run() {
                    //localVariable.set(new LocalVariable());
                    new LocalVariable();
                    System.out.println("use local varaible");
                    //localVariable.remove();
                }
            });

            Thread.sleep(100);
        }
        System.out.println("pool execute over");
    }

}
3. ThreadLocal 线程不安全

ThreadLocal 线程不安全是因为没使用好。
例如,给 ThreadLocal 设置一个静态的变脸值,也就是共享的,就会线程不安全,如下面的 static Number,并不是每个线程的该值是 1,因为都是共用了一个 number。

public class ThreadLocalUnsafe implements Runnable {

    public Number number = new Number(0);

    public void run() {
        //每个线程计数加一
        number.setNum(number.getNum() + 1);
        //将其存储到ThreadLocal中
        value.set(number);
        SleepTools.ms(2);
        //输出num值
        System.out.println(Thread.currentThread().getName() + "=" + value.get().getNum());
    }

    public static ThreadLocal<Number> value = new ThreadLocal<Number>() {
    };

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new ThreadLocalUnsafe()).start();
        }
    }

    private static class Number {
        public Number(int num) {
            this.num = num;
        }

        private int num;

        public int getNum() {
            return num;
        }

        public void setNum(int num) {
            this.num = num;
        }

        @Override
        public String toString() {
            return "Number [num=" + num + "]";
        }
    }

}

2.4 线程协作

wait、notify、notifyAll
等待、通知标准范式:wait 会释放锁,并且被唤醒后,wait 会重新去竞争锁。

synchronized (对象){
        while (条件不满足){
            对象.wait();
        }
        // 业务逻辑
    }

    synchronized (对象){
        // doSth()
        // 改变条件,使 wait 条件满足
        对象.notify()/notifyAll();
    }

1. 用等待超时模式实现一个连接池

public class DBPool {
    private static LinkedList<Connection> pool = new LinkedList<Connection>();

    /*初始化,限制连接池大小*/
    public DBPool(int initialSize) {
        if (initialSize > 0) {
            for (int i = 0; i < initialSize; i++) {
                pool.addLast(SqlConnectImpl.fetchConnection());
            }
        }
    }

    /*释放连接,通知其他的等待连接的线程*/
    public void releaseConnection(Connection connection) {
        if (connection != null) {
            synchronized (pool) {
                pool.addLast(connection);
                //通知其他等待连接的线程
                pool.notifyAll();
            }
        }
    }

    public Connection fetchConnection(long mills) throws InterruptedException {
        synchronized (pool) {
            //永不超时
            if (mills <= 0) {
                while (pool.isEmpty()) {
                    pool.wait();
                }
                return pool.removeFirst();
            } else {
                /*超时时刻*/
                long future = System.currentTimeMillis() + mills;
                /*等待时长*/
                long remaining = mills;
                while (pool.isEmpty() && remaining > 0) {
                    pool.wait(remaining);
                    /*唤醒一次,重新计算等待时长*/
                    remaining = future - System.currentTimeMillis();
                }
                Connection connection = null;
                if (!pool.isEmpty()) {
                    connection = pool.removeFirst();
                }
                return connection;
            }
        }

    }
}

2.5 其他

线程状态转换:
在这里插入图片描述

start、run:启动线程调用 start,在 start 中调用 start0,连续调用两次 start 会抛出异常。
在这里插入图片描述
yield:让线程让出 CPU 使用权,CPU 重新选择线程执行;但是不会让出锁。
join: A 调用 B 的 join,A 暂时挂起。
守护线程:setDaemon,非守护线程结束,守护线程自动结束;与子线程概念不同,子线程不是守护线程。
释放锁与否:

  1. 释放:wait
  2. 不释放:yield、sleep、notify
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值