简单创建Thread线程类:
package Demo_1_30_Thread线程; class MyThead extends Thread { // 线程主体类 private String title; public MyThead(String title) { this.title = title; } @Override public void run() { // 线程的主体方法 for (int x = 0; x < 10; x++) { System.out.println(this.title + "运行, x = " + x); } } } public class ThreadDemo { public static void main(String[] args) { // 必须使用start();方法进行多线程启动 MyThead myThead1 = new MyThead("1"); MyThead myThead2 = new MyThead("2"); MyThead myThead3 = new MyThead("3"); // 调用start()方法但是执行的是run()方法 myThead1.start(); myThead2.start(); myThead3.start(); } }
public synchronized void start() { // start类 if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); // 在start()方法里面调用了start0()方法;private native void start0();
started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } }
以上代码发现在start()方法里面会抛出一个“IllegaThread StateException”异常类对象,但是整个的程序并没有使用throws或者是明确地 try..catch处理,因为该异常一定是RuntimeException的子类,每一个线程类的对象只允许启动一次,如果重复启动就抛出此异常,
myThead1.start();
myThead1.start(); // 重复启动线程
上面代码在运行时就会出现此异常:
Exception in thread "main" java.lang.IllegalThreadStateException at java.base/java.lang.Thread.start(Thread.java:793) at Demo_1_30_Thread线程.ThreadDemo.main(ThreadDemo.java:25)
不同的操作系统在资源的调度上会有不同的算法,因为Java语言的可移植性的特点,JVM会与不同的操作做系统的底层函数交互,底层函数会调用资源分配的算法,最终JVM通过不同的操作系统来实现start0()方法。
start0(); // 在start()方法里面调用了start0()方法;private native void start0();
任何情况下,只要定义了多线程,多线程的启动永远只有一种方案:Thread类中的start()方法。
Runnable线程接口:
@FunctionalInterface public interface Runnable{ // 从JDK1.8以后的lamda表达式的引入就变成了函数式接口 public void run(); }
class MyThead implements Runnable { // 线程主体类 private String title; public MyThead(String title) { this.title = title; } @Override public void run() { // 线程的主体方法 for (int x = 0; x < 10; x++) { System.out.println(this.title + "运行, x = " + x); } } } public class ThreadDemo { public static void main(String[] args) { // 必须使用start();方法进行多线程启动 MyThead myThead1 = new MyThead("1"); MyThead myThead2 = new MyThead("2"); MyThead myThead3 = new MyThead("3"); // 调用start()方法但是执行的是run()方法 myThead1.start(); myThead2.start(); myThead3.start(); } }
此时start()方法无法使用,因为Runnable接口中没有此方法。
package Demo_1_30_Thread线程; import java.lang.Runnable; class MyThread implements Runnable { // 实现Runnable接口 private String title; public MyThread(String title) { this.title = title; } @Override public void run() { // 线程的主体方法 for (int x = 0; x < 10; x++) { System.out.println(this.title + "运行, x = " + x); } } }
package Demo_1_30_Thread线程; public class ThreadDemo { public static void main(String[] args) { // Thread的构造方法接收Runnable对象,那么就将Runnable的实现子类传入以实现线程创建 Thread thread1 = new Thread(new MyThread("对象1")); Thread thread2 = new Thread(new MyThread("对象2")); Thread thread3 = new Thread(new MyThread("对象3")); thread1.start(); thread2.start(); thread3.start(); } }
因为MyThread只是实现了Runnable接口,此时就不存在单继承的局限。
lamda表达式进行创建:
package Demo_1_30_Thread线程; public class ThreadDemo { public static void main(String[] args) { for (int x = 0; x < 3; x++) { String title = "线程对象-" + x; Runnable run = ()->{ // Runnable实现对象run的创建(run只是对象名称不是run()方法),()表示参数的接收,而Runnable的run()方法没有参数,因为Runnable中只有一个方法run(),所以{}里面表示run()的重写 for (int y = 0; y < 10; y++){ System.out.println(title + "运行,y = " + y); } }; new Thread(run).start(); // Thread对象的创建并调用start()方法 } } }
package Demo_1_30_Thread线程; public class ThreadDemo { public static void main(String[] args) { for (int x = 0; x < 3; x++) { String title = "线程对象-" + x; new Thread(()->{ // Thread构造方法接收的是Runnable对象,所以直接传入lamda表达式表示默认为传入构造Runnable对象的lamda表达式 for (int y = 0; y < 10; y++){ System.out.println(title + "运行,y = " + y); } }).start(); // Thread对象的创建并调用start()方法 } } }
在以后的开发中优先考虑的都是Runnable接口实现,并且永远都是通过Thread类对象启动线程。
Thread与Runnable的关系:
先看Thread类的定义:
public class Thread extends 0bject implements Runnable {}
发现Thread类也是Runnable的子类,那么之前继承Thread类其实也是重写了Runnable的run()方法;
Thread thread1 = new Thread(new MyThread("对象1")); Thread thread2 = new Thread(new MyThread("对象2")); Thread thread3 = new Thread(new MyThread("对象3")); thread1.start(); thread2.start(); thread3.start();
多线程设计模式使用的是代理设计模式,用户自定义的主体线程类进行核心的操作处理,其余的辅助功能全部交给Thread类进行处理。
在进行Thread启动多线程的时候调用的是start()方法,而后找到的是run()方法,但通过Thread类的构造方法传递了一个Runnable接口对象的时候,那么该接口对象将被Thread类中的target属性所保存,在start()方法执行的时候会调用Thread类中的run(Runnable target)方法,而这个run()方法去调用Runnable接口子类被覆写过的 target.run() 方法。
资源的描述应该交给Runnable进行。
创建一个简单的并发访问实现操作:
package Demo_1_30_买票程序实现并发访问; public class MyThread implements Runnable{ private int ticket = 5; private String title; @Override public void run() { for (int i = 0; i < 100; i++) { if (this.ticket > 0) { System.out.println("买票,ticket剩余: " + -- this.ticket); } } } }
package Demo_1_30_买票程序实现并发访问; public class Main { public static void main(String[] args) { MyThread myThread = new MyThread(); new Thread(myThread).start(); // 第一个线程启动 new Thread(myThread).start(); // 第二个线程启动 new Thread(myThread).start(); // 第三个线程启动 } }
多线程访问同一资源(mt),就是靠的每一个target。
Callable:
@FunctionalInterface
public interface Callable<V> {
public v call() throws Exception ;
}
设置的泛型就是方法的返回值类型,避免向下转型的隐患
使用Callable实现多线程处理:
package Demo_1_30_Callable实现多线程处理; import java.util.concurrent.Callable; public class MyThread implements Callable<String> { // 实现子类 @Override public String call() throws Exception { // 重写方法 for (int i = 0; i < 10; i++) { System.out.println("****************线程执行、i = " + i); } return "线程执行完毕!"; } }
package Demo_1_30_Callable实现多线程处理; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; public class Main { public static void main(String[] args) throws Exception { FutureTask<String> task = new FutureTask<>(new MyThread()); new Thread(task).start(); // task是FutureTask类型,FutureTask是RunnableFuture的实现子类,RunnableFuture是Runnable的实现子接口,所以FutureTask是Runnable的间接实现子类,所以task也是Runnable类型 System.out.println("【线程返回数据】:" + task.get()); } }
面试题:
请解释Runnable与Callable的区别?
- Runnable是在 JDK1.0的时候提出的多线程的实现接口,而Callable是在JDK1.5之后提出的;
- java.lang.Runnable接口之中只提供有一个run()方法,并且没有返回值;
- java.util.concurrent.Callable接口提供有call()方法,可以有返回值;
线程名称:
多线程的运行状态是不确定的,就像是成百上千的一模一样的马(线程)同时奔跑,那么想要清楚某个线程是哪个几乎不可能,所以就需要解决线程的名称问题,Thread类中就有提供:
- 构造方法: public Thread(Runnable target, String name);
- 设置名字: public final void setName(String name);
- 取得名字:public final String getName();
线程不能靠this来取得,但是所有线程都会执行run()方法,所以就需要通过run()方法取得,在Thread类中提供有方法:
- 获取当前线程:public static Thread currentThread();
设置名称操作:
package Demo_1_31_线程的命名操作; public class MyThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); //currentThread()方法是获取当前线程,要通过getName()方法获取当前线程的名称
} }
package Demo_1_31_线程的命名操作; public class Main { public static void main(String[] args) { MyThread mt = new MyThread(); new Thread(mt,"线程1").start(); // 设置线程名称 new Thread(mt).start(); // 未设置线程名称 new Thread(mt,"线程2").start(); // 设置线程名称 } }
输出结果:
可以看到,当没有给线程设置名称的时候,就会自动生成一个不重复的名称。
关于自动编号,Thread类中有如下操作,通过static属性:
private static int threadInitNumber; private static synchronized int nextThreadNum() { return threadInitNumber++; }
测试一个程序:
mt.run(); // 对象直接调用run()方法
输出结果:
// 当使用了"mt.run();" 在主方法中调用线程类中的run()方法,输出的结果是"main",可以得出一个结论:主方法也是一个线程
每当使用java命令执行程序的时候就表示启动了一个JVM进程,一台电脑可以同时启动多个JVM进程,每个JVM进程都有各自的线程。
进程之中主方法就是一个主线程,在任何开发中,主线程可以创建若干个子线程,创建子线程的目的是可以将一些复杂逻辑或者比较耗时的逻辑交由子线程处理。
测试一个子线程处理操作:
package Demo_1_31_线程的命名操作; public class Main { public static void main(String[] args) { // 子线程的处理 System.out.println("1、执行操作任务一"); new Thread(()->{ int temp = 0; for (int i = 0; i < Integer.MAX_VALUE; i++) { temp += i; } }).start(); System.out.println("2、执行操作任务二"); System.out.println("3、执行操作任务三"); } }
程序的输出结果很快,在程序输出结束后程序却没有结束,说明子线程还在进行运行处理。
程序中,主线程负责处理整体流程,而子线程负责处理耗时操作。
线程的休眠:
如果说现在希望某一个线程可以暂缓执行,那么就可以使用休眠的处理,在Thread类中有定义休眠方法:
- 休眠:publie static void sleep(long millis) throws InterruptedException; // 毫秒单位
- 休眠: public static void sleep(long millis,int nanos) throws InterruptedException;
在进行休眠的时候有可能会产生中断异常“InterruptedException”,中断异常属于Exception的子类,所以改异常必须进行处理。
测试休眠程序:
package Demo_1_31_线程休眠; public class Main { public static void main(String[] args) { for (int x = 0; x < 5; x++) { new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + ", i = " + i); try { Thread.sleep(1000); // 暂缓执行 } catch (InterruptedException e) { e.printStackTrace(); } } }, "线程对象 - " + x).start(); } } }
休眠的主要特点是可以自动实现线程的唤醒,以继续进行后续的处理;
但是需要注意的是,如果现在有多个线程对象,那么休眠也是有先后顺序的。
package Demo_1_31_线程休眠; public class Main { public static void main(String[] args) { Runnable run = () -> { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + ", i = " + i); try { Thread.sleep(1000); // 暂缓执行 } catch (InterruptedException e) { e.printStackTrace(); } } }; for (int x = 0; x < 5; x++) { new Thread(run, "线程对象 - " + x).start(); } } }
此时将产生五个线程对象,并且这五个线程对象执行的方法体是相同的。此时从程序的感觉来讲,好像是一起休眠,然后一起被唤醒,但是实际上是有差别的。
首先是所有对象将会一起进入到run()方法中执行(执行时有先后)
之后是其它程序的执行,并且每一步的操作都是有先有后的,并不是固定的顺序。
线程中断:
package Demo_1_31_线程中断; public class Main { public static void main(String[] args) throws Exception { Thread thread = new Thread(()->{ System.out.println("**** 睡觉补充精力 ****"); try { Thread.sleep(10000); // 睡十秒 System.out.println("**** 醒来 ****"); } catch (InterruptedException e) { // 中断后捕获异常 System.out.println("打断睡眠"); } }); thread.start(); // 开始睡觉 Thread.sleep(1000); if (!thread.isInterrupted()){ // 判断线程是否中断 System.out.println("打断"); thread.interrupt(); // 中断执行 } } }
所有正在执行的线程都是可以被中断的,中断线程必须进行异常的处理。
线程的强制执行:
满足某一条件后,某一线程对象可以一直占用资源,直到该线程的程序执行结束。
测试未进行强制执行的线程执行:
package Demo_1_31_线程的强制执行; public class Main { public static void main(String[] args) { Thread thread = new Thread(()->{ for (int i = 0; i < 20; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ", i = " + i); } },"玩耍的线程"); thread.start(); for (int x = 0; x < 20; x++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("【霸道的线程main线程】x = " + x); } } }
输出结果:
两个线程对象都是交替执行,但是如果希望主线程独占执行,就可以进行强制执行:
package Demo_1_31_线程的强制执行; public class Main { public static void main(String[] args) { Thread mainth = Thread.currentThread(); // 获取主线程 Thread thread = new Thread(()->{ for (int i = 0; i < 20; i++) { if (i == 3){ // 如果i=3,则开始强制执行主线程 try { mainth.join(); } catch (InterruptedException e) { e.printStackTrace(); } } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ", i = " + i); } },"玩耍的线程"); thread.start(); for (int x = 0; x < 20; x++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("【霸道的线程main线程】x = " + x); } } }
输出结果:
在i不等于3之前两个线程对象都是交替执行,当i==3后,就开始强制执行主线程了,且在主线程执行完之后才会继续执行另一个线程。
在执行线程的强制执行之前,一定要先获取强制执行的线程对象,才能调用join()方法进行强制执行。
线程的礼让:
线程的礼让指的是先将资源让出去让别的线程先执行。线程的礼让可以使用Thread中提供的方法:
- 礼让: public static void yield()。
礼让操作:
package Demo_1_31_线程的礼让; public class Main { public static void main(String[] args) { Thread thread = new Thread(()->{ for (int i = 0; i < 20; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ", i = " + i); if (i % 2 == 0){ // 线程的礼让 Thread.yield(); System.out.println("*** 玩耍的线程礼让 ***"); } } },"玩耍的线程"); thread.start(); for (int x = 0; x < 20; x++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("【霸道的线程main线程】x = " + x); } } }
输出结果:
可以发现,礼让程序执行后,就换另一个线程对象执行了。但是每一次调用yield()方法,都只会礼让一次当前资源。
线程优先级:
从理论上来讲线程的优先级越高越有可能先执行(越有可能先抢占到资源)。在Thread类中有两个操作方法;
- 设置优先级:public static void setPriority(int newPriority);
- 获取优先级:public final int getPriority();
在进行优先级定义的时候都是通过int型的数字来完成的,而对于此数字的选择在Thread类里面就定义三个常量:
- 最高优先级:public static final int MAX_PRIORITY;(值:10)
- 中等优先级:public static final int NORM_PRIORITY;(值:5)
- 最低优先级:public static final int MIN_PRIORITY;(值:1)
就算是设置了优先级,线程的优先级也并不绝对:
package Demo_1_31_线程优先级; public class Main { public static void main(String[] args) { System.out.println(Thread.currentThread().getPriority()); // 5 主线程是中等优先级 System.out.println(new Thread().currentThread().getPriority()); // 5 默认的线程对象是中等优先级 Runnable run = ()->{ for (int x = 0; x < 10; x++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "执行!"); } }; Thread thread1 = new Thread(run,"线程对象1"); Thread thread2 = new Thread(run,"线程对象2"); Thread thread3 = new Thread(run,"线程对象3"); thread1.setPriority(Thread.MAX_PRIORITY); // 设置优先级 thread2.setPriority(Thread.MIN_PRIORITY); // 设置优先级 thread3.setPriority(Thread.MIN_PRIORITY); // 设置优先级 thread1.start(); thread2.start(); thread3.start(); } }
输出结果:
同步问题:
简单的卖票测试:
package Demo_1_31_同步问题; public class MyThread implements Runnable{ private int ticket = 10; //总票数量 @Override public void run() { while (true){ if (this.ticket > 0){ System.out.println(Thread.currentThread().getName() + "买票,ticket = " + this.ticket--); }else { System.out.println("***** 没有票了!! *****"); break; } } } }
package Demo_1_31_同步问题; public class Main { public static void main(String[] args) { MyThread mt = new MyThread(); new Thread(mt,"买票1").start(); new Thread(mt,"买票2").start(); new Thread(mt,"买票3").start(); } }
此时程序在进行卖票时并没有出现问题,但是在追加了延迟操作后:
public class MyThread implements Runnable{ private int ticket = 10; //总票数量 @Override public void run() { while (true){ if (this.ticket > 0){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "买票,ticket = " + this.ticket--); }else { System.out.println("***** 没有票了!! *****"); break; } } } }
就可能会出现票数为负数的情况。
出现种情况的原因主要是因为:
当三个线程同时执行的时候,只剩一张票了,在第一个线程通过if判断,进入sleep()时,第二个线程也通过了判断,那么问题就来了:
当第一个线程通过了sleep()后被唤醒就会执行ticket--的操作,此时票数就为0了,而因为第二个线程已经通过了if判断,那么第二个线程在sleep()后被唤醒时,同样会执行ticket--的操作,所以票数就变为了负数。
并且如果第三个线程同样可能会造成以上效果,此时表现出来的就是线程的不同步。
线程同步的解决方案:
关键在于“锁”:当一个线程执行时,其它线程的程序在外面等待。
实现锁的功能:通过synchronized关键字来定义同步方法或同步代码块(在同步代码块的操作中的代码只允许一个线程执行)
1、利用同步代码块进行处理
synchronized(同步对象){
同步代码操作;
}
一般要进行同步对象处理的时候可以采用当前对象this进行同步。
public class MyThread implements Runnable { private int ticket = 12220; //总票数量 @Override public void run() { while (true) { synchronized (this) { if (this.ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "买票,ticket = " + this.ticket--); } else { System.out.println("***** 没有票了!! *****"); break; } } } } }
public class Main { public static void main(String[] args) { MyThread mt = new MyThread(); new Thread(mt,"买票1").start(); new Thread(mt,"买票2").start(); new Thread(mt,"买票3").start(); } }
输出结果:
加入同步处理之后,程序的整体的性能下降了。同步实际上会造成性能的降低。
2、利用同步方法解决:
只需要在方法定义上使用synchronized关键字即可。
package Demo_1_31_同步代码块和方法; public class MyThread implements Runnable { private int ticket = 20; //总票数量 public synchronized boolean sale(){ // 同步方法 if (this.ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "买票,ticket = " + this.ticket--); return true; } else { System.out.println("***** 没有票了!! *****"); return false; } } @Override public void run() { while (this.sale()) { } } }
package Demo_1_31_同步代码块和方法; public class Main { public static void main(String[] args) { MyThread mt = new MyThread(); new Thread(mt,"黄牛1").start(); new Thread(mt,"黄牛2").start(); new Thread(mt,"黄牛3").start(); } }
在日后学习Java类库的时候会发现,系统中许多的类上使用的同步处理采用的都是同步方法。
死锁:
死锁是在进行多线程同步的处理之中有可能产生的一种问题,所谓的死锁指的是若干个线程彼此互相等待的状态。
public class TuFei { public synchronized void say(XingRen xr){ System.out.println("先给钱再过路!"); xr.get(); } public synchronized void get(){ System.out.println("拿钱走人!!"); } }
public class XingRen { public synchronized void say(TuFei tf){ System.out.println("先过路再给钱!"); tf.get(); } public synchronized void get(){ System.out.println("给钱保命!!"); } }
public class DethLock implements Runnable{ private TuFei tf = new TuFei(); private XingRen xr = new XingRen(); @Override public void run() { tf.say(xr); } public DethLock(){ new Thread(this).start(); xr.say(tf); } public static void main(String[] args) { new DethLock(); } }
现在死锁造成的主要原因是因为彼此都在互相等待着,等待着对方先让出资源。
死锁属于正常调试问题。
若干个线程访问同一资源时一定要进行同步处理,而过多的同步会造成死锁。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库