多线程基础,学习后记录
什么是线程?线程与进程区别?
进程:是程序的基本执行实体。
线程:是进程的一个实体,是cpu调度和分派的基本单位,是比进程更小的可以独立运行的基本单位。
电脑系统每一个正在运行的软件都可以看作是一个进程,而每个软件里面包含了很多功能,而这些互相独立且可以同时运行的功能就形成了多线程。
并行与并发
并发:在同一时刻,有多个指令在单个CPU上交替执行。
并行:在同一时刻,有多个指令在多个CPU上同时执行。
多线程三种实现方式
第一种:继承Thread类
继承Thread类重写run方法创建线程,可以很方便通过创建对象调用方法,实现简单但不可以继承其他类;
1.创建MyThread类,继承Thread类重写run方法
public class MyThread extends Thread{
@Override
public void run(){
System.out.println("这是继承Thread类");
}
}
2.创建对象,调用start方法启动线程
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
第二种:实现Runnable接口
实现Runnable接口并重写run方法,可以避免单继承局限性,编程更加灵活,实现解耦。
1.创建MyThread类,实现Runnable接口并重写run方法
public class MyThread implements Runnable{
@Override
public void run(){
System.out.println("这是实现Runnable接口");
}
}
2.启动线程
public static void main(String[] args) {
MyThread myThread = new MyThread();
//创建线程对象
Thread thread = new Thread(myThread);
//启动线程
thread.start();
}
第二种:实现Callable接口
实现Callable接口并重写run方法,可以获得线程执行结果的返回值,并可以抛出异常。
1.创建MyThread类,实现Runnable接口并重写call方法
public class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("这是实现Callable接口");
return "这里是返回值";
}
}
2.启动线程
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
//创建FutureTask对象,管理多线程运行结果
FutureTask<String> stringFutureTask = new FutureTask<>(myThread);
//创建线程对象
Thread thread = new Thread(stringFutureTask);
//启动线程
thread.start();
//获取线程返回值
String result = stringFutureTask.get();
System.out.println(result);
}
多线程常用方法
线程优先级
线程优先级范围是(1-10),默认是5。理论上优先级越高,线程抢到执行权的概率越高,但是注意:因为线程抢到执行权是概率问题,所以即使线程1比线程2优先级更高,也可能是线程2抢到执行权.
public class MyTest extends Thread {
@Override
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println(String.format("%s线程:执行到了%d",Thread.currentThread().getName(),i+1));
}
}
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
//创建线程对象,并指定线程名称
Thread thread1 = new Thread(myTest,"线程1");
Thread thread2 = new Thread(myTest,"线程2");
//打印线程优先级(此时是默认的,也就是5)
System.out.println(thread2.getPriority());
System.out.println(thread1.getPriority());
//设置线程优先级
thread1.setPriority(10);
thread2.setPriority(1);
//启动线程
thread1.start();
thread2.start();
}
守护线程
守护线程与非守护线程区别:当所有非守护线程执行完,守护线程如果没执行完,会直接结束。
public class MyTest1 extends Thread {
@Override
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println(String.format("%s线程:执行到了%d",Thread.currentThread().getName(),i+1));
}
}
}
public class MyTest2 extends Thread {
@Override
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println(String.format("%s线程:执行到了%d",Thread.currentThread().getName(),i+1));
}
}
}
public static void main(String[] args) {
MyTest1 myTest1 = new MyTest1();
MyTest2 myTest1 = new MyTest2();
//指定线程名称
myTest1.setName("非守护")
myTest2.setName("守护")
//指定myTest2为守护线程
myTest2.setDaemon(true);
//启动线程(当线程1执行完毕,线程2跟随线程1结束)
thread1.start();
thread2.start();
}
出让线程
出让当前CPU的执行权,让线程重新抢占执行权。(全部线程抢占执行权,也有可能是当前出让的线程再次抢到执行权)
public class MyTest extends Thread {
@Override
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println(String.format("%s线程:执行到了%d",getName(),i+1));
//出让线程:出让当前CPU的执行权,让线程重新抢占执行权
Thread.yield();
}
}
}
插入线程
如下代码,想等myTest线程执行完毕,再执行最后一句打印。
public class MyTest extends Thread {
@Override
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println(String.format("%s线程:执行到了%d",Thread.currentThread().getName(),i+1));
}
}
}
public static void main(String[] args) throws InterruptedException {
MyTest myTest = new MyTest();
myTest.start();
//插入线程
myTest.join();
System.out.println("我想等前面代码执行完,在执行!");
}
线程生命周期
注意:线程在运行代码时出现其他状况进入阻塞等待后,如果线程恢复后,不会接着进入运行状态,而是进入就绪状态,等待抢占执行权.
锁
在多线程编程中,锁的作用是保护临界区的代码,避免多个线程同时访问共享资源而导致的数据不一致、线程竞争、死锁等问题。在定义上一般分为悲观锁和乐观锁。
悲观锁:认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。悲观锁在Java中,synchronized关键字和Lock的实现类都是悲观锁。
乐观锁:认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法。
代码示例(synchronized):
public class MyTest extends Thread {
//static静态变量(多个对象下可以共享数据)
private static int count = 100;
@Override
public void run(){
while (true) {
synchronized (MyTest.class) { //这里将该类当做锁对象,注意锁对象要唯一
try {
Thread.sleep(100);//这里睡眠100毫秒
if (count == 0) {
break;
} else {
System.out.println(getName() + "线程:获得数据" + count);
count--;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
//此处我创建了两个对象,这里也是为什么将count变成静态变量,如果是一个就不需要了
MyTest myTest1 = new MyTest();
MyTest myTest2 = new MyTest();
//设置线程名称
myTest1.setName("A");
myTest2.setName("B");
//启动线程
myTest1.start();
myTest2.start();
}
代码示例(lock):
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyTest extends Thread {
//static静态变量(多个对象下可以共享数据)
private static int count = 100;
static Lock lock = new ReentrantLock();//创建lock锁的实现类
@Override
public void run(){
while (true) {
lock.lock();//调用加锁方法
try {
Thread.sleep(100);//这里睡眠100毫秒
if (count == 0) {
break;
} else {
System.out.println(getName() + "线程:获得数据" + count);
count--;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//把锁放到finally里面防止锁未被释放造成死锁,finally里面代码最终都会被执行
lock.unlock();//释放锁
}
}
}
}
public static void main(String[] args) throws InterruptedException {
//此处我创建了两个对象,这里也是为什么将count变成静态变量,如果是一个就不需要了
MyTest myTest1 = new MyTest();
MyTest myTest2 = new MyTest();
//设置线程名称
myTest1.setName("A");
myTest2.setName("B");
//启动线程
myTest1.start();
myTest2.start();
}
线程池
线程池是一组已经初始化并等待执行任务的线程集合,通过使用线程池,我们可以避免频繁地创建和销毁线程,从而节省资源和减少系统开销。线程池的核心思想是通过复用线程来提高性能。
线程池工作流程图如下:
Java 提供了 java.util.concurrent 包来支持并发编程。创建线程池的方法很多,以下代码利用ThreadPoolExecutor创建自定义线程池:
// 创建全局线程池
public class GlobalThreadPool {
private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
5, // 最大核心线程数
10, // 最大线程数,要大于核心线程数
60, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(3),//任务队列
Executors.defaultThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
);
// 将线程池设置为全局变量
public static ThreadPoolExecutor getThreadPool() {
return threadPool;
}
}
线程池常见方法:
- 执行无返回值的线程任务void execute(Runnable command):execute()只能提交Runnable类型的任务,没有返回值
- 提交有返回值的线程任务Future submit(Callable task):submit()既能提交Runnable类型任务也能提交Callable类型任务,可以返回Future类型结果,用于获取线程任务执行结果
- 关闭线程池:void shutdown(); 或 shutdownNow();
- shutdown 会等待正在执行的任务先完成,然后再关闭
- shutdownNow 会立刻停止正在执行的任务
- 等待线程池关闭:boolean awaitTermination(long timeout, TimeUnit unit);
具体区别可参考:https://blog.youkuaiyun.com/2301_79694645/article/details/142531117