0. 序言
- Java并发,既然重点,亦是难点。
- 并发,即多线程,指多个线程同一时间去做多个事情,示例:炒菜哥,一人同时炒7个菜。
- 并发相对的是单线程,指一件事情做完才做其他事情,示例:炒完一个菜再去炒第二个菜。
- 并发不同于并行,并行指的是两个或更多任务同时进行,而并发指的两个任务都请求执行,而处理只能接受一个任务,就把两个任务安排轮流执行,由于时间间隔较短,使人感觉两个任务都在运行。
- 本片博文的主要内容有:
- 创建线程常用的三种方式
- 线程的生命周期
- 线程的加入
- 线程的中断
- 线程的礼让
- 线程的优先级
1. 创建线程常用的三种方式
- 继承Thread类
public class Main {
public static void main(String[] args) {
new ThreadTest().start();
}
public static class ThreadTest extends Thread{
private int count = 10;
public void run(){
while (true){
System.out.print(count+" ");
if (--count==0){
return;
}
}
}
}
}
说明:步骤:
① 定义类继承Thread
② 重写run方法
③ 把新线程要做的事写在run方法中
④ 创建线程对象,并自动执行run方法。
- 实现Runnable接口
如果需要继承其他类(非Thread类),而且还要使当前类实现多线程,那么可以通过Runnable接口来实现。
public class Main {
public static void main(String[] args) {
Runnable runnable = new ThreadTest();
Thread thread = new Thread(runnable);
thread.start();
}
public static class ThreadTest implements Runnable{
private int count = 10;
public void run(){
while (true){
System.out.print(count+" ");
if (--count==0){
return;
}
}
}
}
}
说明:步骤:
① 定义类实现Runnable接口
② 重写run方法
③ 将要写的代码写在run方法中
④ 使用参数为Runnable对象的构造方法创建Thread实例
⑤ 调用start方法启动线程
- 实现Callable接口
Callable接口属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能,主要表现为以下3点:
① Callable可以在任务结束后提供一个返回值,Runnable无法提供这个功能。
② Callable中的call()方法可以抛出异常,而Runnable的run()方法不能抛出异常。
③ 运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供了检查计算是否完成的方法。由于线程属于异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下就可以使用Future来监视目标线程调用Call()方法的情况。但调用Future的get()方法以获取结果时,当前线程就会阻塞,直到Call()方法返回结果。
public class Test implements Callable {
public static void main(String[] args) {
Test test = new Test();
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future submit = executorService.submit(test);
try {
Object o = submit.get();
System.out.println(o);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
@Override
public Boolean call() throws Exception {
return true;
}
}
true
说明:这里的情况还需要后续的文章进行补充,通过这个小例子,直到通过Callable接口可以在任务结束后得到一个返回值即可。
2. 线程的生命周期
- 出生状态:New
Thread thread1 = new Thread(instance);
- 就绪状态(可执行状态):Ready
thread1.start();
说明:用户调用start()方法后,线程就处于就绪状态。此时并没有得到系统资源。
- 运行状态:Runnable
说明:当线程得到系统资源后就进入运行状态。 - 等待状态:Wating
try {
thread1.wait();
} catch (InterruptedException e) {
e
说明:等待状态的线程必须调用Thread类中的nofify()方法才能被唤醒;notifyAll()方法是将所有处于等待状态下的线程唤醒。
- 休眠状态(超时等待状态):Time waiting
Thread.sleep(5000);
- 阻塞状态:Blocked
说明:当一个线程在运行状态下发出输入/输出请求,该线程进入阻塞状态;在其等待输入/输出结束时线程进入就绪状态,对于阻塞的线程来说,即使系统资源空闲,线程依然不能回到运行状态。 - 终止状态(死亡状态):Terminated
说明:有两种情况,分别是
① 当线程的run方法执行完毕时。
② 因为一个没有捕获的异常而终止了run方法。 - 使线程处于就绪状态的方法
① 调用sleep()方法
② 调用wati()方法
③ 等待输入输出完成 - 使线程处于运行状态的方法
① 线程调用notify()方法
② 线程调用notifyAll()方法
③ 线程调用interrupt()方法
④ 线程的休眠时间结束
⑤ 输入/输出结束
3. 线程的加入
- 举例说明:
public class Test_Join {
private static int A = 0;
private static int B = 0;
private Thread thread01;
private Thread thread02;
public Test_Join() {
thread02 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
if (B == 99) {
System.out.println("B==99");
}
B++;
}
});
thread01 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
if (A == 99) {
System.out.println("A == 99");
}
A++;
// try {
// thread02.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
});
}
private Thread getThread01() {
return thread01;
}
private Thread getThread02() {
return thread02;
}
public static void main(String[] args) {
new Test_Join().getThread01().start();
new Test_Join().getThread02().start();
}
}
说明:
① 两个线程分别给A或B++的操作,线程01先执行,应该是线程01先到99。
② 添加thread02.join();以后,线程1在执行了一个A++以后,线程2就开始执行,直到执行结束,线程1才执行。
4. 线程的中断
- 布尔值标记的运用
public class Test_Interrupt implements Runnable {
private static boolean isContinue = false;
private static int num = 0;
static Test_Interrupt runnable = new Test_Interrupt();
@Override
public void run() {
while (true){
if (isContinue){
break;
}else{
num++;
}
}
}
private static void setContinue(){
isContinue = true;
}
public static void main(String[] args) {
Thread thread = new Thread(runnable);
thread.start();
try {
Thread.sleep(50);
setContinue();
System.out.println(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
说明:以往有的时候会使用stop()方法来停止线程,但现在的JDK早就废除了stop()方法,不建议使用。现在提倡在run()方法中使用无限循环的形式,然后使用一个布尔型标记控制循环的停止。
- interrupt()方法的运用
如果线程是因为使用了sleep()或wait()方法进入了就绪状态,可以使用Thread类的interrupt()方法使线程离开run()方法,同时结束线程,但程序会抛出InterruptedException异常,用户可以在处理该异常时完成线程的中断业务处理,如终止while循环。在项目中经常在这里执行关闭数据库连接和关闭Socket连接等操作。
public class Test_Interrupt implements Runnable {
private static boolean isContinue = false;
private static int num = 0;
static Test_Interrupt runnable = new Test_Interrupt();
@Override
public void run() {
while (true) {
if (isContinue) {
break;
} else {
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程被中断");
setContinue();
}
}
}
}
private static void setContinue() {
isContinue = true;
}
public static void main(String[] args) {
Thread thread = new Thread(runnable);
thread.start();
thread.interrupt();
}
}
说明:由于调用了interrupt()方法,所以抛出了InterruptedException异常,在异常中我们关闭while循环。
5. 线程的礼让
- Thread类提供了一共礼让方法yield()表示,不过它只是给当前正处于运行状态下的线程一个提醒,告知它可以将资源礼让给其他线程,但这仅仅只是一个暗示,没有任何一种机制保证当前线程会将资源礼让。
- 对于支持多任务的操作系统来说,不需要调用yield()方法,因为操作系统会为线程自动分配CPU时间片来执行。
6. 线程的优先级
- 现在操作系统采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下次分配。线程分配到的时间片多少也决定了线程使用处理器资源的多少,而线程的优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。
- 在Java线程中,通过整型成员变量priority来控制优先级,范围是1~10,线程创建的时候用setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置浇地的优先级,确保处理器不会被独占。
- 然而在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。所以程序正确性不能依赖线程的优先级高低。
7. 后续
如果大家喜欢这篇文章,欢迎点赞!
如果想看更多 Java并发 方面的技术,欢迎关注!