Java中如何进行多线程编程,如何使用多线程?在Java标准库中提供了一个Thread类。Java中,一个进程正在运行时至少会有一个线程正在运行,这些线程在后台默默地执行,比如调用main()方法时就是这样的,主线程是由JVM创建的。实现多线程编程的方式主要有两种,一是继承Thread类,另一种是实现Runnable接口。这里我们先来看看Thread类的结构: 从源代码中可以发现,Thread类实现了Runnable接口,它们之间具有多态关系。其实,使用继承Thread类的方式创建新线程是,最大的局限就是不支持多继承,因为在Java语言特点就是单继承,所以为了支持多继承,完全可以实现Runnable接口的方式。
Java中如何创建线程呢?
1.显示继承Thread,重写run来指定现成的执行代码。
代码
public class Demo1 {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("hello world, 我是一个线程");
while (true) {
}
}
}
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
while (true) {
}
}
}
2.匿名内部类继承Thread,重写run来执行线程执行的代码。
代码
public class Demo2 {
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我是一个新线程");
}
}
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
}
};
t.start();
}
3.显示实现Runnable接口,重写run方法。
代码
public class Demo3 {
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我是一个新线程");
}
}
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
4.匿名内部类实现Runnable接口,重写run方法
代码
public class Demo4 {
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我是一个新线程");
}
}
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("我是一个新线程");
}
};
Thread t = new Thread(runnable);
t.start();
}
5.通过lambda表达式来描述线程执行的代码
代码
public class Demo4 {
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我是一个新线程");
}
}
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("我是一个新线程");
});
t.start();
}
【面试题】:Thread的run和start之间的区别?
run()方法::普通的方法调用,没有创建新的线程,输出语句是在原线程中执行的。 start()方法::这才是创建了一个新线程,由新的线程来执行输出
Thread类的具体用法
方法 | 说明 |
---|
Thread() | 创建线程对象 | Thread(Runnable target) | 使用 Runnable 对象创建线程对象 | Thread(String name) | 创建线程对象,并命名 | Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 | 【了解】Thread(ThreadGroup group, | | Runnable target) | 线程可以被用来分组管理,分好的组即是线程组 |
Thread类常见的一些属性
- ID是现成的唯一标识,不同线程不会重复
- 名称是各种调试工具会用到的
- 状态标识线程当前所处的一个情况
- 优先级高的线程理论上来说更容易被调度到
- 关于后台先后曾,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行
- 是否存活,即run方法是否运行结束了
- 线程的中断问题
我们通过编写具体的代码来观察方法的使用:
public class ThreadDemo6 {
public static void main(String[] args) {
Thread t=new Thread("cxk"){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
System.out.println(t.getName());
System.out.println(t.getPriority());
System.out.println(t.isDaemon());
System.out.println(t.getId());
System.out.println(t.isAlive());
System.out.println(t.isInterrupted());
System.out.println(t.getState());
t.start();
while (t.isAlive()){
System.out.println("cxk线程正在执行");
System.out.println(t.isInterrupted());
System.out.println(t.getState());
}
}
}
执行结果如下:会出现很多组相同的数据
中断一个线程
让一个线程结束有两种情况:
1.此线程已经把任务执行完了。即让线程run完(比较温和)。 2.此线程将任务执行到一半,被强制结束。即调用线程的interrupt()方法,比较激烈。
1.方法一:让线程run完
这种结束方式比较温和,当标记位被设置上之后,等到这次循环执行完了之后,在结束线程,如下,当线程执行到sleep的时候,已经sleep100ms了,此时isQuit被设置为true,当前线程不会立即退出,而是会继续sleep,把剩下的 400ms sleep完才会结束这个线程。
public class ThreadDemo7 {
private static boolean isQuit=false;
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(){
@Override
public void run() {
while (!isQuit){
System.out.println("别烦我,我在忙着转账呢");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("转账操作被终止");
}
};
t.start();
Thread.sleep(500);
System.out.println("有内鬼,终止交易!!!");
isQuit = true;
}
}
执行结果:
1.方法二:调用interrupted()方法
public class ThreadDemo8 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread() {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("别管我, 我在忙着转账呢");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
System.out.println("转账被终止.");
}
};
t.start();
Thread.sleep(5000);
System.out.println("对方是内鬼, 快终止交易!!!");
t.interrupt();
}
}
执行结果如下:
在这段代码中,t.start()是主线程继续往下执行之后,主线程还是会继续走,新线程则会执行run方法,如果没有后续的sleep,新线程能否继续输出就是不确定的了。原因:多线程之间是抢占实质性的,如果主线程中没有sleep,此时接下来CPU是执行主现成的isQuit=true还是新线程的while循环,这都是不确定的。对于新线程来说,run方法执行完,线程就结束了。对于主线程来说main方法执行完,住线程就结束了。 由上可得: 1.通过thread对象调用interrupt()方法通知该线程停止运行。 2.thread收到通知的方式有两种:
- 如果线程调用了wait/join/sleep等方法而阻塞挂起,则以InterrupterException异常的形式通知,清除中断标志
- 如果没有调用上述方式,就只是内部的一个中断标志被设置,thread可以通过Thread.interrupted()判断当前线程的中断标志被设置,来清除中断标志。也可以使用Thread.currentThread().isInterrupted()判断指定线程的中断标志被设置,但是不会清除中断标志。
在Java中第二种方式通知收到的更及时,即使线程正在sleep也可以马上收到。
public class ThreadDemo10 {
public static void main(String[] args) {
Thread t=new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().isInterrupted());
}
}
};
t.start();
t.interrupt();
}
}
public class ThreadDemo11 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(){
@Override
public void run() {
System.out.println("我是新线程");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t.start();
while (true){
System.out.println("我是主线程");
Thread.sleep(1000);
}
}
}
线程等待
线程之间是并发执行的关系,多个线程之间,谁先执行,谁后执行,谁执行到何处让出CPU…开发人员是完全无法感知的,全权由系统内核负责,例如,创建一个新线程的时候,此时接下来是主线程继续执行,还是新线程执行,这个事情是不能保证的,这就是“抢占式”执行的重要特点。这时候就引入了线程等待:开发人员可以控制哪个线程先结束,哪个线程后结束。join()方法的执行就会让线程阻塞,一直阻塞到对应线程执行结束之后,才会继续执行。这就可以控制线程结束的先后顺序。如果线程结束了才调用到join,此时也会立刻返回。
public class ThreadDemo12 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是线程1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t2=new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是线程2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("主线程执行完毕");
}
}
执行结果如下:
线程休眠
当线程在正常运行计算判断逻辑此时就是在就绪队列中排队,调度器就会从就绪队列中筛选出合适的PCB让他上CPU执行,如果某个线程调用sleep就会让对应的线程PCB进入到阻塞队列,线程一旦进入到了阻塞队列是没有办法上CPU执行的,对于sleep进入冷宫的时间是有限制的,时间到了之后,就自动被系统把这个PCB那回到原来的就绪队列中了。
线程的状态转换
|