Java多线程
简介
进程是程序运行的实例
当一个程序执行进入内存运行时,即变成一个进程。进程的资源是彼此隔离的,其它程序不允许访问。
线程是进程内要执行的"任务"
- 线程是进程内的一个”基本任务“,每个线程都有自己的功能是CPU分配与调度的基本单位。
- 一个进程内可以包含多个线程,反之一个线程只能隶属于某一个进程。
- 进程内至少拥有一个”线程“,这个线程叫”主线程“,主线程消亡则进程结束。
创建多线程的三种方式与区别
继承 Thread 类
public class ThreadSample1 {
class Runner extends Thread {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() + "正在打印:" + i);
}
}
}
public void start() {
Runner runner = new Runner();
runner.setName("打印机");
runner.start();
}
public static void main(String[] args) {
ThreadSample1 sample1 = new ThreadSample1();
sample1.start();
}
}
run 方法和 start 方法的区别
原因:
- 当调用
start
方法时,线程对象A和B中的run
方法执行权移交给了操作系统,由操作系统的线程调度策略老决定先执行哪个方法。 - 当调用
run
方法时,此时相当于一般Java方法的调用,方法的执行顺序由调用的顺序决定,先调用的就会先执行,后被调用就后执行。
总结:
- 当调用
start
方法时,使用的是线程的形式。 - 当调用
run
方法时,使用的是一般方法的形式。
调用 start 方法
此时会发现,线程 RunnerA、RunnerB 多数情况下都是交替执行的,这是符合多线程下运行的规律。
调用 run 方法
此时会发现,线程 RunnerA、RunnerB 按照方法调用顺序运行的,这是不符合多线程下运行的规律。
实现 Runnable 接口创建线程
public class Demo2 {
class Cat implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
// Thread.currentThread().getName() 获取当前线程名
System.out.println(Thread.currentThread().getName() + "A cat");
}
}
}
class Dog implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println(Thread.currentThread().getName() + "A dog");
}
}
}
public void start() {
Cat cat = new Cat();
Dog dog = new Dog();
Thread catThread = new Thread(cat);
Thread dogThread = new Thread(dog);
catThread.start();
dogThread.start();
for (int i = 1; i <= 3; i++) {
System.out.println("main thread");
}
}
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
demo2.start();
}
}
实现 Callable 接口创建线程
public class ThreadSample3 {
class Runner implements Callable<Integer> {
public String name;
@Override
public Integer call() throws Exception {
Integer result = 0;
int seed = new Random().nextInt(10);
for (int i = 1; i <= 10; i++) {
Thread.sleep(1000);
result = i * seed;
System.out.println("第" + i + "秒," + this.name + "跑了" + result + "米,(" + seed + "米/s)");
}
return result;
}
}
public void start() throws ExecutionException, InterruptedException {
// 创建一个规定数量线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
Runner runnerA = new Runner();
runnerA.name = "参赛者A";
Runner runnerB = new Runner();
runnerB.name = "参赛者B";
Runner runnerC = new Runner();
runnerC.name = "参赛者C";
Future<Integer> r1 = executorService.submit(runnerA);
Future<Integer> r2 = executorService.submit(runnerB);
Future<Integer> r3 = executorService.submit(runnerC);
executorService.shutdown();
System.out.println(runnerA.name + "跑了" + r1.get() + "米");
System.out.println(runnerB.name + "跑了" + r2.get() + "米");
System.out.println(runnerC.name + "跑了" + r3.get() + "米");
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
new ThreadSample3().start();
}
}
线程生命周期
线程从生到死的状态
Thread 类定义的线程6种状态:
public enum State {
NEW, // 新建状态
RUNNABLE, // 可运行状态
BLOCKED, // 阻塞状态
WAITING, // 等待状态
TIMED_WAITING, // 超时等待状态
TERMINATED // 结束状态
}
通行的5种线程状态
不单单针对Java语言,也可用于其他语言
线程同步
synchronized
(同步锁) 关键字的作用就是利用一个特定的对象设置一个锁lock(绣球),在多线程(游客) 并发访问的时候,
同时只允许一个线程(游客) 可以获得这个锁,执行特定的代码(迎娶新娘)。执行后释放锁,继续由其他线程争抢。
代码中的同步机制:
public class SyncSample1 {
class Printer{
//锁对象
Object lock = new Object();
//synchronized代码块演示,对自定义对象lock上锁
public void print1(){
synchronized (lock) {
try {
Thread.sleep(500);
System.out.print("魑");
Thread.sleep(500);
System.out.print("魅");
Thread.sleep(500);
System.out.print("魍");
Thread.sleep(500);
System.out.print("魉");
System.out.println();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//synchronized方法 - 对this当前对象上锁
public synchronized void print2(){
try {
//this
Thread.sleep(500);
System.out.print("魑");
Thread.sleep(500);
System.out.print("魅");
Thread.sleep(500);
System.out.print("魍");
Thread.sleep(500);
System.out.print("魉");
System.out.println();
} catch (Exception e) {
e.printStackTrace();
}
}
//synchronized静态方法 - 该类的字节码对象Printer.class
/*
public static synchronized void print3(){
try {
//Printer.class
Thread.sleep(500);
System.out.print("魑");
Thread.sleep(500);
System.out.print("魅");
Thread.sleep(500);
System.out.print("魍");
Thread.sleep(500);
System.out.print("魉");
System.out.println();
} catch (Exception e) {
e.printStackTrace();
}
}
*/
}
class PrinterTask implements Runnable {
private Printer printer;
@Override
public void run() {
printer.print1();
}
}
public void start() {
Printer printer = new Printer();
for (int i = 0; i < 10; i++) {
PrinterTask task = new PrinterTask();
task.printer = printer;
Thread thread = new Thread(task);
thread.start();
}
}
public static void main(String[] args) {
SyncSample1 syncSample1 = new SyncSample1();
syncSample1.start();
}
}
synchronized 的锁对象
- synchronized代码块 - 通过实例化一个对象持有该对象的线程拥有执行权力其他线程进入等待状态
- synchronized方法 - 不需要指定对象,默认使用当前对象的this关键字,当作锁对象进行线程同步控制
- synchronized静态方法 - 锁对象:这个类在 Java 中的字节码对象
线程安全的产生及解决
在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况
死锁的产生与解决
死锁是在多线程情况下最严重的问题,在多线程对公共资源(文件、数据)等进行操作时,彼此不是放自己的资源,而去试图操作其他线程的资源,而形成的交叉引用,就会产生死锁
解决死锁最根本的建议是:
- 尽量减少公共资源的引用
- 用完马上释放公共资源
- 增加超时失败的机制
public class DeadLock {
private static String fileA = "A文件";
private static String fileB = "B文件";
class Runner1 implements Runnable{
@Override
public void run() {
while(true) {
synchronized (fileA) {//打开文件A,线程独占
System.out.println(Thread.currentThread().getName() + ":文件A写入");
synchronized (fileB) {
System.out.println(Thread.currentThread().getName() + ":文件B写入");
}
System.out.println(Thread.currentThread().getName() + ":所有文件保存");
}
}
}
}
class Runner2 implements Runnable{
@Override
public void run() {
while(true) {
synchronized (fileB) {//打开文件B,线程独占
System.out.println(Thread.currentThread().getName() + ":文件B写入");
synchronized (fileA) {
System.out.println(Thread.currentThread().getName() + ":文件A写入");
}
System.out.println(Thread.currentThread().getName() + ":所有文件保存");
}
}
}
}
public void start(){
new Thread(new Runner1()).start();
new Thread(new Runner2()).start();
}
public static void main(String[] args) {
new DeadLock().start();
}
}
线程池
并发是伴随着多核处理器的诞生而产生的,为了充分利用硬件资源,诞生了多线程技术。
但是多线程又存在资源竞争的问题,引发了同步和互斥的问题,JDK1.5 推出了java.util.concurrent
(并发工具包) 来解决这戏问题
ThreadPool 线程池
- 重用存在的线程,减少线程对象创建、消亡的开销
- 线程总是可控,提高资源的利用率
- 提供额外的功能,定时执行、定期执行、监控等
JUC 支持的线程池种类
在 java.util.concurrent
中,提供了工具类 Executors
(调度器) 对象来创建线程池,可创建的线程池有四种:
FixedThreadPool
- 定长线程池CachedThreadPool
- 可缓存线程池SingleThreadExecutor
- 但线程池ScheduledThreadPool
- 调度线程池
线程池的4种类型
public class ThreadPoolSample1 {
public static void main(String[] args) {
/**
* 创建一个可创建一个定长线程池
* 定长线程池的特点是固定线程总数,空闲线程用于执行任务,如果县城都在使用,后续任务则处于等待状态
*/
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i <= 1000; i++) {
final int index = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "=>" + index);
}
});
/*
需要返回值,使用 submit 方法执行 Callable 对象,利用 Future 对象接收返回值
Future<Object> ret = threadPool.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
}
});*/
}
threadPool.shutdown();
}
}
public class ThreadPoolSample2 {
public static void main(String[] args) {
/**
* ExecutorService 用于管理线程池
* newCachedThreadPool() 创建一个可缓存线程池
*/
ExecutorService threadPool = Executors.newCachedThreadPool();
// 可缓存线程池的特点是,无限大,如果线程池中没有可用的线程则创建,有空闲线程则利用起来
for (int i = 0; i <= 1000; i++) {
final int index = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "=>" + index);
}
});
}
threadPool.shutdown();
}
}
public class ThreadPoolSample3 {
public static void main(String[] args) {
/**
* ExecutorService 用于管理线程池
* newSingleThreadExecutor() 单线程线程池
* 一般用于处理辅助性任务(资源释放、垃圾回收等)
*/
ExecutorService threadPool = Executors.newSingleThreadExecutor();
for (int i = 0; i <= 1000; i++) {
final int index = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "=>" + index);
}
});
}
threadPool.shutdown();
}
}
public class ThreadPoolSample4 {
public static void main(String[] args) {
/**
* newScheduledThreadPool() 可调度线程池
*/
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(new Date() + "延迟1秒执行,每3秒执行一次");
}
},1,300, TimeUnit.SECONDS);
}
}
线程池中的参数
固定线程池:
固定线程池有两种构造方法,其中课程中使用的是传入一个参数的构造方法:
public static ExecutorService newFixedThreadPool(int nThreads) {}
int nThreads: 线程池中的对象个数
该参数决定了创建线程池中有多少个线程对象
调度线程池
调度线程池有两种构造方法,其中使用的是传入一个参数的构造方法:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {}
int corePoolSize: 核心线程数-线程池中要保留的线程数,即使他们处于空闲状态。
该参数决定了创建的线程池中,最少维持的线程对象的数量。