线程的定义
Java线程是Java编程语言中的执行单元。在Java中,线程可以看作是轻量级的进程,它独立运行,具有自己的执行路径。
线程的原理
Java线程的实现基于操作系统的线程模型,但Java虚拟机(JVM)对线程的管理和调度做了封装和优化,使得Java线程更加可控和可靠。下面是Java线程的一些基本原理:
-
Java线程模型:Java线程模型是基于内核级线程模型的,即每个Java线程对应一个底层操作系统的线程。Java的线程管理由JVM负责,而不是由操作系统直接管理。
-
线程调度:Java线程的调度由JVM的线程调度器完成,它根据线程的优先级和调度算法来决定线程的执行顺序。Java线程的优先级范围是1到10,默认值为5。尽管可以设置线程的优先级,但操作系统可能不一定会严格按照优先级来调度线程。
-
线程同步:Java提供了多种机制来实现线程同步,包括synchronized关键字、Lock接口及其实现类、volatile关键字、wait()、notify()和notifyAll()等。这些机制可以确保多个线程之间对共享资源的访问是安全的,避免了竞态条件和数据不一致的问题。
-
线程状态转换:Java线程具有多个状态,如新建、就绪、运行、阻塞、等待和终止等。线程在不同状态之间转换的规则由JVM管理,开发人员通常无需直接控制线程的状态转换。
-
线程间通信:Java提供了多种机制来实现线程间的通信,包括共享内存、管道、消息队列、信号量、条件变量、读写锁等。其中,最常用的是共享内存和wait()/notify()机制。
-
守护线程:Java中的线程分为守护线程(Daemon Thread)和用户线程(User Thread)。守护线程是为其他线程提供服务的线程,当所有的用户线程结束后,守护线程会自动销毁。典型的守护线程包括JVM的垃圾回收线程和后台IO线程等。
总的来说,Java线程的原理是基于操作系统提供的线程机制,通过JVM对线程的封装和管理实现了更高级别的抽象和控制。Java的多线程编程模型相对较为简单,但开发人员仍需了解底层原理和掌握合适的线程管理技巧,以确保程序的正确性和性能。
线程的创建方式
Runnable接口实现
public class MyRunnable implements Runnable {
@Override
public void run() {
// 线程执行的任务逻辑
for (int i = 0; i < 5; i++) {
System.out.println("Thread is running: " + i);
try {
Thread.sleep(1000); // 模拟任务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 创建一个实现了Runnable接口的实例
MyRunnable myRunnable = new MyRunnable();
// 创建一个线程,传入实现了Runnable接口的实例
Thread thread = new Thread(myRunnable);
// 启动线程
thread.start();
}
}
Thread类实现
public class MyThread extends Thread {
@Override
public void run() {
// 线程执行的任务逻辑
for (int i = 0; i < 5; i++) {
System.out.println("Thread is running: " + i);
try {
Thread.sleep(1000); // 模拟任务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 创建一个继承自Thread类的实例
MyThread myThread = new MyThread();
// 启动线程
myThread.start();
}
}
二者区别
在 Java 中,创建线程可以通过实现 Runnable 接口或者扩展 Thread 类两种方式来实现。这两种方式有一些区别:
-
继承性:
使用 Runnable 接口实现线程的方式可以更好地利用类的继承,因为 Java 不支持多重继承,但可以实现多个接口。因此,如果一个类已经继承了某个类,仍然可以通过实现 Runnable 接口来创建线程。
直接扩展 Thread 类创建线程,该类就不再有机会继承其他类了。 -
资源消耗:
实现 Runnable 接口创建线程,多个线程可以共享同一个 Runnable 实例,因为线程对象和任务对象是分离的,这样可以节约系统资源。
直接扩展 Thread 类创建线程,每次创建一个新线程都需要新建一个 Thread 类的实例,这样可能会消耗较多的系统资源。 -
扩展性:
实现 Runnable 接口的方式更具有扩展性,因为可以在不修改现有类继承关系的情况下,为类添加新的行为。
直接扩展 Thread 类创建线程,线程与任务逻辑紧密耦合在一起,不够灵活。 -
面向对象原则:
使用 Runnable 接口实现线程更符合面向对象设计原则中的“组合优于继承”的理念,因为任务逻辑与线程本身是分离的,更符合单一职责原则。
直接扩展 Thread 类创建线程,线程类既承担了线程控制的职责,又包含了任务逻辑,可能违反了单一职责原则。
总的来说,虽然两种方式都可以用来创建线程,但在大多数情况下,推荐使用实现 Runnable 接口的方式来创建线程,因为它更灵活、更节约资源,更符合面向对象设计原则。
使用场景
-
并发编程:线程最常见的用途之一是实现并发编程。当一个程序需要同时执行多个任务时,可以使用多线程来实现并发执行,提高系统的响应速度和吞吐量。比如,一个网络服务器可以使用多线程同时处理多个客户端的请求,一个图形界面程序可以使用多线程来保持界面的响应性,同时执行耗时的任务。
比如下载图片,一共有A、B、C三个图片需要下载,在传统过程中,如果先下载A图片,如果A没下载完B是不能下载的,那么我们就可以利用并发编程来实现同时操作。代码如下public class ImageDownloader implements Runnable { private String imageUrl; public ImageDownloader(String imageUrl) { this.imageUrl = imageUrl; } @Override public void run() { System.out.println("Downloading image from: " + imageUrl); // 实现图片下载逻辑,这里用一个简单的休眠代替 try { Thread.sleep(2000); // 模拟下载耗时 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Download completed for: " + imageUrl); } public static void main(String[] args) { String[] imageUrls = {"https://example.com/image1.jpg", "https://example.com/image2.jpg", "https://example.com/image3.jpg"}; for (String url : imageUrls) { Thread thread = new Thread(new ImageDownloader(url)); thread.start(); } } }
-
异步编程:线程也常用于实现异步编程,即在执行耗时的操作时不会阻塞主线程,而是使用新的线程来执行这些操作,使得程序能够同时执行其他任务。异步编程在网络通信、文件读写、数据库访问等场景下特别有用,可以提高程序的性能和用户体验。
import java.util.concurrent.CompletableFuture; public class AsyncExample { public static void main(String[] args) { System.out.println("Main thread started"); // 创建第一个异步任务 CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> { System.out.println("Task 1 started"); try { Thread.sleep(2000); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Task 1 completed"); }); // 创建第二个异步任务 CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> { System.out.println("Task 2 started"); try { Thread.sleep(1000); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Task 2 completed"); }); // 等待所有任务完成 CompletableFuture<Void> allOf = CompletableFuture.allOf(future1, future2); // 阻塞直到所有任务完成 allOf.join(); System.out.println("Main thread completed"); } } // 输出内容: // Main thread started // Task 1 started // Task 2 started // Task 2 completed // Task 1 completed // Main thread completed
在这个示例中,我们使用CompletableFuture.runAsync()方法创建了两个异步任务future1和future2,它们分别模拟了耗时的操作。然后,我们使用CompletableFuture.allOf()方法来等待所有的任务都完成,最后使用join()方法来阻塞主线程直到所有任务完成。
-
任务分解:有些任务可以被分解成多个子任务,并行或者并发地执行,从而提高整体的执行效率。线程可以用来并行执行这些子任务,然后将它们的结果合并起来得到最终的结果。这种方式在科学计算、图像处理、数据分析等领域经常被使用。
import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; public class TaskDecompositionExample { public static void main(String[] args) throws InterruptedException, ExecutionException { // 创建一个线程池 ExecutorService executor = Executors.newFixedThreadPool(4); // 创建任务列表 List<Callable<Integer>> tasks = new ArrayList<>(); tasks.add(new SubTask(1, 100)); tasks.add(new SubTask(101, 200)); tasks.add(new SubTask(201, 300)); tasks.add(new SubTask(301, 400)); // 提交任务并获取Future列表 List<Future<Integer>> futures = executor.invokeAll(tasks); // 关闭线程池 executor.shutdown(); // 合并结果 int totalResult = 0; for (Future<Integer> future : futures) { totalResult += future.get(); } System.out.println("Total Result: " + totalResult); } // 子任务类 static class SubTask implements Callable<Integer> { private int start; private int end; public SubTask(int start, int end) { this.start = start; this.end = end; } @Override public Integer call() throws Exception { int result = 0; for (int i = start; i <= end; i++) { result += i; } return result; } } }
以上代码是求1~400的和,并且分成四个子任务同时求和。
-
事件驱动编程:许多 GUI 应用程序都使用事件驱动的编程模型,即通过监听事件来响应用户的操作。线程可以用来处理事件,以保持界面的响应性,同时执行其他任务。比如,在一个多线程的图形界面程序中,可以使用一个线程来监听用户的输入事件,另一个线程来更新界面的显示。
-
后台任务:有些任务不需要用户直接参与,可以在后台默默地执行。比如,定时任务、数据备份、日志记录等工作都可以通过线程来实现,而不影响用户的正常操作。
总的来说,线程在编程中的使用场景非常广泛,可以用来实现并发编程、异步编程、任务分解、事件驱动编程以及后台任务等各种功能。
并行、串行、并发的区别
串行
从开始到结束一直都是赛车A第一名、赛车B第二名、赛车C第三名、赛车D第四名
并行
从始至终,A、B、C、D四辆赛车肩并肩都是第一名
并发
并发也就是正常中的比赛,在比赛过程中,不同的时刻每辆车的名次不同,有可能超车,有可能在同一时刻两辆车平行前进
线程的生命周期
JDK5
JDK8
线程安全-synchronized
-
synchronized
是Java中的一个关键字,它的存在主要是为了解决线程安全问题。 -
可以修饰在方法和静态方法上、或者修饰代码块
public class TestThread { // 实例方法 public synchronized void testA() { ... } // 静态方法 public synchronized static void testB() { ... } // 修饰代码块 synchronized(同步监视器){ ... } }
同步监视器
在Java中,通过使用 synchronized 关键字来创建同步代码块或者同步方法,以确保在同一时刻只有一个线程可以访问被同步的代码块或方法。当一个线程进入同步代码块或方法时,它会尝试获取同步监视器(也称为锁),如果同步监视器已经被其他线程占用,则该线程会被阻塞,直到获取到同步监视器为止。
在使用 synchronized 关键字时,可以指定同步监视器。对于普通的同步方法,同步监视器是当前对象实例(即使用 synchronized 的方法所属的对象实例),而对于同步代码块,同步监视器可以是任意对象。
-
皇上御赐令牌 -> 锁(同步监视器)
-
城门外 便是 synchronized 修饰的代码块内容
-
如果线程A拿上令牌(独一份),一直不放回来,那么别人就别想出这个城门。
synchronized 关键字加到 static 静态方法和 synchronized(class) 代码块上是给 Class 类上锁。
synchronized 关键字加到实例方法上是给对象实例上锁。
解释:Class类,就相当于一个模子,而对象实例就是从模子中刻出来的实物。模子只有一个,但是实物可以有多个。
经典案例:火车站有100张票可售卖,一共有三个窗口,直到卖完为止
public class WindowsMain { public static void main(String[] args){ SaleTicketWindow sale = new SaleTicketWindow(); Thread thread01 = new Thread(sale); Thread thread02 = new Thread(sale); Thread thread03 = new Thread(sale); thread01.start(); thread02.start(); thread03.start(); } }
线程不安全情况
public class SaleTicketWindow implements Runnable{ static int ticket = 100; @Override public void run(){ while(true){ if(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "售出第" + ticket-- + "张票"); try { Thread.sleep(500L); } catch (InterruptedException e) { throw new RuntimeException(e); } }else { break; } } } }
使用synchronized ,解决线程安全
public class SaleTicketWindow implements Runnable{ static int ticket = 100; @Override public void run(){ while(true){ synchronized(this){ if(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "售出第" + ticket-- + "张票"); try { Thread.sleep(500L); } catch (InterruptedException e) { throw new RuntimeException(e); } }else { break; } } } } }
这里 同步监视器 使用
this
的原因是:this
指向当前的SaleTicketWindow
实例,而在主方法中我们只new
了一个实例,所以可以用this
,如果new
了多个SaleTicketWindow
,那么就不是同一把锁了。 -
wait和notify和sleep
-
wait方法属于Object类中的,sleep属于Thread类中的
-
wait方法释放锁资源和执行权,而sleep释放执行权,但不释放锁
-
wait方法一定要放在
synchronized
代码块中,sleep并没有强制要求加synchronized同步锁 -
sleep方法是指定时间自动唤醒的,而wait方法是需要notify方法唤醒或者notifyAll唤醒的
比如张三说“我睡三个小时,自然醒”,而狗蛋说:“我睡觉没准,必须有人给我喊醒,要不然一直睡”
两条线程交替打印1~100
class PrintOneHundred implements Runnable{
static int i = 0;
@Override
public void run() {
while (i <= 100){
synchronized (this) {
notify();
if (i < 100){
i++;
System.out.println(Thread.currentThread().getName() + "打印了" + i);
}
else {
break;
}
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
try {
sleep(50L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class PrintMain{
public static void main(String[] args) {
PrintOneHundred printOneHundred = new PrintOneHundred();
Thread t01 = new Thread(printOneHundred);
Thread t02 = new Thread(printOneHundred);
t01.start();
t02.start();
}
}
生产者&消费者&产品
class Clerk {
static int proCount = 0;
public synchronized void add(){
if (proCount >= 20){
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
proCount++;
System.out.println("生产者生产了第" + proCount + "个产品");
notify();
}
public synchronized void reduce() {
if (proCount <= 0) {
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("消费者消费了第" + proCount + "个产品");
proCount--;
notify();
}
}
class Customer extends Thread{
Clerk clerk;
public Customer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
while (true){
clerk.reduce();
try {
sleep(1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class Productor extends Thread{
Clerk clerk;
public Productor(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
while (true){
clerk.add();
try {
sleep(500L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class Main02{
public static void main(String[] args) {
Clerk clerk = new Clerk();
Customer customer = new Customer(clerk);
Productor productor = new Productor(clerk);
customer.start();
productor.start();
}
}
标签:Java,synchronized,Thread,线程,new,多线程,public
From: https://www.cnblogs.com/dragon-925/p/18224988