多线程
概念
- 程序:一段静态的代码
- 进程:进程作为资源分配的单位,系统在运行时为每个进程划分内存区域
- 线程:线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器
- 并行:多个cpu同时执行多个任务
- 并发:一个cpu同时执行多个任务,比如:秒杀
线程的创建方式
继承Thread类的子类,重写run方法(和1的匿名子类的方式一样)
class MyThread extends Thread{ public void run(){ System.out.println("Thread创建方法0"); } } //调用 MyThread t1 = new MyThread(); t1.start();//必须是start
注意:每个线程对象只能启动一次,当需要多次调用该逻辑,可以创建多个线程对象,进行调用
New线程,重写Thread的run方法
public void test1(){ Thread thread1 = new Thread(){ @Override public void run() { System.out.println("Thread创建方法1"); } }; thread1.start(); }
New线程,实现Runnable接口
class RThread implements Runnable(){ public void run(){ System.out.println("Thread创建方法2"); } } //调用 RThread mt1 = new RThread(); Thread t1 = new Thread(mt1); t1.start();
public void test2(){ Thread thread2 = new Thread(new Runnable() { @Override public void run() { System.out.println("Thread创建方法2"); } }); thread2.start(); }
此方法创建多个线程时只需要将构造器中的参数更换即可,只需要创建一个runnable实现类对象
1,2种创建线程方式的比较:
- 开发中,优先选择实现Runnable接口的方式
- 原因:
- 实现的方式没有类的单继承性局限性
- 实现的方式更适合来处理多个线程有共享数据的情况(因为只创建一个runnable给相同构造器)
- 相同点:两个方式都需要重写run(),将线程要执行的逻辑声明在run()中
New Callable接口,实现call方法,封装成任务,作为参数加入线程
callable可以返回结果值,需要在定义的时候指定返回值类型
public void test3() throws ExecutionException, InterruptedException { Callable<Integer> callable = new Callable<Integer>() { @Override public Integer call() throws Exception { System.out.println("Thread创建方法3"); return 10; } }; //封装成任务 FutureTask<Integer> task = new FutureTask<Integer>(callable); new Thread(task).start(); task.isDone(); System.out.println(task.get()); }
3.1 实现Callable接口
//1.创建Callable的实现类
class CallTest implements Callable{
@Override
//重写call方法
public Object call() throws Exception {
System.out.println("Thread创建方法3");
return 10;
}
}
class test{
//创建实现类实例
CallTest callTest = new CallTest();
//将此实例作为参数传入FutureTask构造器中
FutureTask task = new FutureTask(callTest);
//将FutureTask作为参数传入线程中,为了执行线程
new Thread(task).start();
//非必选步骤:获取返回值
System.out.println(task.get());
}
如何理解Callable接口的方式创建多线程比Runnable接口的方式强大?
- call()有返回值
- call()可以抛出异常,被外边的操作捕获,获取异常信息
- Callables是支持泛型的
使用线程池创建线程
//提供指定线程数量的线程池 ExecutorService service = Executors.newFixedThreadPool(10); service.execute(Runnable command);//方式1提交:用于runnable FutureTask task = service.submit(Callable<T> task);//方式2提交:用于callable //非必选步骤:获取返回值 System.out.println(task.get()); service.shutdown();//关闭线程池
线程的六种状态
NEW:初始态
RUNNABLE:运行态
a) Ready:准备态(排队中,准备执行)
b) Running:执行态
BLOCKED:阻塞态(执行锁方法,锁被其他线程拿走,自己成为阻塞态)
WAITING:等待态(执行锁方法,执行了其中的wait()方法,释放锁,当条件满足之后,得到锁就会立刻执行)
TIMED_WAITING:超时等待态(wait(传参2000),进入等待态2秒后,不管条件是否满足,都执行)
TERMINEATED:结束态
线程同步
同步的方式解决了线程安全的问题。
操作同步代码块时,只要有一个线程参会,其他线程等待,相当于单线程过程,效率低
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
操作共享数据的代码,即为需要被同步的代码
共享数据:多个线程共同操作的变量。
同步监视器,俗称:锁。任何一个对象,都可以当作锁
要求:多个线程必须要公用同一把锁(static)
- 在实现Runnable接口创建多线程的方式,我们可以考虑用this充当同步监视器
- 在继承Thread类创建多线程的方式,慎用this充当同步监视器(因为不是一个对象),可以用类.class
方式二:同步方法
如果操作共享数据的代码都声明在一个方法中,不妨将此方法声明同步的。
public void run(){
while(true){
show();
}
}
public synchronized void show(){
//需要同步的代码
}
总结:
- 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明
- 非静态的同步方法,同步方法是:this(不适用继承Thread类的多线程)
- 静态的同步方法,同步监视器是:当前类本身(适用于所有情况)
Lock锁
Private ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.unlock();
注意,如果是继承Thread的方式,需要将Lock加上static
synchronized与Lock的异同
- 相同:二者都可以解决线程安全问题
- 不同:
- synchronized机制在执行完相应的同步代码之后,自动的释放同步监视器
- Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
优先使用顺序:
Lock–>同步代码块(已经进入了方法体,分类了相应资源)–>同步方法(在方法体之外)
如何解决线程安全问题?有几种
3种,Lock锁方式,同步代码快,同步方法
死锁
不同线程分别占用了对方需要的同步资源不放弃,都在等待对方放弃自己需要的资源
如何解决死锁
- A调B,B调A的情况注意同步问题
- 减少同步资源的定义
- 避免嵌套同步
线程的通信
- wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
- notify():一旦执行此方法,就会唤醒一个线程,如果有多个线程被wait,就唤醒优先级高
- notifyAll():一旦执行此方法,就会唤醒所有wait线程
注意:
- 这三个方法必须使用在同步代码块或同步方法中
- 这三个方法的调用者,必须是同步监视器,否则会出现
IllegalMonitorStateException
(this)- 这三个方法都是定义在object中
Sleep和Wait的区别
a) 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态
a) 不同点:
- 声明的位置不同:Thread类中的声明sleep(),Object类中声明wait()
- 调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须在同步代码快,同步方法中调用
- 关于是否释放同步监视器:如果两个方法都使用在同步代码快或同步方法中,sleep不会释放,wait会
- 唤醒时机不同:sleep()会在读秒之后自己唤醒,wait()不会自动唤醒,需要手动调用notify/notifyall来进行唤醒
Sleep
暂停当前线程执行,但是不释放锁(在厕所里睡着了,但厕所其他人使用不了)
它是Thread中的方法
可以在任何场景中使用
只有睡够时间之后才能醒来
Wait
暂停当前线程执行,但是释放锁
它是Object对象的方法,可以直接使用
只能在同步块,同步方法中使用
可以随时被唤醒,(notify()也可以在其他线程使用来唤醒该线程)
生产者消费者问题(线程通信应用)
class Clerk{
private int i =0;
/*
此处用到同步,在生产时不能进行消费,消费时不能生产
解决线程安全问题
需要注意:
1.wait(),notify()方法必须放在同步代码块/同步方法中
2.唤醒的时机
*/
public synchronized void produce() {
if(i<20){
i++;
System.out.println(Thread.currentThread().getName()+":开始生产第"+i+"个产品");
notify();//在生产完提醒消费
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consume() {
if(i>0){
System.out.println(Thread.currentThread().getName()+":开始消费第"+i+"个产品");
i--;//先消费再减少
notify();//在消费完产品提醒生产
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produce();
}
}
}
class Consumer extends Thread{
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consume();
}
}
}
public class test {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
producer.setName("1号生产者");
consumer.setName("1号消费者");
producer.start();
consumer.start();
}
}
线程池
线程资源必须通过线程池提供,不允许在应用中自行显示创建线程
好处:
- 减少创建/销毁线程上的系统资源开销。
- 提高响应速度(减少创建线程的时间)
- 便于线程管理
创建ExecutorService线程池:
//提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
设置线程池属性:
service.setCorePoolSize(20);//核心池的大小
maximumPoolSize//最大线程熟
keepAliveTime//线程没有任务时最多保持多长时间会终止
ExecutorService:线程池接口
- void execute(Runnable command):执行任务,没有返回值,一般用于执行runnable接
submit(Callable task):执行任务,有返回值(FutureTask),一般用于执行Callable - void shutdown():关闭连接池
Executors:线程池工具类,用于创建不同类型的线程池
线程的常用方法
start() | 启动当前线程,调用当前线程的run() |
---|---|
run() | 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中 |
currentThread() | 返回执行当前代码的线程 |
getName() | 获取当前线程的名字 |
setName() | 设置当前线程的名字 |
yield() | 释放当前cpu的执行权 |
join() | 在线程a中调用线程b的join(),线程a就会进入阻塞状态 |
sleep(long millitime) | 睡眠,让当前线程“睡眠”指定毫秒值,让其在规定时间内是阻塞状态 |
isAlive() | 判断当前线程是否存活 |
线程优先级
优先级:
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5
涉及的方法
- getPriority():返回当前线程优先值,
Thread.currentThread().getPriority()
- setPriority(int newPriority):改变线程的优先级