提示:这是我学完线程的部分理解,对线程有兴趣的小伙伴可以查看“JDK 11 API中文帮助文档”。
文章目录
提示:以下是本篇文章正文内容,下面案例可供参考
一、线程是什么?
Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
二、使用步骤
- 通过实现 Runnable 接口;
- 通过继承 Thread 类本身;
- 通过 Callable 和 Future 创建线程。
1.Thread常用的方法
方法 | 功能 |
---|---|
public void start() | 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
public final void setName(String name) | 改变线程名称,使之与参数 name 相同。 |
public final void setPriority(int priority) | 更改线程的优先级。 |
public void interrupt() | 中断线程。 |
public final boolean isAlive() | 测试线程是否处于活动状态。 |
代码如下(示例):
- Thread,在方法体上和类上需要继承Tread父类,然后需要重写父类的run()方法,下面分别是用主方法实现的主线程和另一个类实现的子线程。
package Thread;
public class Main1 {
public static void main(String[] args) throws InterruptedException {
myThread m=new myThread();
m.start();
for (int i = 0; i < 10; i++) {
System.out.println("我是主方法!"+i);
Thread.sleep(1000);
}
}
}
package Thread;
public class myThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是子线程!"+i);
}
}
}
运行结果为: 主线程和子线程会交替运行,每次的运行的结果都不相同。
2.Runnbale接口的实现
代码如下(示例):
package Thread;
public class myRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是Runnable方法"+i);
}
}
}
package Thread;
public class MainRunnable {
public static void main(String[] args) {
myRunnable m=new myRunnable();
Thread m1=new Thread(m);
m1.start();
for (int i = 0; i < 10; i++) {
System.out.println("我是主方法!"+i);
}
}
}
运行结果和上面的Thread的运行结果一样,都会交替执行。
3.通过 Callable 和 Future 创建线程
-
创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
-
创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
-
使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
-
调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
4.对比Thread和Runnable
提示:这里对两种实现进行总结:
继承Thread父类和实现Runnable接口效果一样。不过考虑到Java是单继承多实现,如果只有一个子线程,两种方法都可以。但是在后期实现的时候,可能会有多个线程一起执行,这个时候就需要实现Runable接口来实现。
5.安全问题
大家都知道线程会交替执行,但是其中就会出现一个新的问题,就是线程的安全问题,这样才能保证线程有序并且按照我们的意愿去执行,这就需要一把“锁”来实现。
原理为:需要一把公共的锁,在一个线程执行的时候,把锁给这个线程。同时其他的线程排队等待执行线程解开锁,注意:排队是随机的,不是先到先得,是按照优先级来获取的。
下面用代码来体现:卖票系统
package ThreadSafe;
public class Demo1 {
public static void main(String[] args) {
Runnable r=new myRunnable();
//创建三个线程代表卖票的窗口
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
static class myRunnable implements Runnable{
private Object o=new Object();//锁
private int count=10;//票数
//重写run方法
@Override
public void run() {
while (true){//用while循环 一直循环直到票被卖完
synchronized (o){//把锁 o 传入 代表谁执行谁拿到锁
if(count>0){
System.out.println("准备卖票!");
try {
Thread.sleep(1000);//让线程睡眠一秒 1秒=1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"正在卖票,余票为:"+count);
}else {
break;
}
}
}
}
}
}
这里是另一种实现方式:在方法上注明这个方法是锁方法,public static synchronized boolean sale()效果也可以达到。
/**
* 卖票方法
* @return
*/
public static synchronized boolean sale() {
if (count > 0) {
System.out.println("准备卖票!");
try {
Thread.sleep(1000);//让线程睡眠一秒 1秒=1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
//Thread.currentThread().getName() :得到当前执行线程的名字
System.out.println(Thread.currentThread().getName() + "正在卖票,余票为:" + count);
return true;
}
return false;
}
三、线程池
线程池的优势
(1)、降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)、提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
(4)提供更强大的功能,延时定时线程池。
提供了四种线程方法
Executors类提供了4种不同的线程池:
- newCachedThreadPool—缓存线程池
- newFixedThreadPool—定长线程池
- newScheduledThreadPool—周期线程线程池
- newSingleThreadExecutor–单线程线程池
总结
线程是非常重要的,同时对于的安全问题也是需要重视的,在创建多个线程时,尽量使用线程池来创建线程,如果考虑到多个线程,这个时候就必须需要用“锁”来保护线程稳定,从而让程序有序的走完。