目录
程序、进程、线程
程序(program)是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码。
进程(process)就是运行中的程序,是操作系统分配资源的最小单位。
线程(thread)是一个进程内部的最小执行单元(具体要做的一件事),是操作系统进行任务调度的最小单元,隶属于进程,进程可进一步细化为线程。
线程和进程的关系
(1)一个进程可以包含多个线程,一个线程只能属于一个进程,线程不能脱离进程而独立运行;
(2)每一个进程至少包含一个线程(称为主线程);在主线程中开始执行程序,java程序的入口main()方法就是在主线程中被执行的;
(3)在主线程中可以创建并启动其它的线程;
(4)一个进程内的所有线程共享该进程的内存资源
创建线程
继承Thread类的方式
实现Runnable接口的方式
实现Callable接口创建线程
实现Callable接口与使用Runnable相比,Callable功能更强大些;
1.相比run()方法,可以有返回值
2.方法可以抛出异常
3.需要借助FuntureTask类,获取返回结果
(1)接收任务
FuntureTask funturetask = new FuntureTask(任务);
(2)创建线程
Thread thread = new Thread(funturetask);
thread.start();
Integer value = futureTask.get(); //获得线程call方法的返回值
线程的生命周期
图1
多线程
当涉及到多线程编程时,虽然多线程可以带来一些好处,但也存在一些缺点。下面是关于Java中多线程的优缺点:
优点:
-
提高程序的效率:多线程可以并行执行多个任务,从而提高程序的执行速度和效率。特别是在一些需要并发处理大量计算任务或IO操作的情况下,多线程可以更好地利用系统资源,提高执行效率。
-
提高系统的响应能力:多线程允许程序同时处理多个请求,这可以使系统更快地响应用户的操作。例如,在Web服务器中,多线程可以同时处理多个用户的请求,提高用户的访问速度和系统的并发性能。
-
提高资源利用率:多线程可以充分利用CPU的多个核心,使得CPU的利用率达到最大化。通过并行执行多个任务,可以减少CPU的空闲时间,提高资源的利用效率。
-
实现更复杂的功能:多线程可以实现一些复杂的功能,如多任务并行处理、并发读写操作、线程间的通信等。它可以帮助实现一些高级功能,如并发搜索算法、数据并行处理、分布式计算等。
缺点:
-
编程复杂度高:多线程编程涉及到线程的同步与互斥,需要考虑线程安全性、资源竞争、死锁等问题。编写和调试多线程程序比较复杂,容易出现并发相关的bug。
-
资源消耗大:每个线程都需要占用系统资源,包括内存和CPU时间片。当同时运行大量线程时,会消耗更多的系统资源,可能会导致系统的负载增加。
-
可能引发并发问题:多线程编程可能会引发一些并发问题,如数据竞争、死锁、活锁等。这些问题需要仔细地设计和思考,确保程序的正确性和稳定性。
-
可能引发性能下降:如果线程之间存在频繁的上下文切换,可能会导致性能下降。上下文切换需要花费时间和内存,同时线程间的竞争和同步也会带来一定的开销。在某些情况下,并不是线程越多越好,需要综合考虑来提升性能。
因此,在使用多线程编程时,需要仔细评估和权衡其优点和缺点,选择适当的场景和设计方案,确保程序的正确性和性能。
线程同步
解决线程安全问题
(1)同步代码块:使用 synchronized 同步关键字来修饰代码块,确保同一时刻只能有一个线程访问该代码块
(2)同步方法:使用 synchronized 同步关键字来修饰方法,确保同一时刻只能有一个线程访问该方法
(3)使用锁:Java 提供了内置的锁机制,通过 Lock 接口及其实现类,如 ReentrantLock 来控制多个线程对共享资源的访问
synchronized关键字的用法
(1)需要注意锁的作用范围,使用synchronized修饰一段代码块时,要注意多个线程对应的是同一个对象;synchronized修饰方法时,要注意方法是静态还是非静态,静态方法的锁对象是该类的Class对象,而非静态方法的锁对象默认是this
(2)使用synchronized时要注意避免出现死锁的情况,为了避免死锁,要合理规划锁的获取顺序,并避免长时间持有锁
synchronized和Lock锁的区别
(1)synchronized是Java语言内置的关键字,在方法上或代码块中使用;而Lock是一个接口,在Java的并发包(java.util.concurrent)中定义。
(2)synchronized关键字是隐式获取锁的,当线程进入同步代码块时自动获取锁,并在代码块执行完毕或发生异常时自动释放锁。而Lock锁需要显式地获取和释放锁,使用lock()方法获取锁,使用unlock()方法释放锁
(3)synchronized关键字只有一种锁,即对象锁,每个对象都有一个锁与之关联。而Lock锁提供了多种锁的实现,如ReentrantLock等
wait()和sleep()区别
(1)wait()是Object类中的方法,而sleep()是Thread类中的静态方法
(2)wait()方法必须在同步代码块或同步方法中使用,而sleep()方法可以在任何地方使用
(3)调用wait()方法会释放当前线程持有的锁,使得其他等待线程可以获取到该锁,而调用sleep()方法则不会释放锁
(4)调用wait()方法后,需要其他线程调用相同对象上的notify()或notifyAll()方法来唤醒等待的线程;而调用sleep()方法可以通过指定时间长度或者被中断来唤醒
引入案例:
public class Ticket implements Runnable {
int num = 10;
public void run() {
while (true) {
if (num == 0) {
break;
}
this.printTicket();
}
}
public synchronized void printTicket() {
if (num > 0) {
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + num);
num--;
}
}
}
public class Ticket extends Thread {
static int num = 10;
public void run() {
while (true) {
if (num == 0) {
break;
}
this.printTicket();
}
}
public static synchronized void printTicket() {
if (num > 0) {
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + num);
num--;
}
}
}
死锁
产生死锁的四个必要条件
互斥条件:资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。
不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放。
请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。
循环等待条件:在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。
引入一个案例:
public class DieLock extends Thread{
static final Object objectA = new Object();
static final Object objectB = new Object();
boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
public void run() {
if(flag) {
synchronized (objectA) {
System.out.println("if objectA");
synchronized (objectB) {
System.out.println("if objectB");
}
}
} else {
synchronized (objectB) {
System.out.println("else objectB");
synchronized (objectA) {
System.out.println("else objectA");
}
}
}
}
}
线程通信
引入案例:
public class PrintNumThread extends Thread{
static int num = 1;
static Object object = new Object();
public void run() {
while (true) {
synchronized (object) {
object.notify();
System.out.println(Thread.currentThread().getName() + num);
num++;
if(num>=100) {
break;
}
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//柜台
public class Counter {
int num = 3; //共享数据
//生产者添加商品
public synchronized void add() {
if(num == 0) {
num = 3;
this.notify();
System.out.println("生产者添加了三个商品");
}
else {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费者买走商品
public synchronized void sub() {
if(num > 0) {
num--;
this.notify();
System.out.println("消费者买走一个商品");
}
else {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者线程
public class CustomerThread extends Thread{
Counter counter;//共享数据
public CustomerThread(Counter counter) {
this.counter = counter;
}
public void run() {
while (true) {
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
counter.sub();
}
}
}
//生产者线程
public class ProductorThread extends Thread{
Counter counter;//共享数据
public ProductorThread(Counter counter) {
this.counter = counter;
}
public void run() {
while (true) {
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
counter.add();
}
}
}
引入案例:
public class SumTask implements Callable<Integer> {
int sum = 0;
public Integer call() throws InterruptedException {
Thread.currentThread().sleep(1000);
for (int i = 1; i <= 10; i++) {
sum += i;
}
return sum;
}
}
public class Test {
public static void main(String[] args) {
SumTask sumTask = new SumTask();
FutureTask futureTask = new FutureTask(sumTask);
Thread thread = new Thread(futureTask);
thread.start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}