当涉及到计算机操作系统中的并发执行时,进程和线程是两个核心概念。
一、程序(program)
程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码,静态对象。
二、进程(Process)
进程(Process):
- 进程是操作系统中的一个执行实例,它代表了
一个正在运行的程序
。 - 每个进程有自己
独立的
内存空间、代码段、数据段和系统资源。 - 进程之间相互隔离,彼此独立运行,进程之间的通信需要通过IPC(Inter-Process Communication,进程间通信)机制实现。
- 每个进程由操作系统进行分配资源和调度任务。
- 进程的创建和销毁比较耗费系统资源。
例如:使用键盘同时操作ctrl+alt+.
打开任务管理器,就可以查看电脑目前这执行的进程了
三、线程(Thread)
线程(Thread):
- 线程是进程的一部分,是进程中的执行单元。
- 每个进程可以包含多个线程,线程
共享进程的内存空间和系统资源
。 - 线程
能够并发执行
,且切换开销较小,因此能更高效地利用计算机资源。 - 线程之间可以
共享数据和资源
,但需要注意线程安全和资源同步问题。 - 线程的创建、启动和销毁都在进程内部进行。
例如:下面以微信来举例
当我们在电脑打开微信的时候,系统就会产生一个微信的进程,同时给微信这个进程动态的分配资源
通过微信,我们可以在聊天栏进行聊天,那么这样就会产生一个聊天的线程
,我们还可以浏览朋友圈,这样又会产生一个浏览朋友圈的线程
,而且这两个线程互不影响,比如看朋友圈的时候,我们同时可以接收信息。同时聊天和看朋友圈这两个线程都共享我们当前登录微信这个用户的数据和资源。
四、线程的创建和启动的四种方式
4.1、通过继承 Thread 类方式
- 定义子类继承Thread类。
- 子类中重写Thread类中的run方法。
- 创建Thread子类对象,即创建了线程对象。
- 调用线程对象start方法:启动线程,调用run方法。
//1、创建一个名称是MyThread的线程
class MyThread extends Thread {
public void run() {//2、完成线程真正的功能放在run()方法里面
// 线程执行的任务
System.out.println("聊天");
}
}
public class TestDemo {
public static void main(String[] args) {
// 创建线程
MyThread thread1 = new MyThread();//3、实例化一个线程对象
// 启动线程
thread1.start();//4、调用Thread类的start()方法执行线程
}
}
//运行结果
聊天
4.2、通过 Rummable 接口方式
如果程序员需要继承其他类(非 Thread )
而且还要使当前类实现多线程,那么可以通过 Rummable 接口来实现。
- 定义子类,实现Runnable接口。
- 子类中重写Runnable接口中的run方法。
- 通过Thread类含参构造器创建线程对象。
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
- 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
//1、创建一个名称是MyRunnable的线程
class MyRunnable implements Runnable {
public void run() {
// 线程执行的任务
System.out.println("聊天");//2、完成线程真正的功能放在run()方法里面
}
}
public class TestDemo {
public static void main(String[] args) {
// 创建线程
Thread thread = new Thread(new MyRunnable());//3、实例化一个线程对象
// 启动线程
thread.start();//4、调用Thread类的start()方法执行线程
}
}
//运行结果
聊天
4.3、通过实现Callable接口
案例创建一个线程,call()方法返回1到10的和。
package MyPackage;
import java.util.concurrent.*;
//1.创建一个类实现Callable接口,实现call()方法。
class MyCallable implements Callable {
private int num = 1;
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName() + ",正在执行!");
int sum = 0;
while (true) {
if(num<=10){
sum+=num;
++num;
}
else {
break;
}
}
return sum;
}
}
public class TestDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 2.创建一个FutureTask对象,传入Callable对象。
FutureTask task = new FutureTask(new MyCallable());
// 3.创建一个线程Thread,传入FutureTask对象。然后调用run()方法,运行该线程。
Thread thread = new Thread(task);
thread.start();
// 4.获取call()方法的返回值,通过FutureTask对象调用get()方法。
System.out.println("1到10的和="+task.get());
}
}
//运行结果
Thread-0,正在执行!
1到10的和=55
4.4、通过使用线程池
package MyPackage;
import java.util.concurrent.*;
//1.创建一个类实现Callable接口,实现call()方法。
class MyCallable implements Callable {
private int num = 1;
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName() + ",正在执行!");
int sum = 0;
while (true) {
if (num <= 10) {
sum += num;
++num;
} else {
break;
}
}
return sum;
}
}
public class TestDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 2、创建一个固定大小的线程池
ExecutorService service = Executors.newFixedThreadPool(5);
// 3、submit是用于Callable
Future f = service.submit(new MyCallable());
// 4、获取call()的返回值
Integer sum = (Integer)f.get();
System.out.println("1到10的和=" + ":" + sum);
// 5、关闭线程池
service.shutdown();
}
}
//运行结果
pool-1-thread-1,正在执行!
1到10的和=:55
或者使用 ThreadPoolExecutor 可以更灵活地配置线程池的参数,如核心线程数、最大线程数、任务队列等,以满足具体的业务需求。
package MyPackage;
import java.util.concurrent.*;
//1.创建一个类实现Callable接口,实现call()方法。
class MyCallable implements Runnable {
private int num = 1;
public void run() {
System.out.println(Thread.currentThread().getName() + ",正在执行!");
int sum = 0;
while (true) {
if (num <= 10) {
sum += num;
++num;
} else {
break;
}
}
System.out.println("1到10的和=" + ":" +sum); ;
}
}
public class TestDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 2、创建一个固定大小的线程池
ExecutorService executor = new ThreadPoolExecutor(
5,// 核心线程数
5,// 最大线程数
5,// 空闲线程的存活时间,单位为秒
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>() // 任务队列
);
// 3、循环提交任务给线程池:每个任务都是一个实现了 Runnable 接口的 MyTask 对象。
// 线程池会根据线程池参数自动创建和管理线程,分配任务执行。
for (int i = 0; i < 5; i++) {
Runnable task = new MyCallable();
executor.execute(task);
}
// 4、关闭线程池:这会使线程池停止接受新的任务,然后等待所有已提交的任务执行完成。
executor.shutdown();
}
}
//一种运行结果
pool-1-thread-1,正在执行!
pool-1-thread-3,正在执行!
1到10的和=:55
pool-1-thread-4,正在执行!
pool-1-thread-5,正在执行!
pool-1-thread-2,正在执行!
1到10的和=:55
1到10的和=:55
1到10的和=:55
1到10的和=:55
五、线程的状态和生命周期
5.1、线程的状态
新建(New)
:当线程对象被创建时,它处于新建状态。此时线程的相关资源还没有被分配,尚未开始执行。就绪(Runnable)
:当线程被启动后,进入就绪状态,表示线程已准备好执行,但还未被分配到CPU执行时间。运行(Running)
:当线程被系统调度后,进入运行状态,表示线程正在执行自己的任务。阻塞(Blocked)
:在某些情况下,线程可能会进入阻塞状态,例如等待I/O操作、获取锁等。在阻塞状态下,线程暂时停止执行,不参与CPU的调度,直到满足解除阻塞的条件。等待(Waiting)
:线程在调用wait()方法后,会进入等待状态,等待其他线程的通知或特定条件的满足。超时等待(Timed Waiting
):类似于等待状态,但是线程在调用带有超时参数的等待方法(如Thread.sleep()、Object.wait(long))后,线程会在超时时间到达或收到 notify/notifyAll 通知前等待。终止(Terminated)
:线程的任务执行完毕或出现异常时,线程会进入终止状态,表示线程已结束。
5.2、线程的生命周期
线程在不同状态之间转换的过程构成了线程的生命周期。
六、操作线程的方法
6.1、Thread类构造器
Thread():创建新的Thread对象
Thread(String threadname):创建线程并指定线程实例名
Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
Thread(Runnable target,String name):创建新的Thread对象
Thread():创建新的Thread对象
class MyThread extends Thread {
public void run() {
// 线程执行的任务
String name = Thread.currentThread().getName();//获取线程名称
System.out.println("和"+name+"聊天");
}
}
public class TestDemo {
public static void main(String[] args) {
// 创建线程
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
// 启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
//运行结果
和Thread-1聊天
和Thread-2聊天
和Thread-0聊天
Thread(String threadname):创建线程并指定线程实例名
package MyPackage;
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
public void run() {
// 线程执行的任务
String name = Thread.currentThread().getName();
System.out.println("和"+name+"聊天");
}
}
public class TestDemo {
public static void main(String[] args) {
// 创建线程
MyThread thread1 = new MyThread("大毛");
MyThread thread2 = new MyThread("二毛");
MyThread thread3 = new MyThread("三毛");
// 启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
//运行结果
和二毛聊天
和大毛聊天
和三毛聊天
Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
class MyThread extends Thread {
public void run() {
// 线程执行的任务
String name = Thread.currentThread().getName();
System.out.println("和"+name+"聊天");
}
}
public class TestDemo {
public static void main(String[] args) {
// 创建线程
Thread myThread = new Thread(new MyThread());
System.out.println("主线程:"+Thread.currentThread().getName());
// 启动线程
myThread.start();
}
}
//运行结果
主线程:main
和Thread-1聊天
Thread(Runnable target,String name):创建新的Thread对象
class MyThread extends Thread {
public void run() {
// 线程执行的任务
String name = Thread.currentThread().getName();
System.out.println("和"+name+"聊天");
}
}
public class TestDemo {
public static void main(String[] args) {
// 创建线程
Thread myThread = new Thread(new MyThread(),"二毛");
System.out.println("主线程:"+Thread.currentThread().getName());
// 启动线程
myThread.start();
}
}
//运行结果
主线程:main
和二毛聊天
6.2、Thread类的有关方法
void start(): 启动线程,并执行对象的run()方法
run(): 线程在被调度时执行的操作
String getName(): 返回线程的名称
void setName(String name):设置该线程名称
static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
public void run() {
// 线程执行的任务
//执行Thread.currentThread()获取当前线程对象,执行名称getName()获取当前线程名称
String name = Thread.currentThread().getName();
System.out.println("和"+name+"聊天");
}
}
public class TestDemo {
public static void main(String[] args) {
// 创建线程
MyThread thread1 = new MyThread("二毛");//初始化线程名称”二毛“
thread1.setName("三毛");//修改线程名称为”三毛“
// 启动线程
thread1.start();
}
}
//运行结果
和三毛聊天
static void sleep(long millis):(指定时间:毫秒)
- 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队
- 抛出InterruptedException异常
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
public void run() {
// 线程执行的任务
String name = Thread.currentThread().getName();
if(name.equals("二毛")){//判断但这个线程的名称是二毛的时候,然它睡2秒
try {
Thread.sleep(2000);//当前线程暂停两秒后执行
System.out.println("二毛两秒后在和你聊天");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("和"+name+"聊天");
}
}
public class TestDemo {
public static void main(String[] args) {
// 创建线程
MyThread thread1 = new MyThread("大毛");
MyThread thread2 = new MyThread("二毛");
MyThread thread3 = new MyThread("三毛");
// 启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
//运行结果
和大毛聊天
和三毛聊天
二毛两秒后在和你聊天
和二毛聊天
static void yield():线程让步(线程礼让)
- 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
- 若队列中没有同优先级的线程,忽略此方法
但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
package MyPackage;
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
public void run() {
// 线程执行的任务
String name = Thread.currentThread().getName();
if(name.equals("二毛")){
Thread.yield();//暂停当前正在执行的线程对象,并执行其他线程。
System.out.println("先不回复二毛信息,先回复大毛和三毛信息");
}
System.out.println("和"+name+"聊天");
}
}
public class TestDemo {
public static void main(String[] args) {
// 创建线程
MyThread thread1 = new MyThread("大毛");
MyThread thread2 = new MyThread("二毛");
MyThread thread3 = new MyThread("三毛");
// 启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
//运行结果
和大毛聊天
和三毛聊天
先不回复二毛信息,先回复大毛和三毛信息
和二毛聊天
//也有可能
先不回复二毛信息,先回复大毛和三毛信息
和二毛聊天
和三毛聊天
和大毛聊天
public final void join(long millisec):
当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
- 低优先级的线程也可以获得执行
package MyPackage;
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
public void run() {
// 线程执行的任务
String name = Thread.currentThread().getName();
System.out.println("和" + name + "聊天");
}
}
public class TestDemo {
public static void main(String[] args) throws InterruptedException {
// 创建线程
MyThread thread1 = new MyThread("二毛");
// 启动线程
thread1.start();
//设置主线程名称
Thread.currentThread().setName("main主线程");
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"====="+i);
if (i == 5) {
thread1.join();//先执行完子线程任务,在接着执行主线程任务
}
}
}
}
//运行结果
main主线程=====0
main主线程=====1
main主线程=====2
main主线程=====3
main主线程=====4
和二毛聊天
main主线程=====5
main主线程=====6
main主线程=====7
main主线程=====8
main主线程=====9
boolean isAlive():返回boolean,判断线程是否还活着
package MyPackage;
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
public void run() {
// 线程执行的任务
String name = Thread.currentThread().getName();
System.out.println("和" + name + "聊天");
}
}
public class TestDemo {
public static void main(String[] args) throws InterruptedException {
// 创建线程
MyThread thread1 = new MyThread("子线程");
// 启动线程
thread1.start();
System.out.println("子线程是不是还活着:"+thread1.isAlive());
//设置主线程名称
Thread.currentThread().setName("main主线程");
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"====="+i);
if (i == 5) {
thread1.join();//先执行完子线程任务,在接着执行主线程任务
}
}
System.out.println("子线程是不是还活着:"+thread1.isAlive());
}
}
//运行结果
子线程是不是还活着:true
main主线程=====0
main主线程=====1
main主线程=====2
main主线程=====3
和子线程聊天
main主线程=====4
main主线程=====5
main主线程=====6
main主线程=====7
main主线程=====8
main主线程=====9
子线程是不是还活着:false
stop(): 强制线程生命期结束,不推荐使用
建议通过自定义标识,然后通过改变标识的值,和判断该标识的值来结束线程
package MyPackage;
class MyThread extends Thread {
//自定义线程是否结束标识
private boolean flag = true;
public MyThread(String name) {
super(name);
}
public void run() {
// 线程执行的任务
while (flag) {
String name = Thread.currentThread().getName();
System.out.println("和" + name + "聊天"+flag);
}
}
public void stopMyThread() {
this.flag = false;
}
}
public class TestDemo {
public static void main(String[] args) throws InterruptedException {
// 创建线程
MyThread thread1 = new MyThread("二毛");
// 启动线程
thread1.start();
for (int i = 0; i < 6; i++) {
System.out.println("main======>"+i);
if (i == 3) {
thread1.stopMyThread();
System.out.println("线程停止");
}
}
}
}
//运行结果
main======>0
main======>1
main======>2
和二毛聊天true
main======>3
和二毛聊天true
线程停止
main======>4
main======>5
从上面运行结果可以看出,线程只执行了两次(和二毛聊天true),就停止了。
七、线程的优先级
先学习线程的优先级之前我们来看看线程是怎么调度的(即多个线程的执行顺序)
7.1、线程调度
线程调度是操作系统对各个线程的管理和分配CPU执行时间的过程。它决定了各个线程按照何种顺序执行,并且分配给每个线程多长时间来执行任务
-
抢占式调度(Preemptive Scheduling):
- 多数现代操作系统采用抢占式调度方式,它基于优先级或其他调度算法决定哪个线程可以执行。
高优先级的线程抢占CPU
。 - 操作系统会分配给每个线程一个时间片(一小段时间),当时间片用完后,操作系统会剥夺该线程的CPU执行权限,切换到其他线程继续执行。
- 抢占式调度允许操作系统在必要时
强制剥夺
当前正在执行的线程的CPU时间,以确保公平性、响应性和系统资源的合理利用。
- 多数现代操作系统采用抢占式调度方式,它基于优先级或其他调度算法决定哪个线程可以执行。
-
分时调度(Time-sharing Scheduling):
- 分时调度是抢占式调度的一种实现方式,它通过将CPU时间分割成小的时间片来切换线程,每个线程在一个时间片内进行执行。
- 在分时调度中,每个线程在一个时间片内轮流获得CPU时间,看起来好像是同时运行的。这种调度方式使得多个线程能够共享CPU,提高了系统的吞吐量和响应性。
-
协同式调度(Cooperative Scheduling):
- 在协同式调度中,线程执行完自己的任务后,
主动让出CPU控制权
,将执行权交给其他线程。 - 协同式调度依赖于线程的合作,只有在当前线程主动交出执行权的情况下,其他线程才能获得CPU执行权限。
- 这种调度方式要求线程编写者在适当的时候放弃CPU时间,否则可能导致某些线程长时间占用CPU而影响系统的整体性能。
- 在协同式调度中,线程执行完自己的任务后,
线程调度的目标是合理分配CPU资源,以提高系统性能和响应速度。通过使用调度算法,操作系统可以根据线程的优先级、等待时间、资源需求等因素来决定线程的执行顺序和分配的CPU时间。
需要注意的是,线程调度是由操作系统内核负责管理的,开发人员无法直接控制线程的调度顺序,但可以通过设置线程的优先级
和使用线程同步机制
来影响调度的结果。
7.2、学习线程的优先级
明确一点线程的优先级是执行概率大小问题,而不是执行先后顺序问题
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用,实际需要看CPU调度
7.2.1、线程的优先级表示
//线程的优先级等级
最高优先级----》MAX_PRIORITY:对应常数10
最低优先级----》MIN_PRIORITY:对应常数1
普通优先级----》NORM_PRIORITY:对应常数5(默认情况下main线程的优先级是5)
7.2.2、调度方法
- 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
- 对高优先级,使用优先调度的抢占式策略
说明
1)线程创建时继承父线程的优先级
2)低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
7.2.3、涉及的方法
- getPriority() :返回线程优先值
- setPriority(int newPriority) :改变线程的优先级
package MyPackage;
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
public void run() {
// 线程执行的任务
String name = Thread.currentThread().getName();
System.out.println("和" + name + "聊天");
}
}
public class TestDemo {
public static void main(String[] args) {
// 创建线程
MyThread thread1 = new MyThread("大毛");
MyThread thread2 = new MyThread("二毛");
MyThread thread3 = new MyThread("三毛");
//获取main线程优先级
System.out.println("main线程优先级为:" + Thread.currentThread().getPriority());
// 获取子线程默认优先级
System.out.println("获取线程默认优先级----------------------------");
System.out.println("thread1线程优先级为:" + thread1.getPriority());
System.out.println("thread2线程优先级为:" + thread2.getPriority());
System.out.println("thread3线程优先级为:" + thread3.getPriority());
/*先设置优先级再start*/
// 修改线程的优先级
System.out.println("修改线程的优先级-----------------------------");
thread1.setPriority(Thread.MIN_PRIORITY);//或者thread3.setPriority(1);
thread2.setPriority(2);
thread3.setPriority(10);
// 获取修改后的子线程默认优先级
System.out.println("修改后thread1线程优先级为:" + thread1.getPriority());
System.out.println("修改后thread2线程优先级为:" + thread2.getPriority());
System.out.println("修改后thread3线程优先级为:" + thread3.getPriority());
// 启动线程
System.out.println("启动线程-----------------------------------");
thread1.start();
thread2.start();
thread3.start();
}
}
//运行结果
main线程优先级为:5
获取线程默认优先级----------------------------
thread1线程优先级为:5
thread2线程优先级为:5
thread3线程优先级为:5
修改线程的优先级-----------------------------
修改后thread1线程优先级为:1
修改后thread2线程优先级为:2
修改后thread3线程优先级为:10
启动线程-----------------------------------
和大毛聊天
和三毛聊天
和二毛聊天
再次说明:低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用,实际需要看CPU调度
八、线程的同步
8.1、抛出问题
模拟火车站售票程序,开启三个窗口售票。在代码中判断当前票数是否大于 0,如果大于 0 则执行将该票出售给乘客的功能,
package MyPackage;
class Ticket implements Runnable {
private int tick = 10;
public void run() {
while (true) {
if (tick > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
String threadName=Thread.currentThread().getName();
System.out.println(threadName + "售出车票,tick号为:" +tick--);
} else {
break;
}
}
}
}
public class TestDemo {
public static void main(String[] args) {
// 创建线程
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("t1窗口");
t2.setName("t2窗口");
t3.setName("t3窗口");
t1.start();
t2.start();
t3.start();
}
}
//运行结果
t1窗口售出车票,tick号为:10
t2窗口售出车票,tick号为:9
t3窗口售出车票,tick号为:8
t3窗口售出车票,tick号为:7
t1窗口售出车票,tick号为:6
t2窗口售出车票,tick号为:5
t2窗口售出车票,tick号为:4
t1窗口售出车票,tick号为:3
t3窗口售出车票,tick号为:2
t2窗口售出车票,tick号为:1
t1窗口售出车票,tick号为:0
t3窗口售出车票,tick号为:-1
//或者
t1窗口售出车票,tick号为:10
t2窗口售出车票,tick号为:10
t3窗口售出车票,tick号为:9
t1窗口售出车票,tick号为:8
t2窗口售出车票,tick号为:6
t3窗口售出车票,tick号为:7
t2窗口售出车票,tick号为:4
t3窗口售出车票,tick号为:3
t1窗口售出车票,tick号为:5
t3窗口售出车票,tick号为:2
t2窗口售出车票,tick号为:2
t1窗口售出车票,tick号为:1
//或者
......
从运行结果可以看出:
1、当两个线程同时访问这段代码时(假如这时只剩下一张票),第一个线程将票售出,与此同时,第二个线程也已经执行完成判断是否有票的操作,并得出票数大于 0 的结论,于是它也执行售出操作,这样就会产生负数。
2、或者会售出两张一模一样的票
所以,在编写多线程程序时,应该考虑到线程安全问题。
8.2、问题的原因
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
8.3、解决办法:线程同步机制
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
8.3.1、synchronized 同步代码块:
语法如下:
synchronized (Object){
// 需要被同步的代码;
}
说明:
通常将共享资源的操作放置在 synchronized 定义的区域内,这样当其他线程获取到这个锁时,就必须等待锁被释放后才可以进入该区域。Obiect 为任意一个对象,每个对象都存在一个标志位,并具有两个值,分别为0和1。一个线程运行到同步块时首先检查该对象的标志位,如果为 0 状态,表明此同步块内存在其他线程,这时当期线程处于就绪状态,直到处于同步块中的线程执行完同步块中的代码后,这时该对象的标识位设置为 1,当期线程才能开始执行同步块中的代码,并将 Obiect 对象的标识位设置为 0,以防止其他线程执行同步块中的代码。
package MyPackage;
class Ticket implements Runnable {
private int tick = 10;
public void run() {
while (true) {
synchronized(this){
if (tick > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
String threadName=Thread.currentThread().getName();
System.out.println(threadName + "售出车票,tick号为:" +tick--);
} else {
break;
}
}
}
}
}
public class TestDemo {
public static void main(String[] args) {
// 创建线程
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("t1窗口");
t2.setName("t2窗口");
t3.setName("t3窗口");
t1.start();
t2.start();
t3.start();
}
}
//运行结果
t1窗口售出车票,tick号为:10
t1窗口售出车票,tick号为:9
t1窗口售出车票,tick号为:8
t1窗口售出车票,tick号为:7
t1窗口售出车票,tick号为:6
t1窗口售出车票,tick号为:5
t1窗口售出车票,tick号为:4
t1窗口售出车票,tick号为:3
t1窗口售出车票,tick号为:2
t1窗口售出车票,tick号为:1
8.3.2、synchronized 同步方法
synchronized还可以放在方法声明中,表示整个方法为同步方法。
例如:
public synchronized void show (String name){
….
}
package MyPackage;
class Ticket implements Runnable {
private int tick = 10;
public synchronized void doit() {
if (tick > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "售出车票,tick号为:" + tick--);
}
}
public void run() {
while (true) {
if (tick == 0) {
break;
} else {
doit();
}
}
}
}
public class TestDemo {
public static void main(String[] args) {
// 创建线程
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("t1窗口");
t2.setName("t2窗口");
t3.setName("t3窗口");
t1.start();
t2.start();
t3.start();
}
}
//运行结果
t1窗口售出车票,tick号为:10
t1窗口售出车票,tick号为:9
t1窗口售出车票,tick号为:8
t1窗口售出车票,tick号为:7
t1窗口售出车票,tick号为:6
t1窗口售出车票,tick号为:5
t1窗口售出车票,tick号为:4
t3窗口售出车票,tick号为:3
t3窗口售出车票,tick号为:2
t3窗口售出车票,tick号为:1
8.3.3、Lock(锁)
格式如下:
class A{
private final ReentrantLock lock = new ReenTrantLock();
public void m(){
lock.lock();
try{
//保证线程安全的代码;
}
finally{
lock.unlock();
}
}
}
实例:
package MyPackage;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;
class Ticket implements Runnable {
private int tick = 10;
private Lock lock = new ReentrantLock();
public void run() {
while (true) {
lock.lock();
try {
if (tick > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "售出车票,tick号为:" + tick--);
}
else {
break;
}
} finally {
lock.unlock();
}
}
}
}
public class TestDemo {
public static void main(String[] args) {
// 创建线程
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("t1窗口");
t2.setName("t2窗口");
t3.setName("t3窗口");
t1.start();
t2.start();
t3.start();
}
}
//运行结果
t1窗口售出车票,tick号为:10
t1窗口售出车票,tick号为:9
t1窗口售出车票,tick号为:8
t3窗口售出车票,tick号为:7
t3窗口售出车票,tick号为:6
t2窗口售出车票,tick号为:5
t1窗口售出车票,tick号为:4
t3窗口售出车票,tick号为:3
t2窗口售出车票,tick号为:2
t2窗口售出车票,tick号为:1
九、线程的通信
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
package MyPackage;
class Productor implements Runnable { // 生产者
Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
public void run() {
System.out.println("生产者开始生产产品");
while (true) {
try {
Thread.sleep((int) Math.random() * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.addProduct();
}
}
}
class Consumer implements Runnable { // 消费者
Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
public void run() {
System.out.println("消费者开始取走产品");
while (true) {
try {
Thread.sleep((int) Math.random() * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.getProduct();
}
}
}
class Clerk { // 售货员
private int product = 0;
public synchronized void addProduct() {
if (product >= 20) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
product++;
System.out.println("生产者生产了第" + product + "个产品");
notifyAll();
}
}
public synchronized void getProduct() {
if (this.product <= 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("消费者取走了第" +
product + "个产品");
product--;
notifyAll();
}
}
}
public class TestDemo {
public static void main(String[] args) {
// 创建线程
Clerk clerk = new Clerk();
Thread productorThread = new Thread(new Productor(clerk));
Thread consumerThread = new Thread(new Consumer(clerk));
productorThread.start();
consumerThread.start();
}
}