1.程序与进程
- 程序:静态的代码
- 进程:正在运行的程序。动态的。
- 线程:一个进程可以细分多个线程,可以并行(同时)执行。
- 每个进程,独立资源分配单位;
- 线程:调度和执行的单位,有独立的程序计数器和运行栈。
(线程:切换环境不变;进程:切换之后环境啥的全部清空)
2. 并行和并发
- 并发执行:像单核cpu,同时其实只能执行一个,不同进程轮询切换,看起来好像都是同时执行的。(一段时间多个任务)
- 并行执行:真正的做到同时进行。(某刻时间,多个任务)
3.线程的生命周期
主要关注方法+状态
JDK中 Thread.State 的几种生命状态:
(五状态图)
- 新建态(创建态):声明和创建态new
- 就绪态:已经具备了运行的所有条件,就等着cpu的调度
- 运行态:运行所需的资源全部都足够了,且cpu资源现在也是自己的
- 阻塞态:被干预,被动进入阻塞态,让cpu终止了自己的执行
- 死亡(terminal):完成了所有的工作,或者被强制终止、出现异常之类的导致结束。(终态)
生命状态的转换:
- 创建态→就绪态:调用start(); 就绪态→运行态:获得cpu执行权;
- 运行态→阻塞态:被剥夺资源:sleep(long time);join()方法;等待同步锁;wait()状态、suspend();(阻塞只能到就绪)
- 阻塞态→就绪态:sleep()结束;join()结束;获取了同步锁;
- 运行态→就绪态:失去cpu的执行权(时间片到、yield();)
- 运行态→死亡态:run方法执行结束;stop调用线程;异常、error且未处理;
4.※线程的创建和使用:(4种方式)
-
thread类的方式 、 runnable接口的方式、新增的callable方式、线程池的方式。
-
单核cpu,先后完成任务比使用多个线程完成更快。
解释:单核的多线程在切换的时候会用很多时间。 -
多线程:提高用户体验。图形化界面更有意义。多模块,方便修改等。
Thread类的有关方法:
- start();启动当前线程,再调用当前线程的run()方法(因此是不可以直接启动run()方法的,必须是start()方法。
- run();通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
- currentThread();静态方法,返回执行当前代码的线程 (默认thread-0)
- getName();获取当前线程的名字
- setName();设置当前线程的名字
public class javaMethodTest {
public static void main(String[] args) { //主线程
HelloThread h1 = new HelloThread();
h1.setName("线程一"); //子线程的命名
h1.start();
Thread.currentThread().setName("主线程"); //给主线程命名(当前的线程)
for (int i = 0; i < 100; i++) {
if(i%2!=0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class HelloThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
构造器命名:
public class javaMethodTest {
public static void main(String[] args) { //主线程
HelloThread h1 = new HelloThread("Thread:1"); //写入要改的参数(名字)
h1.start();
Thread.currentThread().setName("主线程"); //给主线程命名
for (int i = 0; i < 100; i++) {
if(i%2!=0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class HelloThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public HelloThread(String name){ //构造器传参改名
this.super(name); //这里的this相当于是Thread.currentThread()这里指的是当前的线程。
}
}
- yield();释放当前cpu的执行权
- join();用thread调用,本身会抛出异常,try- catch一下:优先处理的一个线程(在线程a中调用线程b的join(),此时a阻塞,b完全执行完a才不阻塞)
stop();已过时了,强制结束当前线程- sleep(long millitime);单位是ms,抛出异常:try-catch,这里也不可以用抛出异常的方法(父类没有抛出异常的时候,子类不可以抛)(让当前线程在指定的时间内阻塞)
- ***.isAlive(); 判断当前线程是否存活。
线程通信方法:wait();notify();notifyAll();(不是Thread里面的方法)
线程的优先级调度:(OS)
Java调度方法:
- 相同优先级先进先出队列(先来先服务),时间片轮转策略
- 对高优先级,使用优先调度抢占式策略
MAX_PRIORITY=10;(最高级)
MIN_PRIORITY=1;
NORM_PRIORITY=5(默认优先级都是5)
获取当前线程的优先级:
.getPriority();默认的(未设置过的为5)
设置新的优先级:
setPriority();
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
♥ 注意: 设置之后,得到的并不是哪个优先级高就先把高的执行完,实际上还是交互性的。只是从概率上来讲,高概率的情况下会有高优先级优先执行。
执行结果如下:
5:0
1:1
5:2
1:3
5:4
1:5
.....
- 何时需要多线程?(守护线程+用户(主)线程)
多任务同时执行;
存在等待(资源)的任务;
后台运行。
多线程的创建:
(一条路径:单线程)
多线程:调用Thread类
1. 方法一:继承thread类的方式:
step1:声明(创建)thread类的子类;
step2:重写thread类的run();
step3:创建thread类的子类对象;
step4:通过此对象调用start()。
Thread extends Thread{ //1.创建thread子类继承Thread(子线程)
//2.重写Thread类的run()方法。run+enter.
@Override
public void run() {
//该线程需要做到的事情写在run()方法体中;
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(i);
}
}
}
}
public class ThreadTest { //(主线程)
public static void main(String[] args) {
// 3.创建Thread类的子类对象。new 上面的新线程,alt+enter
MyThread t1 = new MyThread();
// 4.通过此对象调用start()方法
t1.start ();
for (int i = 0; i <100 ; i++) {
if(i%2 != 0){
System.out.println("********");
}
}
主线程和子线程交互进行:(与不同计算机之间的处理频率有关)执行结果如下:
********
********
0
********
********
2
4
6
********
......
2.Thread匿名子类
/**
* 两个线程遍历不一样的,一个遍历100内的偶数,一个遍历100内的奇数
*/
public class ThreadDemo {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2(); //3. 创建两个线程的对象;
t1.start();
t2.start(); //4. 在主线程里面调用子类对象start()一下。
}
}
class MyThread1 extends Thread{ //1.创建不同的继承Thread类的两个方法1(线程1)
@Override
public void run() { //2.重写run();方法
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class MyThread2 extends Thread{ //1. 创建两个不同的继承Thread类的方法2(线程2)
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
得出交互性的结果:
Thread-1:1
Thread-0:0
Thread-1:3
Thread-0:2
Thread-1:5
Thread-0:4
Thread-1:7
Thread-0:6
......
(简便写法):创建Thread类的匿名子类:
public class ThreadDemo {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}.start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}.start();
}
}
3. 方法二:实现Runnable接口:
1. 创建一个实现了Runnable接口的类
```javaclass MThread implements Runnable{ } ```
2. 实现类去实现runnable中的抽象方法:run();
3. 创建实现类的对象;new***
4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5. 通过Thread类的对象调用start();
class MThread implements Runnable{ //1.创建的一个runnable接口;会有提示错误,alt+enter到2.
@Override //2.实现类去实现runnable中的抽象方法:run();
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+ i);
}
}
}
}
public class ThreadTest2 {
public static void main(String[] args) {
//3.创建实现类的对象
MThread mThread = new MThread();
// 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
//5. 通过thread调用start();启动线程、调用当前线程的run(),调用了runnable类型的target
t1.start();
Thread t2 = new Thread(mThread);
t2.setName("线程2");
t2.start();
}
}
区别:继承类有一定的局限性(共享声明static才行),而且实现方法天然地实现了共享,因此实现方式更优;如果创建的多个线程需要有共享数据,用实现的方式也可以天然地实现共享。
联系:thread类本身也实现了runnable方法;都需要重写run()方法,将要做的逻辑声明在run()方法中。
总结:
1. 实现的方式,没有类的单继承性的局限性
2.实现的方式更适合处理多个线程有共享数据的情况。
4.新增方式一(方法三):Callable接口
- 与Runnable相比的强大的有:
- runnable重写的是run()方法,无返回值;Callable重写的是 call()方法,可以有返回值。
- runnable只能try-catch异常,callable可以抛出throw异常,被外面的捕获,获取异常的信息。
- callable支持泛型的返回值。<>里面决定类型。
- 需要借助future task类,比如获取返回结果。(future task本身就实现了runnable接口)
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//1. 创建一个实现callable的实现类
class NumThread implements Callable{ //<>泛型
// 2. 重写call()方法,需要在执行的声明在call()方法中。可以抛出异常
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
if(i%2==0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
NumThread numThread = new NumThread(); //3. 建一个callable接口实现类的对象;
//4. 将callable接口实现类的对象作为参数传递到Future Task的构造器中,创建Future Task的对象
FutureTask futureTask = new FutureTask(numThread);
// 5. 将future task的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法。
new Thread(futureTask).start();
//get()方法
try { //6. 需要返回值才要的get()方法
//get方法的返回值,即为future task构造器参数callable实现类重写call的返回值(不需要返回值时,可以不用get来获取返回值)
Object sum = futureTask.get();
System.out.println("打印的总额为:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
5. 新增方法二(方法四):使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。===于是可以提前造好多个线程,使用时直接获取,用完放回,不销毁。
优点:可以避免频繁创建、销毁,还可以实现重复利用。
- 可以降低相应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用,也不要每次创建一次)
- 便于线程管理。(提前规定一下:核心池大小corePoolSize、最大线程数maximumPoolSize、无连接多少时间会释放keepaLiveTime…)
使用线程池方式:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10); //造了一个10个线程的线程池,具体到做什么还是需要自己再做
//2. 提供一个实现runnable或者callable接口的类的对象
service.execute(new NumberThread()); //适合用于runnable
service.execute(new NumberThread1()); //适合用于runnable
// service.submit(); //适合适用于callable
//3. 关闭
service.shutdown(); //关闭线程。
service.shutdown(); //关闭线程。
}
}
实现线程(属性)管理设置:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10); //造了一个10个线程的线程池,具体到做什么还是需要自己再做
//上面是接口,接口中的常量不用设置。下面进行强制转换,得以继承,使用子类的特有方法
ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
//属性设置
System.out.println(service.getClass());
service1.setCorePoolSize(15);
//....
//2. 提供一个实现runnable或者callable接口的类的对象
service.execute(new NumberThread()); //适合用于runnable
service.execute(new NumberThread1()); //适合用于runnable
// service.submit(); //适合适用于callable
//3. 关闭
service.shutdown(); //关闭线程。
service.shutdown(); //关闭线程。
}
}
总结:创建多线程的实现方式有4种。
5.※线程的同步:解决线程安全的问题:3种方式
共享的资源同步使用时会产生的安全问题:
一个实际问题:
买票的过程中出现重复票,或者错票的问题-1
- 问题的原因:还没操作完成时,其他线程参与进来,进行同样的操作,就混了。
- 解决方法:当一个线程在操作时,其他线程不可以参与进来,直到他完成操作时,其他的ticket才能参与进来。即使a出现了阻塞,也不可以被改变。(上锁)
- 代码解决方法:同步机制解决
1. 方式一:同步代码块
① runnable接口实现:
synchronized(同步监视器){
// 需要被同步的代码(不能包多了,也不能包少了)
}
说明:1.操作共享数据的代码是什么,即为需要被同步的代码
2.有无共享数据:多个线程共同操作的变量(数据)
3.同步监视器:锁。 要求多个线程必须要共用一把锁。(随便是什么锁,但是必须同一个)
4.操作同步代码时,只能有一个线程参与,相当于单线程的过程了,效率相对降低了。
class Window1 implements Runnable{
private int ticket = 100;
Object obj = new Object(); //任意一个类:谁的对象无所谓,但是必须是共用的一个
@Override
public void run() {
while (true){
synchronized(obj){
if (ticket > 0) {
try {
Thread.sleep(100); //这里阻塞,可能正好大家都被阻塞了。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class safeProblem {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
更简便的方法:synchronized(this){ }
② 使用同步代码块,解决继承Thread类的方式的线程安全问题:
继承方式下:static共享数据才可以。
继承Thread的方式:
class Window1 extends Thread{ }
添加静态声明:
static Object obj = new Object();
创建多个对象:
Window1 t1 = new Window1();
Window1 t2 = new Window1();
Window1 t3 = new Window1();
所以在这个继承Thread的方式下,创建了多个对象,不能用this(多个不同的锁)。可以写成synchronized(window1.class){ }
(类也是对象:且类只会加载一次)
2. 方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的———同步方法
① 使用同步方法来解决实现runnable接口的线程安全问题:
(把需要synchronized包起来的代码块单独写一个类)
class Window1 implements Runnable{
private int ticket = 100; //共享数据
@Override
public void run() {
while (true){
show();
}
}
private synchronized void show(){ //直接声明一下synchronized即可 (在这个show方法中的同步监视器就是this)
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ":卖票,还剩下" + ticket);
ticket--;
}
}
}
②使用同步方法解决继承Thread线程安全问题
class Window1 extends Thread{
private static int ticket = 100; //共享数据
@Override
public void run() {
while (true){
show();
}
}
private static synchronized void show(){
//这里的同步监视器修改后从t1、t2、t3变成了当前的类
if (ticket > 0) {
try {
Thread.sleep(100); //这里阻塞,可能正好大家都被阻塞了。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ":卖票,还剩下" + ticket); //静态调用
ticket--;
}
}
}
关于同步方法的总结:
- 仍然涉及到同步监视器,但是不需要显示声明。
- 非静态的同步方法,同步监视器为this(runnable接口)
静态的同步方法,同步监视器为当前类(Thread继承类)。
方式三:同步死锁问题(lock锁)
死锁:不同的线程分别占用对方的资源不放弃,都在等对方先放弃,所有的线程都处于阻塞状态,无法继续。
说明:死锁之后不会出现异常,不会报错,只是都处于阻塞态。使用同步时,要避免死锁。
lock.lock();+lock.unlock();中间的锁上。
- Q & A :
- synchronized与lock方法的相同点:都可以用来解决线程的安全问题
- 不同点:lock是手动的上锁lock和解锁unlock;synchronized是执行完之后自动的释放同步监视器。
- 选择:大部分都选的是synchronized,但是lock是jdk后面的版本新增的,而且手动的lock更加灵活,所以倾向优先用lock
- 建议的顺序:lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)
同步线程的练习:
Q:银行有一个账户;两个储户分别向同一个账户存3000元,每次存1000元,存三次。每次打印账户余额。
存在的什么安全问题?解决方案?
分析:
1. 是否是多线程?是,两个储户线程
2. 是多线程一定存在线程安全问题吗?--取决于是否有共享数据
有共享数据,账户(或者账户余额)
3. 存在安全问题吗?--两个线程共享操作共享数据。--存在。
4. 解决多线程安全问题:同步机制--三种方法
class Account{
private double balance;
public Account(double balance) {
this.balance = balance;
}
//存钱的方法
public synchronized void deposit(double amt){
if(amt > 0){
balance += amt;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"存钱成功,当前余额是:" + balance);
}
}
}
class Customer extends Thread{
private Account acct;
public Customer(Account acct){
this.acct = acct;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
acct.deposit(1000);
}
}
}
public class AccountTest {
public static void main(String[] args) {
Account acct = new Account(0);
Customer c1 = new Customer(acct);
Customer c2 = new Customer(acct);
c1.setName("甲");
c2.setName("乙");
c1.start();
c2.start();
}
}
6.线程的通信:
三个方法:
-
wait()方法:一旦执行此方法,当前线程进入阻塞态,并释放同步监视器。
-
notify()方法:一旦执行此方法,就会唤醒一个wait的线程,如果有多个线程被wait,就唤醒优先级高的,优先级相同的时候,随机唤醒一个。
-
notifyAll()方法:一旦被执行,唤醒所有的wait。
说明:
1. 这三个方法都必须是使用在同步代码块或者同步方法中,调用者必须是同步代码块或者同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常。(省略了this或者此类来调用的)
2. 这三个方法都是定义在java.lang.object类中的
sleep()和wait()方法的异同:
- 相同点:一旦执行,都会使当前的线程进入阻塞态;
- 不同点:
- 两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait();
- 调用的要求不一样,sleep()可以在任何场景下调用,wait()只能用在同步代码块或者同步方法由同步监视器中
- 是否释放同步监视器:如果两个方法都使用在同步代码块或者同步方法中,sleep()不会释放同步监视器,wait()会释放同步监视器。(wait有)
多线程问题:①生产者-②消费者问题
- 生产者productor:生产产品,将产品交给店员clerk(clerk最多只能有20个产品)
- 店员clerk:生产者生产了20个就会试图让生产者停止生产,没产品了让生产者生产一下,让消费者等一下
- 消费者custome:消费产品
- 可能的问题:
/**
* 分析:
* 1. 是否是多线程的?--是,生产者、消费者线程
* 2. 是否有共享数据?--有,店员(或者产品、产品的数据—)
* 3. 如何解决线程的安全问题?--同步机制,有三种方法
* 4.是否涉及到线程的通信?--是,sleep、wait、notify、、、
*/
class Clerk{ //① 共享数据类
private int productCount = 0; //⑧ 共享数据变量:仅体现产品数量上的变化
//⑦ 第一个方法:生产产品
public synchronized void produceProduct() { //⑨ 同步监视器clerk
if( productCount < 20){
productCount++;
System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");
notify(); //最后,唤醒另一个线程。
}else{
try {
wait(); //⑩ wait()没有notify()的情况下:消费者一直都是0的状态,于是不消费。+try-catch包住
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//⑦ 第二个方法:消费产品
public synchronized void eatProduct() { //⑨ 同步监视器clerk
if(productCount>0){
System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
productCount--;
notify(); //最后,唤醒另一个线程
}else {
try {
wait(); //⑩ wait()没有notify()的情况下:消费者一直都是0的状态,于是不消费。+try-catch包住
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{ //① 多线程--生产者继承Thread线程
private Clerk clerk; //② 共享变量:生产者和消费者公用的变量clerk+构造器构造方法
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override //③ 重写run()方法
public void run() {
System.out.println("生产者" + Thread.currentThread().getName() + ":开始生产产品");
while(true){
try {
Thread.sleep(10); // ④ 写得慢点 thread.sleep(ms);+try-catch包一下
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct(); //⑥ 共用类调用生产者方法; 生产者方法不存在,alt+enter快捷键生成一个在clerk类中。
}
}
}
class Eat extends Thread{ //① 多线程--消费者继承Thread类
private Clerk clerk; //② 共享变量:生产者消费者共用的变量clerk+构造器构造
public Eat(Clerk clerk) {
this.clerk = clerk;
}
@Override //③ 重写run()方法
public void run() {
System.out.println("消费者" + Thread.currentThread().getName() + ":开始消费产品");
while(true){
try {
Thread.sleep(20); // ④ 写得慢点 thread.sleep(ms);+try-catch包一下
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.eatProduct(); //⑥ 共用类调用生产者方法; 生产者方法不存在,alt+enter快捷键生成一个在clerk类中。
}
}
}
public class ProductTest { //① 主线程
public static void main(String[] args) {
Clerk clerk = new Clerk(); //新建公共变量用来调用
Producer p1 = new Producer(clerk); //调用
p1.setName("生产者"); //更名
Eat e1 = new Eat(clerk);
e1.setName("消费者1");
Eat e2 = new Eat(clerk);
e2.setName("消费者2");
p1.start(); //调用+启动
e1.start();
e2.start();
}
}
同步和异步:通常用来形容一次方法调用。
- 同步方法调用:一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
- 异步方法调用:更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另外一个线程中,“真实”地执行着。整个过程,不会阻碍调用者的工作。
同步类似于单线程;异步类似于两个一起同时进行。