大家好!我是老猿,一个热爱技术的程序猿,Java行业入行7年,每天都在学习和分享的路上!
线程
1.简介
大家都知道多线程给我们带来了更好的资源利用和更好的程序响应,所以关于它的简介我就不多阐明了,大家可以自行搜索,我主要讲的就是重点
- 线程就是独立的执行路径;
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程、gc线程;
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
- 线程会带来额外的开销,如cpu调度时间,并发控制开销;
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致;
2.线程创建
线程的创建有三种方式:
- 继承 Thread 类,重写 run() 方法
- 实现 Runnable 接口,重写 run() 方法
- 实现 Callable 接口(可以设置返回值),重写 call() 方法
//继承Tread类,重写run()方法
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("这是一个继承Thread类的线程。");
}
public static void main(String [] args) {
//线程启动需要使用start()方法,而不是run()
new MyThread().start();
}
}
//实现Runnable接口,重写run()方法
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("这是一个实现Runnable接口的线程。");
}
public static void main(String [] args) {
//Runnable接口创建线程需要通过Thread类
new Thread(new MyRunnable()).start();
}
}
//实现Callable接口,重写call()方法
public class MyCallable implements Callable<E> {
@Override
public E call() {
System.out.println("这是一个实现Callable接口的线程。");
return new E();
}
public static void main(String [] args) throws ExecutionException, InterruptedException {
//第一种方法创建Callable线程
//创建线程池
ExecutorService ser = Executors.new FixedThreadPool(3);
//向线程池提交Callable<E>实现类,并返回Future<E>类
Future<E> future = ser.submit(new MyCallable());
//通过get()方法获取call()方法返回值
E e = future.get();
//关闭线程池
ser.shutdownNow();
//第二种方法创建Callable线程
FutureTask<E> futureTask = new FutureTask<E>(new MyCallable());
new Thread(futureTask).start();
//若不先通过start()运行,会导致程序无法停止。
System.out.println(futureTask.get());
}
}
推荐使用实现Runnable接口,因为Java的单继承局限性,而接口可以多继承,方便同一个对象被多个线程使用。
3.线程

- 创建状态 为new 一个线程。
- 就绪状态 为线程调用 start() 方法,此时线程立即进入就绪状态,但 不意味着立刻被cpu调度运行 。
- 运行状态 为线程被cpu调度运行中,此时获得cpu资源。
- 阻塞状态 为线程被停止了,如线程调用 sleep() 、 wait() 、同步锁定时,线程进入阻塞状态,即代码不能继续向下执行,在阻塞状态解除后,会重新进入就绪状态,等待cpu调度执行。
- 死亡状态 为线程被中止、结束,一旦进入死亡状态,就不能再次启动。
3.1线程方法与说明

