由于单核CPU性能瓶颈IO等待场景,并发编程成为充分利用多核资源、提升CPU利用率的刚需

进程 (Process)与线程(Thread)

进程是操作系统资源分配基本单位,有独立的内存空间、文件句柄等系统资源创建和销毁的代价较

线程是操作系统调度执行基本单位(CPU线程为单位进行调度),因此被称为“轻量级进程”。

进程与线程的关系

一个进程至少包含一个线程(称为线程);同一进程内的线程共享进程的内存空间等资源;不同进程之间内存共享,各自拥有独立运行环境。

崩溃影响:一个程崩溃通常影响其他进程,但一个线程崩溃可能导致整个程崩溃。

创建开销:创建进程时,系统分配内存等进程级资源的同时也会创建第一个线程;在已有进程再创建新线程时,无需重复申请进程资源需分配私有栈空间量资源,开销远小于进程。

应用价值:线程以极额外开销提供并发能力,使服务器可以同时处理大量客户端请求;正好适配 7×24 小时不间断运行、随时面对未知数量客户端的高并发服务场景

为进一步提升效率,又引入了线程池和协程

多线程

每个线程都是独立的执行流,能够独立执行自身的指令序列,并且多个线程可以同时执行不同的代码;多线程技术一个任务分解多个子任务,由不同线程分别处理,其中负责启动其他线程的线程称为主线程

多进程需为每个进程独立申请资源,开销较多线程共享同一进程资源,更加轻量。

增加线程CPU核心数充足提升效率,但线程数超过核心数后,调度开销剧增,反使性能下降

多个线程操作共享资源(如内存中同一个变量)可能产生冲突,引发 bug;一个线程抛出未捕获(catch)异常,整个进程(包括所有线程)都会止;而多进程中一个进程崩溃不会影响其他进程。

多线程编程须合理控制线程数量,并妥善处理同步与异常

线程休眠

Thread.sleep() 静态方法,通过类名.方法名调用;让当前正在执行的线程暂停指定的毫秒数;线程休眠时不会释放它所持有的监视器

:会抛出 InterruptedException,属于受查异常(Checked Exception),必须显式处理:使用 throws 声明抛出 使用 try-catch 捕获

1.创建线程

Java 标准库中的Thread是对操作系统线程 API(如 Linux 的 pthread)的封装JVM 重点封装多线程能力,从而屏蔽了底层系统的差异。

1.1 继承Thread类

定义一个继承 Thread 的内部类重写 run() 方法,run() 是线程的入口方法,用来描述线程要执行的任务逻辑;

创建内部类实例,利用向上转型将其赋值给 Thread 类型的引用(多态),最后调用 start() 方法启动线程

public class Demo {
	static class MyThread extends thread {
		@Override
		public void run(){
			System.out.println("Hello Thread");
		}
	}
	
	public static void main(String[] args) {
		// 创建线程对象
		Thread thread = new MyThread();

		// 调用start方法启动线程 
		thread.start();
		
		System.out.println("Hello main");
	}
		
}

通过while(true) 循环持续打印,可以直观对比两种输出差异;为避循环打印过,可以添加 sleep 方法让程序短暂休眠,但需要显示处理异常

main方法中的sleep 可以通过 throws 声明抛出可以用 try-catch 捕获

子类重写 run() 方法中,因父类 Thread 的 run() 没有声明任何异常,子类须遵循异常声明一致规则,所以不能使用 throws只能用try-catch捕获,否则会编译错误。

public class Demo {
	static class MyThread extends Thread {
		@Override
		public void run(){
			while(true){
				System.out.println("Hello thread");
				
				try {
					Thread.sleep(1000);
				}catch (InterruptedException e) {
					e.printStackTrace();
				}
					
	
			}
		}
	}
	
	public static void main(String[] args) throws InterruptedException{
		// 创建线程对象
		Thread thread = new MyThread();

		// 调用start方法启动线程   并发执行
		thread.start();
		
		// 仅调用run()方法体  单线程顺序执行
		//thread.run();
		
		while(true){
			System.out.println("Hello main");
			Thread.sleep(1000);
		}
	}
}


每个至少有一个线程(称为线程),Java 程序中的 main 方法就运行在至少存在的线程中。

调用线程对象 start() 方法会通过操作系统 API ,底层创建真正的线程,以 run() 为入口主线程并发执行,两者输出可能交替出现;

