一个线程就是一个 “执行流”. 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 “同时” 执行
着多份代码.
举个例子如下场景:
一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别排队执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread)。
序号 | 方法 | 解释 |
---|---|---|
1 | Thread() | 创建线程对象 |
2 | Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
3 | Thread(String name) | 创建线程对象,并命名 |
4 | Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
5 | 【了解】Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可 |
Thread t1 = new Thread();
//我们需要创建一个类来继承Runnable类,在实例化,下文在创建我会给读者朋友演示的
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
属性 | 获取方法 |
---|---|
ID | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
方法名 | 解释 |
---|---|
public void run() | 该方法用来封装线程运行时执行的内容 |
public synchronized void start() | 线程创建并执行run方法 |
public static native void sleep(long millis) throws InterruptedException | 使线程休眠millis毫秒(我们需要处理抛异常也可以用try catch) |
public final void join() throws InterruptedException | 等待线程结束(在哪个线程中调用哪个对象的join方法,哪个线程就等待哪个对象) |
public final synchronized void join(long millis) throws InterruptedException | 等待线程结束,()内可以添加你想等待的ms时间最多等待millis毫秒 |
public final synchronized void join(long millis, int nanos) throws InterruptedException | 指定最多等待时间等待线程,精确到纳秒 |
public void interrupt() | 中断线程对象所关联的对象,如果线程在休眠(阻塞状态)会抛出异常通知,否则设置中断标志位break |
public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后会清除线程的中断标志位 |
public boolean isInterrupted() | 判断当前线程的中断标志位是否设置,调用后不会影响线程的标志位 |
public final synchronized void setName(String name) | 修改线程对象名称 |
public static native Thread currentThread() | 获取当前线程对象 |
//MyThread来继承Thread类并且重写了run方法
class MyThread extends Thread{
@Override
public void run(){
System.out.println("hello 1");
}
}
public class dome1 {
public static void main(String[] args) {
Thread thread = new MyThread();
//记住只有执行start方法是线程才上真正的创建成功
thread.start();
}
}
//MyRunnable来继承Runnable接口,并且在run方法中重写要执行的内容
class MyRunnable implements Runnable{
@Override
public void run(){
System.out.println("hellow 3");
}
public class dome3 {
public static void main(String[] args) {
Thread thread3 = new Thread(new MyRunnable());
thread3.start();
}
}
public class demo4 {
public static void main(String[] args) {
Thread thread4 = new Thread(){
@Override
public void run(){
System.out.println("hello 4");
}
};
thread4.start();
}
}
public class deom5 {
public static void main(String[] args) {
Thread thread5 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello 5");
}
});
thread5.start();
}
}
public class deom5 {
public static void main(String[] args) {
Thread thread5 = new Thread(()-> {
System.out.println("hello 5");
});
thread5.start();
}
}
public class demo7 {
public static void main(String[] args) {
Thread thread7 = new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Thread线程在执行");
try {
Thread.sleep(1000);//休眠一秒,每过一秒打印一次
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread7.start();
for (int i = 0; i < 10; i++) {
System.out.println("main线程在执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
从上面的运行结果可以看出一个问题,因为thread线程与main线程都是每打印一句语句线程休眠1秒,两个线程唤醒的先后顺序是随机的,这也是java多线程中的一个“万恶之源”,这个问题给我们带来了很多麻烦,后续的博客我会细说
public class demo8 {
//创建一个不可更改成员实数count
public static final long count = 10_0000_0000;
//多线程使用方法
public static void thread() throws InterruptedException {
//获取开始执行时间戳
long start = System.currentTimeMillis();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
int a = 0;
for (int i = 0; i < count; i++) {
a++;
}
}
});
thread1.start();
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
int b = 0;
for (int i = 0; i < count; i++) {
b++;
}
}
});
thread2.start();
thread1.join();//需要抛异常
thread2.join();
//获取结束执行时间戳
long end = System.currentTimeMillis();
System.out.println("多线程执行时间:" + (end - start) + "ms");
}
//单线程方法
public static void one(){
//获取开始执行时间戳
long start = System.currentTimeMillis();
int a =0;
for (int i = 0; i < count; i++) {
a++;
}
int b =0;
for (int j = 0; j < count; j++) {
b++;
}
//获取结束执行时间戳
long end = System.currentTimeMillis();
System.out.println("单线程执行时间:" + (end - start) + "ms");
}
public static void main(String[] args) throws InterruptedException {
//多线程
thread();//因为thread方法执行了join方法所以要在main抛异常
//单线程
one();
}
}
我们发现在执行大量计算执行结束后多线程的效率是比单线程执行的效率要快很多
在我们下载好的jdk文件打开bin文件
找到这个文件双击
点击我们执行的文件
点击链接后,弹出这个页面,点击不安全链接
点击右上角线程这样我们就可以看到Java线程的一些属性
方法一:
public class deom9 {
private static boolean quite = false;
public static void main(String[] args) throws InterruptedException {
Thread thread9 = new Thread(new Runnable() {
@Override
public void run() {
//我们让每次相隔1秒打印一次
while (!quite) {
System.out.println("一个不起眼的线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread9.start();
//main和线程一起执行,这样我们可以先限制5秒这样就可以先打印5次在终止了
Thread.sleep(5000);
quite = true;
}
}
但是该方法是不够严谨的,有些场景可能达不到预期的效果,最优的做法就是调整线程对象或者线程类中的自带标志位
优化版本
public class demo10 {
public static void main(String[] args) throws InterruptedException {
Thread thread10 = new Thread(()->{
while (!Thread.interrupted()) {
System.out.println("一个不起眼的线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread10.start();
//休眠5秒
Thread.sleep(5000);
//使用interrupt方法修改线程标志位,使其中断
thread10.interrupt();
}
}
我看到当我们进行5秒后的中断指令后发现线程还在继续执行,抛出一个InterruptedException异常后,线程没有中断,而是继续运行,原因是interrupt方法遇到因为调用 wait/join/sleep 等方法而阻塞的线程时会使sleep等方法抛出异常,并且中断标志位不会修改为true,这时我们的catch语句里面值输出了异常信息并没有去中断异常,所以我们需要在catch语句中加上线程结束的收尾工作代码和退出任务循环的break语句就可以了。
这样我们就可以看到我们已经成功中断线程执行了
方法二:
首先使用currentThread方法获取线程对象,然后再调用该对象中的isterrupted方法获取该对象的中断标志位代替我们自己所写的isQuit标志位,然后等该线程运行一段时间后使用interrupt方法改变标志位,中断线程,写出如下代码,看看能不能达到预期效果:
我们在实际使用中建议使用方法二
public class demo10 {
public static void main(String[] args) throws InterruptedException {
Thread thread10 = new Thread(()->{
while (!Thread.currentThread().interrupted()) {
System.out.println("一个不起眼的线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("线程终止");
break;
}
}
});
thread10.start();
//休眠5秒
Thread.sleep(5000);
//使用interrupt方法修改线程标志位,使其中断
thread10.interrupt();
}
}
像上面的计算自增20亿次的例子就需要线程等待join方法,main线程需要等两个线程运行完毕后才能计算计算结束时的时间戳。
我们来假设几个线程,线程A表示调用join方法的线程,线程B表示join方法来自B线程对象,那么在A线程使用B.join方法,那就是A线程等待B线程结束
// join 效果就是等待线程结束. t1.join 就是让 main 线程等待 t1 结束. t2.join 让 main 线程等待 t2 结束.
t1.join();
t2.join();
当我们调用run方法就是单纯地调用了Thread对象中的一个重写普通方法而已,并没有创建一个新线程来执行run方法,而是通过main线程来执行的run方法,而使用start方法,会创建一个新线程并执行run方法。
注意:只有在执行线程的start方法时才是真正的完成创建线程