多线程基础知识复习

本文详细介绍了Java中的多线程基础知识,包括线程的创建(通过继承Thread类、实现Runnable接口、实现Callable接口及使用线程池)、线程的状态转换,以及线程同步和通信的方法(如synchronized关键字、Lock接口)。此外,还讨论了线程安全问题,如死锁,并给出了实际开发中使用线程池的优势和示例。
摘要由CSDN通过智能技术生成

多线程基础知识复习

程序、进程、线程的作用

  • 程序:一组指定集合(一段静态代码)- 静
  • 进程:程序的一次执行过程(把程序加载到内存让它跑起来,即运行起来的程序)- 动
  • 线程:进程可以进一步细化为线程,是程序内部的一条执行路径
    • 线程作为调度和执行的单位,每个线程拥有独立的运行栈程序计数器

补充无关知识点:单核与多核

  • 单核:一个时间只能做一件事,假的多线程
  • 多核:一个时间可以做多件事情,真正的多线程

一个java应用程序至少有3个线程:main、gc、异常处理

并行与并发
并行:多个CPU同一时间做不同的事情
并发:一个CPU(采用时间片)“同时”执行多个任务(快速切换)

多线程的创建(4种方式,面试时要能说出)

方式一:继承与Thread类

  • 创建一个Thread类子类
  • 重写Thread类的run()
  • 创建Thread类的子类对象
  • 通过类对象调用start()
    • 启动当前线程
    • 调用当前线程的run()

方式二:实现Runnable接口

  • 创建一个类实现Runnable接口
  • 实现Runnable中的抽象方法run()
  • 创建实现类对象
  • 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  • 通过Thread类的对象调用start()

方式三:实现Callable接口(JDK5.0+)

// demo
public class NumThread implements Callable {
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i ++ ) {
            if (i % 2 == 0) {
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

public class MyTest {
    public static void main(String[] args) {
        NumThread numThread = new NumThread();
        FutureTask futureTask = new FutureTask(numThread);
        new Thread(futureTask).start();
        try {
            System.out.println("final: " + futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

方式四:使用线程池(实际开发中使用的方法)

好处:

1.提高响应速度(减少了新线程的创建时间)
2.降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
3.便于管理(有对应操作可以更改线程池的属性)

// demo
class N1 implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i ++ ) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class N2 implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i ++ ) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

public class ThreadPool {
    public static void main(String[] args) {
        // 1.提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        // 2.执行指定的线程操作。需要提供Runnable和Callable接口的实现类对象
        service.execute(new N1());
        service.execute(new N2());
        // service.submit(); // 适用于Callable接口实现类的调用
        // 3.关闭连接池
        service.shutdown();
    }
}

线程的生命周期

5种状态

  • 新建
  • 就绪
  • 运行
  • 阻塞
  • 死亡

之间的关系
(-- 此处应该有张图 --)

线程的同步(线程的安全问题)

实现线程同步的方式:

  • 方式一:同步代码块
synchronized(同步监视器) {

	/**
	 * 说明:
	 * 1)操作共享数据的代码
	 * 2)共享数据(多个线程共同操作的变量)
	 * 3)同步监视器,俗称:锁
	 * 4) 任何类的对象都可以充当锁
	 * 5)多个线程共用一把锁才能保证其线程同步
	 *    a) 实现Runnable接口情况下,可以使用this(慎用)
	 *    b) 集成Thread情况下,可以用当前类.class
	 * /
}

同一时间只允许一个进程进入同步代码块,相当于是一个单线程的过程,效率低(局限性)

  • 方式二:同步方法
private synchronized void fun() {
	操作共享数据
}

1)将fun()放入run()中即可

2)非static同步方法的锁:默认就是this
static同步方法的锁:当前类

实操例子: 使用同步的方式将单例的懒汉式改成线程安全

class Bank {
	private Bank() {}

	private static Bank instance = null;

	public static Bank getInstance() {
		if (instance == null) {
			instance = new Bank();
		}
		return instance;
	}
}

改写 DCL 双检索机制

class Bank {
	private Bank() {}

	private static Bank instance = null;

	public static Bank getInstance() {
		if (instance == null) {
			synchronized (Bank.class) {
				if (instance == null) {
					instance = new Ban();
				}
			}
		}
		return instance;

死锁问题

public class Main {


    public static void main(String[] args) {

        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();

        new Thread() {
            @Override
            public void run() {
                synchronized (s1) {

                    s1.append("a");
                    s2.append("1");

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized (s2) {
                        s1.append("b");
                        s2.append("2");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2) {

                    s1.append("c");
                    s2.append("3");

                    synchronized (s1) {
                        s1.append("d");
                        s2.append("4");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

  • 方式三:Lock
public class Window implements Runnable {

    static int ticket = 100;

    // 1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock(true);

    @Override
    public void run() {
        while (ticket > 0) {

            try {
                // 2.调用锁定方法
                // lock后被锁定的部分为单线程
                lock.lock();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName() + ": " + ticket);
                ticket -- ;
            } finally {
                // 3.调用解锁方法
                lock.unlock();
            }


        }

    }
}

面试题:Lock与synchronized的异同?

同:都可以解决线程安全问题
异:
synchronized机制在执行完相应的同步代码块后,自动的释放同步监视器
Lock需要手动启动同步(lock()),同时结束同步也需要手动实现(unlock()

线程通信

线程通信实例

例题:打印1-100,使用两个线程交替打印

线程通信中用到的方法:

  • wait() 使得调用wait()方法的线程进入阻塞状态<睡觉时要释放锁,这一点和Thread.sleep不同>
  • notify() 执行后,唤醒被wait()的一个方法,若有很多被wait()的方法,则唤醒优先级最高的一个
  • notifyAll() 唤醒所有被wait()的方法

· 以上三个方法都必须使用在同步代码块/同步方法中

✨三个方法的调用者必须是同步代码块/同步方法的同步监视器

public class Number implements Runnable {

    private static int number = 1;

    @Override
    public void run() {
        while (true) {

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (this) {

                this.notify();

                System.out.println(Thread.currentThread().getName() + ": " + number);
                number ++ ;

                if (number >= 100) break;

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

        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水能zai舟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值