直接调用 run() 则等同于当前线程(如线程)中顺序执行普通方法不会创建新线程;若 run() 内包含死循环主线程将被永久阻塞,后续代码 hello main 无法执行。

可通过 Ctrl+C 终止程序;线程调度操作系统决定,顺序无序不可预测;即便并发执行中个线程调用Thread.sleep(1000) ,但休眠结束哪个线程执行仍不确定,因此输出结果并非严格交替。



1.2 实现Runnable接口

总结

1.3 匿名内部类


1.4 lambda表达式(常用

2.Thread类及常见方法

2.1 构造方法

2.2 常见属性



2.3 启动一个线程 start()

调用start方法,才真的在操作系统的底层创建出一个线程。

2.4 中断一个线程

package thread;

public class Demo7 {

    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {   
            while(flag){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            }
        });

        t.start();
        System.out.println("hello main");
        Thread.sleep(3000);

        flag = false;
        System.out.println("让 t 线程终止");

    }

}

package thread;

public class Demo7 {

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {   
            while(Thread.currentThread().isInterrupted()){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            }
        });

        t.start();
        System.out.println("hello main");
        Thread.sleep(3000);

        t.interrupt();
        System.out.println("t 线程的中断标志位:" + t.isInterrupted());


    }

}




2.5 等待一个线程 join()

有时,我们需要等待一个线程完成它的工作后,才能进的下⼀步工作,这时我们需要⼀个方法明确等待线程的结束。

主线程等待代码

package thread;

public class Demo7 {
    public static void main(String[] args) throws InterruptedException {

        Thread t = new Thread(() -> {
            for(int i = 0; i < 5; i++){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    //e.printStackTrace();
                    break;
                }
            }
        });

        System.out.println("主线程开始");

        t.start();
        System.out.println("主线程等待之前");
        t.join();
        System.out.println("主线程等待之后");
        
        System.out.println("主线程结束");


    }
}

t 线程等待代码

package thread;

public class Demo7 {
    public static void main(String[] args) throws InterruptedException {

        Thread mainThread = Thread.currentThread();

        Thread t = new Thread(() -> {
                try {
                    System.out.println("t 线程等待之前");
                    mainThread.join();
                    System.out.println("t 线程等待之后");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            
        });

        t.start();

        for(int i = 0; i < 3; i++){
            System.out.println("hello main");
            Thread.sleep(1000);
        }


    }
}


2.6 获取当前线程引用

2.7 休眠当前线程

3.线程的状态

线程的状态是⼀个枚举类型 Thread.State



4.多线程带来的的风险-线程安全(重点!)

4.1 概念 例子



4.2 线程不安全的原因






 5.死锁(重点)




    Object lock1 = new Object();
    Object lock2 = new Object();
    Thread t1 = new Thread() {
        @Override
        public void run() {
            synchronized (lock1) {
                synchronized (lock2) {
                    // do something...
                }
            }
        }
    };
    t1.start();
    Thread t2 = new Thread() {
        @Override
        public void run() {
            synchronized (lock2) {
                synchronized (lock1) {
                    // do something...
                }
            }
        }
    };
    t2.start();

不会产生环路等待的代码:约定好先获取lock1,再获取lock2,就不会环路等待。

    Object lock1 = new Object();
    Object lock2 = new Object();
    Thread t1 = new Thread() {
        @Override
        public void run() {

            synchronized (lock1) {
                synchronized (lock2) {
                    // do something...
                }
            }
        }
    };
    t1.start();
    Thread t2 = new Thread() {
        @Override
        public void run() {
            synchronized (lock1) {
                synchronized (lock2) {
                    // do something...
                }
            }
        }
    };
    t2.start();

6.synchronized关键字 - 监视器锁(monitor lock)




6.1 synchronized的特性



6.2 synchronized使用示例

重点是第一种,也是最常用的。

6.3 Java标准库中的线程安全类

7.wait和notify

7.1 wait()方法




7.2 notify() notifyAll() 方法

7.3 wait和sleep的对比(面试题)

8.多线程案例

8.1 单例模式(面试常考)

单例模式是校招中最常考的设计模式之一






8.2 阻塞队列








8.3 线程池

(1)概念

线程池最大的好处就是减少每次启动、销毁线程的损耗。







        

(2)模拟实现

8.4 定时器



自行实现一个定时器






虽然执行结果看起来对了,但其实还是有几个问题的。




9.总结

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