- 除此之外,还有JDK提供的 stop() 、 destroy() 方法。【已废弃】
- 推荐让线程自己停止下来。
- 建议使用一个标志位进行终止变量,当flag 为 false时,则终止线程运行。
3.2线程休眠sleep()
- sleep(时间)指定当前线程阻塞的毫秒数;
- slee()方法为Thread类静态方法;
- sleep存在受检异常InterruptedException;
- sleep时间达到后线程进入就绪状态;
- sleep可以模拟网络延时,倒计时等等;
- 每一对象都有一个锁,但 sleep不会释放锁 ;
- 在哪个线程调用sleep方法就会停止哪个线程;如main线程调用thread.sleep(),此时暂停的是main线程而不是thread线程;
3.3线程让步yield()
- 线程让步,让当前正在执行的线程暂停,但不阻塞;
- yield()方法为Thread类静态方法, 该方法不会释放锁 ;
- 将线程从运行状态转为就绪状态;
- 让cpu重新调度,所以 让步不一定成功 !只是让线程重新争夺cpu资源;
yield()与sleep()区别
- sleep方法给其他线程运行机会时不考虑线程的优先级,因此会给低线程优先级运行的机会,而yield方法只会给相同优先级或者更高优先级线程运行的机会
- 线程执行sleep()方法后转入阻塞状态,所以,执行sleep()方法的线程在指定的时间内不会被执行,而yield()方法只是使当前线程重新回到可执行状态,所以执行yield()方法的线程可能在进入可执行状态后马上又被执行
- sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常
- sleep()方法比yield()方法(跟操作系统相关)有更好的可移植性
3.4线程加入join()
- 合并线程,待调用join方法的线程执行完成后,再执行其他线程,其他线程阻塞;
- 可以想象成插队;
- join存在受检异常InterruptedException;
3.5线程状态State
- NEW :尚未启动(即尚未调用start())的线程处于此状态;
- RUNNABLE :正在运行的线程处于此状态(在Java虚拟机中执行的线程);
- BLOCKED :被阻塞等待监视器锁定的线程处于此状态;
- WAITING :正在等待另一个线程执行特定动作的线程处于此状态;
- TIMED_WAITING :正在等待另一个线程执行动作达到指定等待时间的线程处于此状态;
- TERMINATED :已退出的线程处于此状态;
3.6线程优先级PRIORITY
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行(实际调度还是由cpu调度,优先级只是建议);
线程优先级用数字表示,范围从1~10
- Thread.MIN_PRIORITY = 1;
- Thread.MAX_PRIORITY = 10;
- Thread.NORM_PRIORITY = 5;
使用 getPriority()或setPriority(int newPriority) 改变或获取优先级
3.7守护线程daemon
- 线程分为 用户线程 和 守护线程 ;
- 虚拟机必须确保用户线程执行完毕;
- 虚拟机不用等待守护线程执行完毕,如:gc;
- 使用场景如:后台记录操作日志、监控内存、垃圾回收等
通过 setDaemon(boolean on) 设置是否为守护线程(默认为false,不是守护线程);
4.线程同步
4.1同步介绍
由于同一进程的多个线程共享同一块存储空间,在多线程带来方便的同时,也带来了访问冲突问题,为了保证数据在每个线程中都能保证准确性,在访问时加入锁机制 synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待占用线程释放锁后才可争夺资源。
但锁也会带来一些问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题;
4.2synchronized关键字
synchronized可以修饰方法或代码块,当修饰普通方法时,锁的是当前对象,修饰静态方法时,锁的是整个类,当修饰代码块时, synchronized(this){} 锁的是当前对象, synchronized(this.getClass()) 锁的是整个类。
4.3线程死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情况,某一个同步块同时拥有”两个以上对象的锁”时,就可能发生”死锁”问题。
产生死锁的四个必要条件:
- 互斥条件 :一个资源每次只能被一个进程使用;
- 请求与保持条件 :一个进程因请求资源而阻塞时,对已获得的资源保持不放;
- 不剥夺条件 :进程已获得的资源,在未使用完之前,不能强行剥夺;
- 循环等待条件 :若干进程之间形成一种头尾相接的循环等待资源关系;
//死锁例子
public class _05ThreadDeadLock {
static Object knife = new Object();
static Object fruit = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (knife){
System.out.println("获得小刀,想要水果");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (fruit){
System.out.println("获得水果");
}
}
/*
//解决方法内部锁外置:
synchronized (fruit){
System.out.println("获得水果");
}
*/ }).start();
new Thread(() -> {
synchronized (fruit){
System.out.println("获得水果,想要小刀");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (knife){
System.out.println("获得小刀");
}
}
/*
//解决方法内部锁外置:
synchronized (knife){
System.out.println("获得小刀");
}
*/ }).start();
}
}
/*
运行结果为:
获得小刀,想要水果
获得水果,想要小刀
程序无法停止……
*/
4.4Lock接口(锁)
- 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
- ReentrantLock类实现了Lock,它拥有与synchronized关键字相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock(可重用锁),可以显示加锁、释放锁。
public class _05ThreadLock {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(new Ticket()).start();
}
}
}
class Ticket implements Runnable {
private static int ticket = 10;
private static final Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
try {
if (ticket < 1)break;
System.out.println("第"+ticket--+"张");
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}finally {
//使用Lock锁时建议使用trycatch语句的finally保证锁的释放
lock.unlock();
}
}
}
}
Lock与synchronized的对比:
- Lock是显式锁(需要 手动开启 和 手动关闭锁 !),synchronized是隐式锁,出了作用域自动释放。
- Lock只有代码块锁,synchronized有代码块锁和方法锁。
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性。(JUC提供了不同的实现类)
- 优先使用顺序: Lock > 同步代码块 > 同步方法 。
5.线程协作
经典线程协作:生产者消费者模式。
生产者消费者模式指生产者生产一份产品,消费者消费一份产品,当已经有产品时,需要通知生产者停止生产,等待消费者消费,当没有产品时,需要通知生产者继续生产,消费者则等待产品生产。
面对这种线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。这时,仅有synchronized是不够的,因为synchronized无法实现不同线程间的消息传递(通信)。
5.1线程通信
Java提供了几个方法解决线程之间的通信问题:

注意 :均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException。
Lock接口想要完成线程间通信问题,需要通过Condition接口完成,通过lock.newCondition()获得Condition,调用其await()、signalAll()方法。(与wait()、notifyAll()相似)
5.2生产者消费者模式代码实现
//生产者消费者模式代码实现
public class ThreadProCus {
public static void main(String[] args) {
Goods Goods = new Goods();
new Thread(() ->{
for (int i = 0; i < 10; i++) {
lockGoods.add();
}
},"A").start();
new Thread(() ->{
for (int i = 0; i < 10; i++) {
lockGoods.sub();
}
},"B").start();
}
}
class Goods {
private int num;
//Lock方式实现
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void add(){
lock.lock();
try {
while (num == 10){
//产品已满,停止生产
condition.await();
}
num++;
System.out.println("生产了第"+num+"件商品");
//生产了产品,通知消费者消费
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void sub(){
lock.lock();
try {
while (num == 0){
condition.await();
}
System.out.println("消费了第"+num+"件商品");
num--;
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
/*
//synchronized方式实现
public synchronized void add() throws InterruptedException {
while (num == 10){
//生产者停止
this.wait();
}
num++;
System.out.println("生产一个,当前num="+num);
//通知消费者
this.notifyAll();
}
public synchronized void sub() throws InterruptedException {
while (num == 0){
//消费者停止
this.wait();
}
num--;
System.out.println("消费一个,当前num="+num);
//通知生产者
this.notifyAll();
}
*/}
6.线程池
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
这时,为了解决这个问题,可以通过提前创建多个线程,放入线程池中,使用时直接获取,使用后放回池中,来避免频繁创建、销毁,实现重复利用。
线程池的优点 :
- 提供响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程管理
线程池可以设置的属性有:corePoolSize(核心池的大小,即常驻线程数)、maximumPoolSize(最大容纳线程数)、keepAliveTime(线程没有任务时最多存活时间)等
从JDK5.0开始,提供了线程池相关API:ExecutorService和Executors。
ExecutorService :真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command):执行任务/命令,没有返回值,一般用于执行Runnable
- Future submit(Callable task) :执行任务,有返回值,一般用于执行Callable
- void shutdown():关闭连接池
Executors :工具类、线程池的工厂类,用于创建并返回不同类型的线程池。
//通过线程池创建线程
public class _09ThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
executorService.execute(new MyThread());
}
executorService.shutdown();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
等等,还有……
学习建议
很多人担心学了容易忘,这里教你一个方法,那就是 重复学习。
最后
非常感谢各位小哥哥小姐姐们能看到这里,原创不易,如果你觉得本篇实操方法对你有用麻烦帮助 点个关注、点个赞、留言 都是支持(莫要白嫖)! 有补充有疑问欢迎评论区留言!!!!

我会每周定期分享6-7篇 操作系统、计算机网络、Java、分布式、数据库等干货精品原创文章 ,我们下篇文章见!