多线程

1.程序、进程、线程区分:

程序:为了完成特定任务,用某种语言编写的一组指令的集合,之一段静态的代码
进程:正在运行的一个程序。(他是作为资源分配的单位,系统会为不同的进程分配不同的内存空间)
线程:一个程序内部的一条执行路径。

2.jvm内存结构

image.png
进程可以细化为多个线程:
每个线程,拥有自己独立的:栈、程序计数器
多个线程,共享同一个进程中的结构:方法区、堆

3.一个java程序最少有3个线程:mian()主线程、gc()垃圾回收线程、异常处理线程。

4.并行与并发(理解就好)

5.创建多线程的方式(4种)

方式一:继承Thread类

1>创建一个继承Thread的子类
2>重写run方法
3>创建Thread类的子类对象
4>通过此对象调用start()方法:①启动当前线程 ②调用run()方法【因此不能通过run启动线程,只能通过start()方法】

方式二:通过实现Runnable接口的类

1>创建一个Runnable接口类。
2>实现Runnable中的抽象方法run()
3>创建此实现类的对象
4>将此对象作为Thread类的参数传递到Thread类的构造器中,创建Thread类的对象
5>通过Thread类的对象调用start()方法
此方法中是怎样调用到实现类中的run方法的呢?
针对此问题,可以阅读一下Thread中相应的源码
Thread类源码中run()的源码是这样的:

 public void run() {
        if (target != null) {
            target.run();
        }
    }

可以看到,运行run方法时先判断target,若不为空执行target中的run()
target在源码中是Runnable类型,其对应我们创建的实现类

private Runnable target;

同时Thread中还有以下构造方法

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

如此这般,运行了实现类中的run()也就说得通了

上述两种方式对比
开发中:优先选择实现Runnable接口类的方法
原因:1.实现的方式没有单继承的局限性
2.能更好的处理多个线程数据共享的情况。
联系:Thread类也实现了Runnable接口
相同点:都要重写Run(),将线程要执行的逻辑声明在run()中
启动线程都需要调用Thread的start()方法。

方式三:实现callable接口——>JDK5.0新增

实现代码:


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class NumberThread 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(Thread.currentThread().getName() + ":" + i);
                sum += i;
            }
        }
        return sum;
    }
}

public class CallableTest {
    public static void main(String[] args) {
        NumberThread number = new NumberThread();
	//若要创建多个线程,也需要声明多个FutureTask
        FutureTask futureTask = new FutureTask(number);
        FutureTask futureTask2 = new FutureTask(number);
        FutureTask futureTask3 = new FutureTask(number);
        Thread t1 = new Thread(futureTask,"线程一");
        Thread t2 = new Thread(futureTask2,"线程二");
        Thread t3 = new Thread(futureTask3,"线程三");

        t2.start();
        t3.start();
        t1.start();
        Object sum = null;
        try {
            sum = futureTask.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("总和为" + sum);


    }


}

若要创建多个线程,也需要声明多个FutureTask,作为参数传入不同线程中。futureTask相当于一个特殊的runnable,作为参数传入Thread。
Future接口,可以对具体的Runnable、Callable任务的执行结果进行取消、查询是否完成、返回结果等
FutureTask是Future的唯一实现类,他同时实现了Runnable和Future接口,既可以作为Runnable被线程调用,又可以作为Future返回Callable的返回值

面试题:如何理解Callable比Runnable强大

1.call()可以有返回值
2.call()可以抛出异常
3.Callable支持泛型

方式四:使用线程池

先上代码:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class OddNum implements Runnable {


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

        }
    }
}

class EvenNum implements Runnable {


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

        }
    }
}

public class ThreadPool {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(15);
        service.execute(new OddNum());//接受runnable,犯过的错误:接收的参数应该为实例,不是类
        service.execute(new EvenNum());
        // service.submit();接收callable
        service.shutdown();

    }
}
使用线程池的好处:

1.提高响应速度(减少了创建线程的时间)
2.降低了资源消耗(重复利用线程池资源不用每次都创建)
3.便于线程管理(通过设置如下属性)
corePoolSize:核心池大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多维持多长时间后会终止

线程管理的方式:
ExecutorService service = Executors.newFixedThreadPool(15);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
        service1.setCorePoolSize(10);
        service1.setMaximumPoolSize(5);

