谈多线程之前,先来了解一些概念:
单核和多核:
学过操作系统我们都知道,操作系统有单处理机系统和多处理机系统之分。单处理机也就只有一核处理器CPU,多处理机也就有多核处理器了,所以它的计算处理性能肯定是强于单核系统的。
并行与并发:
并行就是同一时间处理多条命令,就是一个时间点可以处理多个任务;并发就是同一时间段处理多条命令,虽然微观上分时交替执行,但之间的间隙人们感觉不到,宏观上还是多个命令同时执行。可见,多核系统可以做到程序的并行执行,而单核不可以。
进程与线程:
进程就是一个内存中运行的应用程序,它是操作系统资源分配的基本单位。我们一个应用程序可以有多个进程在同时运行。线程是进程的一个实例,是CPU进行资源调度的基本单位,一个进程可以有多个线程在同时运行。一个程序至少有一个进程,一个进程至少有一个线程。
1,多线程实现方式
java中实现多线程的两种方式:
- 方法1,继承Thread类,重写run方法;
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("mythread1");
MyThread myThread2 = new MyThread("mythread2");
myThread1.start();
myThread2.start();
for(int i = 0;i < 2;i++){
System.out.println("main线程输出"+i);
}
}
}
/**
* 自定义多线程类,重写run方法
*/
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for(int i = 0;i < 2;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
//某一次输出结果
main线程输出0
mythread2 0
mythread2 1
mythread1 0
mythread1 1
main线程输出1
- 方法2,实现runnable接口,重写run方法,并通过构建Thread类,将runnable作为目标对象构建多线程;
MyRunnable myRunnable1 = new MyRunnable();
Thread thread1 = new Thread(myRunnable1,"myRunnable1");
Thread thread2 = new Thread(myRunnable1,"myRunnable2");
thread1.start();
thread2.start();
for(int i = 0;i < 2;i++){
System.out.println("main线程输出"+i);
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0;i < 2;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
//某一次输出结果
main线程输出0
myRunnable1 0
main线程输出1
myRunnable2 0
myRunnable1 1
myRunnable2 1
法2匿名内部类方式构建多线程:
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类方式");
}
};
new Thread(runnable).start();
2,线程安全问题的解决方式
多线程自然就会造成线程安全问题,下面介绍三种线程安全解决机制:
- synchronized(){}同步代码块;
- synchronized同步方法;
- 锁机制。
//传入任意对象object
synchronized (object) {
需要同步执行的代码
}
public synchronized void method(){
需要同步执行的代码
}
Lock lock = new ReentrantLock();
lock.lock();
需要同步执行的代码
lock.unlock();
3,线程的几种状态
在API中,通过java.lang.Thread.State
枚举出了六种状态,他们分别是:
线程间的状态切换:
tips:一个时间只能有一个线程获得对象锁,如果某个线程进入等待wait状态,那么它将释放锁;休眠sleep状态则不会释放锁。
wait()、notify()、notifyAll()方法是Object对象所有,所以他们必须是在synchronized内调用;而sleep()方法通过Thread类调用即可。
import java.util.Date;
public class ThreadTest2 {
public static void main(String[] args) {
Object object = new Object();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println(new Date() + "-" + Thread.currentThread().getName() + "得到执行并获取到当前锁,调用wait方法,进入waitting状态");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date() + "-" + Thread.currentThread().getName() + "被唤醒");
}
}
}, "线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println(new Date() + "-" + Thread.currentThread().getName() + "休眠5s,此时不会释放锁");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date() + "-" + Thread.currentThread().getName() + "苏醒后设置等待2s,此时会释放锁");
try {
object.wait(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date() + "-" + Thread.currentThread().getName() + "等待完毕立即获得锁,执行唤醒waitting中的线程1");
object.notify();
}
}
}, "线程2").start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object) {
System.out.println(new Date() + "-" + Thread.currentThread().getName() + "获取锁对象执行了");
}
}
}
}, "线程3").start();
}
}
//某次执行结果:
Wed Jan 27 16:56:39 CST 2021-线程1得到执行并获取到当前锁,调用wait方法,进入waitting状态
Wed Jan 27 16:56:39 CST 2021-线程2休眠5s,此时不会释放锁
Wed Jan 27 16:56:44 CST 2021-线程2苏醒后设置等待2s,此时会释放锁
Wed Jan 27 16:56:44 CST 2021-线程3获取锁对象执行了
Wed Jan 27 16:56:45 CST 2021-线程3获取锁对象执行了
Wed Jan 27 16:56:46 CST 2021-线程2等待完毕立即获得锁,执行唤醒waitting中的线程1
Wed Jan 27 16:56:46 CST 2021-线程1被唤醒
Wed Jan 27 16:56:46 CST 2021-线程3获取锁对象执行了
Wed Jan 27 16:56:47 CST 2021-线程3获取锁对象执行了
可以看到,开始线程1抢到资源获得执行进入无线等待状态,然后线程2获得执行进入休眠5s,此时并不会释放对象锁,线程3无法执行,所以系统5s内处于空闲状态(这里3个线程都加了对象锁,如果某个没有则无此限制)。5s后线程2苏醒,执行2s等待,此时会释放锁,那么系统里的线程3得到机会便立即执行,直到线程2再次苏醒并唤起线程1。线程1执行完后,就是并发执行了,但此时系统只有线程3会一直循环执行下去。
4,线程池
线程的创建非常的简单,但线程同样需要管理。我们根据事务的并发量来确定线程的创建多少。但大量的创建线程务必会影响资源的开销,如何做好线程的回收?如何有效的对线程进行管理?为此,我们有线程池的解决机制。线程池就是一个容纳多个线程的容器,使用就向池里面获取,使用完毕后进行回收。一次创建,多次(终身)使用,这样就无需反复创建线程了。
线程池的好处:
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
线程池的创建:
public static void main(String[] args) {
//创建线程池对象
ExecutorService executorService = Executors.newFixedThreadPool(2);
//创建Runnable实例对象
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"获得了线程池资源");
}
};
//从线程池中获取线程对象,调用Runnable中的run()方法
executorService.submit(runnable);
executorService.submit(runnable);
executorService.submit(runnable);
//关闭线程池
//executorService.shutdown();
}