多线程

多个线程同时或交替运行, 单核CPU为顺序执行(交替执行), 多核情况下, 每个CPU有自己的运算器, 所以在多个CPU中可以同时运行.

创建线程的方式

1.继承Thread

public class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
        System.out.println(Thread.currentThread().getName() + "执行完毕");
    }
}
public class ThreadTest {

    public static void main(String[] args) {

        MyThread myThread = new MyThread();
        myThread.setName("测试");
        myThread.start();
        System.out.println(Thread.currentThread().getName() + "执行完毕");
    }
}

开始线程, 可以看出main线程和测试线程是两个独立的线程
调用myThread.run();方法相当于直接在主线程运行run方法, 而不是开启一个新的线程去执行

2.实现Runnable接口

public class MyRunable implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "执行完毕");
    }
}
public class ThreadTest {

    public static void main(String[] args) {

        MyRunable runable = new MyRunable();

        Thread thread = new Thread(runable);
        thread.start();

        System.out.println(Thread.currentThread().getName() + "执行完毕");
    }
}

3.使用线程池
3.1 可以在spring中配置相关线程池, 使用时从容器取出即可, 也可以自己声明线程池

<bean id="threadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <!-- 核心线程数,默认为1 -->
    <property name="corePoolSize" value="5"/>
    <!-- 最大线程数,默认为Integer.MAX_VALUE -->
    <property name="maxPoolSize" value="20"/>
    <!-- 队列最大长度,一般需要设置值>=notifyScheduledMainExecutor.maxNum;默认为Integer.MAX_VALUE
    <property name="queueCapacity" value="1000" /> -->

    <!-- 线程池维护线程所允许的空闲时间,默认为60s -->
    <property name="keepAliveSeconds" value="300"/>

    <!-- 队列最大长度 -->
    <property name="queueCapacity" value="2000"/>

    <!-- 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者 -->
    <property name="rejectedExecutionHandler">
        <!-- AbortPolicy:直接抛出java.utils.concurrent.RejectedExecutionException异常 -->

        <!-- CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,可以有效降低向线程池内添加任务的速度 -->

        <!-- DiscardOldestPolicy:抛弃旧的任务、暂不支持;会导致被丢弃的任务无法再次被执行 -->

        <!-- DiscardPolicy:抛弃当前任务、暂不支持;会导致被丢弃的任务无法再次被执行 -->

        <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy"/>
    </property>

</bean>

3.2 Executors 创建线程池

public class ThreadTest {

    public static void main(String[] args) {

        ExecutorService threadPool = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            threadPool.execute(new MyRunable());
        }

    }
}

当手动创建线程池时, 如果IDEA安装阿里 P3C 插件后会报错提示以下内容, 建议

线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明: Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。

建议使用如下方式:

public class ThreadTest {

    public static void main(String[] args) {
        // 定时任务 建议为线程起名
        ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(3,
                new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").build());
            executorService.scheduleAtFixedRate(new MyRunable(), 0, 1, TimeUnit.SECONDS);

    }
}
public class ThreadTest {

    public static void main(String[] args) {

        // 线程工厂
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
                .setNameFormat("demo-pool-%d").build();

        //Common Thread Pool
        ExecutorService pool = new ThreadPoolExecutor(5, 20, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

        pool.execute(()-> System.out.println(Thread.currentThread().getName()));
        pool.shutdown();//gracefully shutdown

    }
}

线程优先级

1.myThread.setPriority(1);设置优先级
2.优先级从低到高为 1-10, Thread类提供 Thread.MIN_PRIORITY=1, Thread.NORM_PRIORITY=5, Thread.MAX_PRIORITY=10
3.默认优先级为 5 即 NORM_PRIORITY
4.优先级高的仅代表获取进入运行机会的几率大, 并不代表一定会比优先级低的先执行

sleep()和wait()

1.sleep()线程未释放锁, 时间结束后线程继续执行
2.wait线程释放锁, 需要使用notify或notifyAll
3.wait常用于线程之间的交互

package com.liuzhihang.tool.alternate;

/**
 * 交替打印奇偶数
 *
 * @author liuzhihang
 * @date 2018/9/4 18:39
 */
public class AlternateNum {

    public static void main(String[] args) {

        Num num = new Num();

        Thread thread1 = new Thread(new Odd(num));
        Thread thread2 = new Thread(new Even(num));

        thread1.start();
        thread2.start();

    }
}

class Num {
    int anInt = 1;
    boolean flag = true;

}

class Odd implements Runnable {

    private Num num;

    public Odd(Num num) {
        this.num = num;
    }

    @Override
    public void run() {
        while (num.anInt < 1000) {
            // 使用同一把锁
            synchronized (num) {
                if (num.flag) {
                    System.out.println("奇数 -> " + num.anInt);
                    num.anInt++;
                    num.flag = false;
                    num.notify();

                } else {
                    try {
                        num.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }

    }
}


class Even implements Runnable {

    private Num num;

    public Even(Num num) {
        this.num = num;
    }

    @Override
    public void run() {
        while (num.anInt < 1000) {
            // 使用同一把锁
            synchronized (num) {
                if (!num.flag) {
                    System.out.println("偶数 -> " + num.anInt);
                    num.anInt++;
                    num.flag = true;
                    num.notify();

                } else {
                    try {
                        num.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }

    }
}