多态的应用,管理设置线程池的方法要对前面的service进行强转,变成service1才能用子类特有的方法

6.Thread中常用的方法:

start():启动当前线程,调用当前线程中的run()
run():通常需要重写,将线程所需执行的操作声明再次方法内
currentThread():静态方法,返回当前执行代码的线程
getName()
setName()
yield():释放当前cpu的执行权
join():在a中调用b.join(),a陷入阻塞状态,待b完全执行完毕,a结束阻塞状态
stop():该方法已过时。用于终止线程
sleep(long millitime):在指定的时间(单位毫秒)内,线程程阻塞状态
isAlive():判断线程是否存活

线程的优先级:
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
getPriority():获取线程优先级
setPriority():设置优先级
优先级只表示概率不意味着高优先级一定比低优先级先执行

**线程的通信:**wait(),notify(),notifyAll():这三个方法动议在Object类中

7.线程的分类

守护线程和用户线程(前者服务后者),垃圾回收就是以恶搞守护线程,若JVM中都是守护线程,JVM退出

8.线程的生命周期

image.png
线程最终状态都是死亡,阻塞只是一个临时状态,不可做为最终状态。

9.线程的同步机制(三个方式)

方式一:同步代码块

操作共享数据的代码
同步监视器:俗称锁,任何类都可以充当
在实现Runnable接口创建的多线程中,可以考虑使用this充当锁
在继承创建的多线程中,慎用this,可以考虑使用当前类

synchronized(同步监视器){
  	需要被同步的代码
}
方式二:同步方法

不需要显式声明同步监视器
对于非静态同步方法:同步监视器式this
对于静态的同步方法:锁为当前本身

方式三:lock锁 ——> JDK5.0新增

private Reentrantlock lock= new Reentrantlock();
lock.lock();
.
.
.
lock.unlock();

面试题:synchronized和lock的异同

同:都可解决线程安全问题
异:synchronized在执行完同步代码后,自动释放锁;lock需要手动lock(),unlock()

操作同步代码时,相当于单线程

10.线程安全的单例模式(懒汉式):

懒汉式单例模型:

public class ThreadSafeSingletonModle {
    private ThreadSafeSingletonModle() {
    }

    private static ThreadSafeSingletonModle instance = null;

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

改进为线程安全的,两种方式的分析都在代码注释中

public class ThreadSafeSingletonModle {
    private ThreadSafeSingletonModle() {
    }

    private static ThreadSafeSingletonModle instance = null;

    public static ThreadSafeSingletonModle getInstance() {
        //第一种方式效率低,原因第一个线程创建完实例后,后面线程其实都无需等待
//        synchronized (ThreadSafeSingletonModle.class) {
//            if (instance == null) {
//                instance = new ThreadSafeSingletonModle();
//            }
//        }
//        return instance;
//    }
        //第二种方式就是在第一种方式前面在包一层if判断,这样除了前面极少数进入的线程外,后续其余的线程均无需等待,直接取用实例,提高效率
        synchronized (ThreadSafeSingletonModle.class) {
            if (instance == null) {
                synchronized (ThreadSafeSingletonModle.class) {
                    if (instance == null) {
                        instance = new ThreadSafeSingletonModle();
                    }
                }
            }
            return instance;
        }
    }
}

11.死锁

不同资源分别占用对方同步资源不放,都在等待对方放弃自己需要的同步资源

不会释放锁的操作:sleep(),yield(),suspend()挂起,尽量避免用suspend()和resume()来控制线程

12.线程通信

涉及到的三个方法:wait(),notify(),notifyAll()

这三个方法都定义在Object类中,都必须使用在同步方法或同步代码块当中
三个方法调用者都必须是同步代码块或同步方法中的同步监视器,否则,会出现IllegalMonitorStateException异常

面试题:sleep()和wait()的异同

同:都能是线程变成阻塞状态
异:1.两个方法声明的位置不同,sleep()声明在Thread中,wait()声明在Object类中
2.sleep可以在任何需要的场景中调用,wait需要使用在同步代码块或同步方法中
3.如果两个方法都使用在同步代码块和同步方法中,sleep()不会释放锁,wait()会释放锁。

Q.E.D.