多线程基础知识复习
程序、进程、线程的作用
- 程序:一组指定集合(一段静态代码)- 静
- 进程:程序的一次执行过程(把程序加载到内存让它跑起来,即运行起来的程序)- 动
- 线程:进程可以进一步细化为线程,是程序内部的一条执行路径
- 线程作为调度和执行的单位,每个线程拥有独立的
运行栈
和程序计数器
- 线程作为调度和执行的单位,每个线程拥有独立的
补充无关知识点:单核与多核
- 单核:一个时间只能做一件事,假的多线程
- 多核:一个时间可以做多件事情,真正的多线程
一个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();
}
}
}
}
}