为解决某种问题,使用计算机语言编写的一系列指令(代码)的集合,本章中的程序,特指的是静态的,安装在硬盘上的代码集合。
运行中的程序(被加载到内存中),是操作系统进行资源分配的最小单位。
进程可以进一步细化为线程,是进程内一个最小执行单位,是cpu进行任务调度的最小单位(早期没有线程,早期cpu在执行的时候,是以进程为单位执行,进程单位还是比较大的,当一个进程执行时,其他的进程就不能执行,所以后来,将进程中的多个任务,细化成线程,cpu执行单位,也从进程转为更小的线程)。
注:main也是一个线程叫做主线程,代码只能从上到下的顺序执行。以QQ举例,其为一个进程,操作系统会为这个进程分配内存资源,一个聊天窗口就认为是一个线程,这多个聊天窗口可以同时被cpu执行,但是这些聊天窗口属于进程。
在Java中要实现线程,最简单的方式就是扩展Thread类,重写其中的run方法,其本身并不执行任何操作,当线程启动时,它将执行run方法
public class MyThread extends Thread{
public void run(){
//线程操作
}
}
MyThread thread = new MyThread();
thread.start();
可以通过Runnable接口的方式来实现线程,其中仅有一个抽象方法,只要实现其中的run方法。
public class MyTask implements Runnable{
public void run(){
//线程操作
}
}
MyTask myTask = new MyTask();
Thread th = new Thread(myTask);
th.start();
new Thread(runnable);==>接收一个任务对象
new Thread(runnable,String name);==>接收一个对象并设置名字
线程生命周期,线程从创建到销毁,期间经历5个状态。
如图所示:
守护线程也是线程中的一种,区别在于它的结束,如果一个线程是守护线程,那么它会等java中其他的线程任务结束后,自动终止,它为其他线程提供服务,例如jvm的垃圾回收线程就是一个守护线程,守护线程设置要在start()方法之前,方法setDaemon(true)开启守护线程。
在一个应用程序中,存在多个线程,不同的线程可以并行的执行任务。
通过排队+锁的方式能保护共享资源的安全性。
synchronized (同步对象){
//要保护的资源
}
对多个线程对应的对象必须是同一个,在对象的对象头中有一块空间用来记录有没有线程进入到同步代码块中的,同步对象可以是Java中的任何类对象。
synchronized放在方法声明中,表示整个方法为同步方法。
public [static] synchronized void show(){
//要保护的资源
}
注意:
import java.util.*;
public class Main {
public static void main(String[] args) {
Bank bank = new Bank(0);
Thread thread = new Thread(bank);
thread.start();
Bank bank1 = new Bank(114514);
bank.deposit(1919);
bank.withdraw(810);
System.out.println("银行还有:" + bank1.getBalance());
}
}
class Bank implements Runnable{
private int balance;
public Bank(int balance) {
this.balance = balance;
}
// 存钱
public synchronized void deposit(int amount) {
balance += amount;
}
// 取钱
public synchronized void withdraw(int amount) {
if (balance < amount) {
throw new IllegalArgumentException("余额不足!");
}
balance -= amount;
}
public int getBalance() {
return balance;
}
@Override
public void run() {
Bank bank = new Bank(114514);
bank.deposit(1919);
bank.withdraw(810);
System.out.println("银行还有:" + bank.getBalance());
}
}
synchronized不仅可以修饰方法,还可以修饰代码块,通常是用来控制对某个对象的访问,以避免多个线程同时访问同一个对象所导致的竞争条件。
在这个示例中,我们使用了双重检查锁定(Double-checked Locking)的方式来确保单例。在getInstance()方法中,如果instance为空,则会创建一个新的Singleton对象,但是为了避免多个线程同时创建新的实例,我们使用了synchronized关键字来修饰代码块。
这个代码块中的锁是Singleton.class对象,也就是说,当一个线程进入这个代码块时,其他线程不能同时进入该代码块,直到锁被释放。这样可以避免多个线程同时创建Singleton实例的问题,从而保证单例的正确性和线程安全性。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
从JDK5.0开始,同步锁使用Lock对象充当,ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,可以显式加锁放锁,其只能对代码块加锁,不能对方法加锁。
ReentrantLock是Java中的一个锁实现,它可以用于线程同步。下面是一个简单的例子,说明如何使用ReentrantLock进行线程同步:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++; // 访问共享变量
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count; // 访问共享变量
} finally {
lock.unlock();
}
}
}
在上面的例子中,ReentrantLock被用来保护一个共享变量(count),确保多个线程同步访问该变量。
在increment方法中,lock.lock()方法会获取锁,如果锁已经被其他线程占用,则等待直到获得锁。在锁内部,可以访问共享变量,然后在finally块中调用lock.unlock()方法释放锁。
在getCount方法中,同样也需要获取锁,确保只有一个线程访问共享变量count,然后在finally块中释放锁。
这样可以保证多个线程并发操作共享变量时,不会出现数据竞争和错误结果,从而保证线程安全。
线程通讯指的是多个线程通过相互牵制,相互调度,即线程间的相互作用。
注意: .wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
Java线程通信机制可以使用wait、notify和notifyAll方法,让不同线程之间协调工作。下面是一个简单的例子,说明这三个方法的用法:
public class ThreadCommunicationExample {
public static void main(String[] args) {
Message message = new Message();
Thread producer = new Thread(new Producer(message));
Thread consumer = new Thread(new Consumer(message));
producer.start();
consumer.start();
}
}
class Message {
private String content;
private boolean empty = true;
public synchronized String read() {
while (empty) {
try {
wait(); // 等待消息的通知
} catch (InterruptedException e) {}
}
empty = true;
notifyAll(); // 通知生产者可以生产消息了
return content;
}
public synchronized void write(String content) {
while (!empty) {
try {
wait(); // 等待消费者的通知
} catch (InterruptedException e) {}
}
empty = false;
this.content = content;
notifyAll(); // 通知消费者可以消费消息了
}
}
class Producer implements Runnable {
private Message message;
public Producer(Message message) {
this.message = message;
}
@Override
public void run() {
String[] messages = {"Hello", "World", "Good", "Morning"};
for (String message : messages) {
this.message.write(message);
System.out.println("Produced " + message);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
this.message.write("Finished");
}
}
class Consumer implements Runnable {
private Message message;
public Consumer(Message message) {
this.message = message;
}
@Override
public void run() {
String message = null;
while ((message = this.message.read()) != "Finished") {
System.out.println("Consumed " + message);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
}
}
在上面的例子中,Message类表示一个消息对象,其中包含一个字符串和一个变量empty,用于表示消息对象是否为空。read方法表示消费者从消息对象中读取消息,如果消息对象为空,则等待生产者生产消息的通知。同时,消费者线程调用了wait()方法,使得自己进入阻塞状态,等待生产者线程调用notify()或者是notifyAll()方法来通知消费者线程可以消费消息了。
write方法表示生产者向消息对象中写入消息,如果消息对象不为空,则等待消费者消费消息的通知。同时,生产者线程调用了wait()方法,使得自己进入阻塞状态,等待消费者线程调用notify()或者是notifyAll()方法来通知生产者线程可以生产消息了。
在Producer和Consumer类中,分别模拟生产者和消费者。生产者线程不断向消息对象中写入消息,然后休眠1秒钟,消费者线程不断从消息对象中读取消息,然后休眠1秒钟,直到读取的消息为"Finished"时结束。
通过wait、notify和notifyAll方法的配合使用,生产者和消费者之间可以协调工作,保证线程安全。
在使用ReentrantLock时,可以使用Condition对象来进行线程通信。Condition是一个基于锁的等待/通知机制,与Object中的wait、notify和notifyAll方法类似,但是更灵活。下面是一个简单的例子,说明ReentrantLock和Condition如何进行线程通信:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadCommunicationExample {
public static void main(String[] args) {
Message message = new Message();
Thread producer = new Thread(new Producer(message));
Thread consumer = new Thread(new Consumer(message));
producer.start();
consumer.start();
}
}
class Message {
private String content;
private boolean empty = true;
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public String read() {
lock.lock();
try {
while (empty) {
condition.await(); // 等待消息的通知
}
empty = true;
condition.signalAll(); // 通知生产者可以生产消息了
return content;
} catch (InterruptedException e) {
return null;
} finally {
lock.unlock();
}
}
public void write(String content) {
lock.lock();
try {
while (!empty) {
condition.await(); // 等待消费者的通知
}
empty = false;
this.content = content;
condition.signalAll(); // 通知消费者可以消费消息了
} catch (InterruptedException e) {
} finally {
lock.unlock();
}
}
}
class Producer implements Runnable {
private Message message;
public Producer(Message message) {
this.message = message;
}
@Override
public void run() {
String[] messages = {"Hello", "World", "Good", "Morning"};
for (String message : messages) {
this.message.write(message);
System.out.println("Produced " + message);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
this.message.write("Finished");
}
}
class Consumer implements Runnable {
private Message message;
public Consumer(Message message) {
this.message = message;
}
@Override
public void run() {
String message = null;
while ((message = this.message.read()) != "Finished") {
System.out.println("Consumed " + message);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
}
}
在上面的例子中,Message类中定义了一个ReentrantLock对象lock和一个Condition对象condition,用于进行线程通信。read和write方法中使用lock.lock()和lock.unlock()来控制同步代码块,使用condition.await()和condition.signalAll()来进行线程等待和通知。
在Producer和Consumer类中,分别模拟生产者和消费者。生产者线程不断向消息对象中写入消息,然后休眠1秒钟,消费者线程不断从消息对象中读取消息,然后休眠1秒钟,直到读取的消息为"Finished"时结束。
通过ReentrantLock对象和Condition对象的配合使用,生产者和消费者之间可以协调工作,保证线程安全。
简单的来说就是在使用ReentrantLock时,可以使用Condition对象来进行线程通信,且:
import java.util.concurrent.Callable;
public class Example implements Callable {
private String message;
public Example(String message) {
this.message = message;
}
public String call() throws Exception {
Thread.sleep(1000);
return "The message is: " + message;
}
public static void main(String[] args) {
Example task = new Example("Hello, world!");
FutureTask Ftask = new FutureTask(task);
Thread thread = new Thread(Ftask);
thread.start();
String str = new String(Ftask.get());
}
}
这段代码中,我们创建了一个实现了Callable接口的Example类,并在构造函数中传入一个字符串类型的参数,表示消息。
在call()方法中,我们使用Thread.sleep()模拟了一个耗时的操作,并在1秒后返回一个带有消息的字符串。
在main()方法中,我们首先创建了一个Example实例task,然后使用该实例创建了一个FutureTask对象Ftask,将该FutureTask对象传入一个新的线程中启动。
接着,我们调用Ftask.get()方法来获取call()方法的返回值,并将其保存在一个字符串变量str中。需要注意的是,Ftask.get()是一个阻塞操作,在线程执行call()方法的过程中,主线程会一直等待直到call()方法返回结果。如果call()方法抛出了异常,Ftask.get()也会抛出相应的异常。
因此,整个程序的流程是: