线程
概念
线程是进程中的单个顺序执行流,是一条执行路径。一个进程如果只有一条执行路径,则成为单线程程序;而如果有多条执行路径,则成为多线程程序。
CPU分时调度
时间片,即CPU分配给各个程序的时间,每个进程被分配一个时间段,称作它的时间片。即该进程允许运行的时间,使各个程序从表面上看是同时进行的。
如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程,将当前进程挂起。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换,而不会造成CPU资源浪费。当又切换到之前执行的进程,把现场恢复,继续执行。
在宏观上:我们可以同时打开多个应用程序,每个程序并行,同时运行。
在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。多核提高了并发能力。
单核CPU和多核CPU(了解):
单核CPU同时只能干一件事情,当启动多个程序时,CPU快速切换轮流处理每个程序,但如果CPU不够强劲,同时排队等待处理的任务太多,就会感觉系统会有延时、反应慢、卡顿等,甚至某一个程序在处理时出现错误,无法响应了,会造成后面排队的其他任务只能等待。
多核CPU是在基板上集成多个单核CPU+任务分配系统,两个核心同时处理两份任务,相比单核执行速度更快,有利于同时运行多个程序,不容易造成卡顿,更流畅!
Java程序的运行过程:
通过eclipse(java命令)运行一个Java程序,java命令会启动JVM(java虚拟机),等于启动了一个应用程序,也就是启动了一个进程。
该进程会自动启动一个“主线程”,然后主线程去调用某个类的 main 方法,所以 main 方法运行在主线程中。在此之前的所有程序代码都是按照顺序依次调用的,都是单线程的。
如果希望程序中实现多段程序代码交替执行的效果,则需要创建多线程程序。
为什么要使用多线程?
单线程程序执行时都只有一条顺序执行流,如果程序执行某行代码时遇到了阻塞(比如:抛异常),那么程序的运行将会停滞在这一行,其他代码将会无法执行!
这就像去银行办理业务,只有1个业务窗口(单线程),所有的客户都需要在一个窗口排队办理业务,如果业务员在为某一个客户办理业务时,花费了很长时间,那么将会导致后面的客户等待很长时间,这样处理业务的效率也是非常低的。
但如果银行为了提高效率,同时开放了5个窗口(多线程),客户可以分布在这5个窗口分别办理业务,即使某一个窗口在为个别客户办理业务时花费了很长时间,但不影响其他窗口办理业务的进度。
多线程理解起来其实非常简单:
单线程的程序只有一个顺序执行流。
多线程的程序则可以包括多个顺序执行流,多个顺序流之间互不干扰。
[并行]和[并发]的区别: (这是两个概念)
并行执行指在同一时刻,有多条指令在多个处理器上同时执行;
并发执行指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
多线程的特性
随机性
多线程的程序在执行时,在某一时间点具体执行哪个线程是不确定的,可以确定的是某一个时间点只有一个线程在执行(单核CPU)。
虽然我们感觉这些线程像是在同时运行,实际上是因为CPU在快速切换轮流执行这些线程,由于切换速度是纳秒级别的,所以我们感觉不到。
如何实现多线程
由于线程是依赖进程存在的,因此首先需要创建一个进程,但进程是操作系统创建的,而Java程序是不能直接调用系统功能的。但Java可以去调用C或C++写好的程序去创建进程从而实现多线程程序。
在Java中要想实现多线程操作有两种方式,一种是继承Thread类,另一种是实现Runnable接口。接下来针对这两种创建多线程的方式分别进行讲解。
实现多线程1: 继承Thread类
Thread类概述
Thread类是在java.lang包中定义的类
JavaSE规范中规定,一个类只要继承了Thread类,此类就是多线程的子类。
在Thread子类中,必须重写该类的run()方法,此方法为线程的主体。
通过继承Thread类创建线程
接下来通过案例演示<通过继承Thread类的方式实现多线程>
下面创建线程,并测试主线程和创建的新线程(子线程)交替执行的效果
代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package thread; public class ThreadDemo1 { public static void main (String[] args) { MyThread1 t1 = new MyThread1 (); MyThread2 t2 = new MyThread2 (); t1.start(); t2.start(); } } class MyThread1 extends Thread { @Override public void run () { for (int i=0 ;i<1000 ;i++){ System.out.println("你是谁啊?" ); } } } class MyThread2 extends Thread { @Override public void run () { for (int i = 0 ; i < 1000 ; i++) { System.out.println("开门,我是查水表的!" ); } } }
从上面的运行结果可以看出,main方法(主线程)和run方法(子线程)中的两个for循环中的输出语句交替执行了,说明通过集成Thread类实现了多线程。
(如果没有测试出主线程和子线程交替执行的效果,可以多测试几次!)
实现多线程2: 实现Runnable接口
Runnable接口概述
通过继承Thread类实现了多线程,但是这种方式有一定的局限性。因为Java中只支持单继承,一个类一旦继承了某个父类就无法再继承Thread类,例如猫类Cat继承了动物类Animal,就无法通过继承Thread类实现多线程。
为了克服这种弊端,在Thread类中提供了两个构造方法:
public Thread(Runnable target)
public Thread(Runnable target,String name)
这两个构造方法都可以接收Runnable的子类实例对象,这样创建的线程将调用实现了Runnable接口类中的run()方法作为运行代码,而不需要调用Thread类的run()方法,所以就可以依靠Runnable接口的实现类启动多线程。
通过实现Runnable接口实现多线程
接下来通过案例演示<通过实现Runnable接口的方式实现多线程>
下面创建线程,并测试主线程和创建的新线程(子线程)交替执行的效果
代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package thread; public class ThreadDemo2 { public static void main (String[] args) { MyRunnable1 r1 = new MyRunnable1 (); MyRunnable2 r2 = new MyRunnable2 (); Thread t1 = new Thread (r1); Thread t2 = new Thread (r2); t1.start(); t2.start(); } } class MyRunnable1 implements Runnable { @Override public void run () { for (int i = 0 ; i < 1000 ; i++) { System.out.println("hello~大姐姐~~" ); } } } class MyRunnable2 implements Runnable { @Override public void run () { for (int i = 0 ; i < 1000 ; i++) { System.out.println("来了~老六~~" ); } } }
从上面的运行结果可以看出,main方法(主线程)和run方法(子线程)中的两个for循环中的输出语句交替执行了,说明实现Runnable接口同样也实现了多线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package thread; public class ThreadDemo3 { public static void main (String[] args) { Thread t1 = new Thread () { @Override public void run () { for (int i = 0 ; i < 1000 ; i++) { System.out.println("who are you~~" ); } } }; t1.start(); Runnable r1 = () -> { for (int i = 0 ; i < 1000 ; i++) { System.out.println("i'm 老六~" ); } }; Thread t2 = new Thread (r1); t2.start(); } }
Thread的常用方法和总结
CurrentThreadDemo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package thread; public class CurrentThreadDemo { public static void main (String[] args) { Thread main = Thread.currentThread(); doSome(); System.out.println("主线程:" +main); new Thread ("无敌霸王鸡" ){ @Override public void run () { Thread t = Thread.currentThread(); System.out.println(t); doSome(); } }.start(); } public static void doSome () { Thread t = Thread.currentThread(); System.out.println("运行doSome方法的线程:" +t); } }
ThreadInfoDemo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package thread; public class ThreadInfoDemo { public static void main (String[] args) { Thread main = Thread.currentThread(); String name = main.getName(); System.out.println("名字:" +name); long id = main.getId(); System.out.println("id:" +id); int priority = main.getPriority(); System.out.println("优先级:" +priority); boolean alive = main.isAlive(); boolean daemon = main.isDaemon(); boolean interrupted = main.isInterrupted(); System.out.println("是否活着:" +alive); System.out.println("是否为守护线程:" +daemon); System.out.println("是否被中断:" +interrupted); } }
PriorityDemo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package thread; public class PriorityDemo { public static void main (String[] args) { Thread min = new Thread (){ public void run () { for (int i = 0 ; i < 10000 ; i++) { System.out.println("皇帝来找min啦" ); } } }; Thread norm = new Thread (){ public void run () { for (int i = 0 ; i < 10000 ; i++) { System.out.println("皇帝来找norm啦" ); } } }; Thread max = new Thread (){ public void run () { for (int i = 0 ; i < 10000 ; i++) { System.out.println("皇帝来找max啦" ); } } }; min.setPriority(Thread.MIN_PRIORITY); max.setPriority(10 ); min.start(); norm.start(); max.start(); } }
进程
什么是进程
进程是操作系统中运行得到一个任务(一个应用程序运行在一个进程中).
进程(process)是一块包含了某些资源的内存区域.操作同利用进程把它的工作划分为一些功能单元.
进程中所包含的一个或者多个执行单元称为线程(thread).进程拥有一个私有的虚拟地址空间,该空间仅能被他所包含的线程访问
线程只能归属于一个进程并且他只能访问该进程所拥有的资源.当操作系统创建一个进程后,该进程会自动申请一个主线程或者首要线程的线程
线程状态
概述
新建状态(New):当一个线程对象被创建后,线程就处于新建状态。在新建状态中的线程对象从严格意义上看还只是一个普通的对象,还不是一个独立的线程,不会被线程调度程序调度。新建状态是线程生命周期的第一个状态。
例如: Thread t = new MyThread();
就绪状态(Runnable):处于新建状态中的线程被调用start()方法就会进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,但并不是说执行了start()方法线程就会立即执行;
另外,在等待/阻塞状态中的线程,被解除等待和阻塞后将不直接进入运行状态,而是首先进入就绪状态
运行状态(Running):处于就绪状态中的线程一旦被系统选中,使线程获取了 CPU 时间,就会进入运行状态。
线程在运行状态下随时都可能被调度程序调度回就绪状态。在运行状态下还可以让线程进入到等待/阻塞状态。
在通常的单核CPU中,在同一时刻只有一个线程处于运行状态。在多核的CPU中,就可能两个线程或更多的线程同时处于运行状态,这也是多核CPU运行速度快的原因。
注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,必须先处于就绪状态中;
阻塞状态(Blocked):根据阻塞产生的原因不同,阻塞状态又可以分为三种
a)等待阻塞:运行状态中的线程执行wait()方法,使当前线程进入到等待阻塞状态;
b)锁阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),线程会进入同步阻塞状态;
c)其他阻塞:通过调用线程的sleep(),suspend(), join(), 或发出了I/O请求时等,线程会进入到阻塞状态。当sleep()睡眠结束、调用resume(),?join()等待的线程终止或者超时、或I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):当线程中的run方法执行结束后,或者程序发生异常终止运行后,线程会进入死亡状态。处于死亡状态的线程不能再使用 start 方法启动线程。
SleepDemo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 package thread; import javafx.scene.transform.Scale; import java.util.Scanner; public class SleepDemo { public static void main (String[] args) { System.out.println("程序开始了!" ); Scanner scanner = new Scanner (System.in); System.out.println("请输入一个数字:" ); try { for (int num = scanner.nextInt(); num > 0 ; num--) { System.out.println(num); Thread.sleep(1000 ); } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("程序结束了!" ); } } **SleepDemo2** ```JAVA package thread; public class SleepDemo2 { public static void main (String[] args) { Thread lin = new Thread (){ @Override public void run () { System.out.println("林:刚美完容~睡一会吧~~" ); try { Thread.sleep(500000000 ); System.out.println("林:睡醒了!" ); } catch (InterruptedException e) { System.out.println("林:干嘛呢!干嘛呢!都破了相了!" ); } } }; Thread huang = new Thread (){ @Override public void run () { System.out.println("黄:大锤80,小锤40,开始砸墙!" ); for (int i = 0 ; i < 5 ; i++) { System.out.println("黄:80!" ); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("咣当!" ); System.out.println("黄:大哥!搞定!" ); lin.interrupt(); } }; lin.start(); huang.start(); } }
DaemonThreadDemo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package thread; public class DaemonThreadDemo { public static void main (String[] args) { Thread rose = new Thread (){ @Override public void run () { for (int i = 0 ; i < 5 ; i++) { System.out.println("rose:let me go!" ); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("rose:aaaaaaaaaa~~~" ); System.out.println("扑通!!" ); } }; Thread jack = new Thread (){ @Override public void run () { while (true ){ System.out.println("jack:you jump! i jump!" ); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } } }; rose.start(); jack.setDaemon(true ); jack.start(); while (true ); } }
线程的同步和异步
JoinDemo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package thread; public class JoinDemo { static boolean isFinish = false ; public static void main (String[] args) { Thread download = new Thread () { @Override public void run () { System.out.println("down:开始下载图片..." ); for (int i = 0 ; i <= 100 ; i++) { System.out.println("down:" + i + "%" ); try { Thread.sleep(50 ); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("down:图片下载完毕!!!" ); isFinish = true ; } }; Thread show = new Thread (){ @Override public void run () { System.out.println("show:开始显示文字..." ); try { Thread.sleep(2000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("show:显示文字完毕!!!" ); try { download.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("show:开始显示图片..." ); if (!isFinish){ throw new RuntimeException ("图片加载失败!!!" ); } System.out.println("show:图片显示完毕!!!" ); } }; download.start(); show.start(); } }
同步锁
同步方法
同步方法概述
除了可以将需要的代码设置成同步代码块以外,也可以使用synchronized关键字将一个方法修饰成同步方法,它能实现和同步代码块同样的功能。
具体语法格式如下:
1 2 3 权限修饰符 synchronized 返回值类型/void 方法名([参数1 ,...]){ 需要同步的代码; }
被synchronized修饰的方法在某一时刻只允许一个线程访问,访问该方法的其他线程都会发生阻塞,直到当前线程访问完毕后,其他线程才有机会执行该方法。
需要注意的是,同步方法的锁是当前调用该方法的对象,也就是this指向的对象。
同步方法的使用
下面使用同步方法解决上面的多线程售票程序中的线程安全问题
需要注意的是:
将有可能发生线程安全问题的方法使用synchronized修饰,同一时间只允许一个线程进入同步方法中
synchronized方法的锁对象是当前调用该方法的对象,也就是this指向的对象。
如果当前方法是非静态方法,this表示的是调用当前方法的对象
如果当前方法的静态方法,this表示的是当前类。
示例1: SyncDemo1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package thread; public class SyncDemo1 { public static void main (String[] args) { Table table = new Table (); Thread t1 = new Thread ("小红" ){ @Override public void run () { while (true ){ int bean = table.getBean(); Thread.yield (); System.out.println(getName()+"抢了一颗豆子,还剩:" +bean); } } }; Thread t2 = new Thread ("小蓝" ){ @Override public void run () { while (true ){ int bean = table.getBean(); Thread.yield (); System.out.println(getName()+"抢了一颗豆子,还剩:" +bean); } } }; t1.start(); t2.start(); } } class Table { private int beans = 20 ; public synchronized int getBean () { if (beans <= 0 ){ throw new RuntimeException ("没有豆子了!!!" ); } Thread.yield (); return beans--; } }
同步代码块
同步代码块概述
同步是指多个操作在同一个时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行。
Java为线程的同步操作提供了synchronized关键字,使用该关键字修饰的代码块被称为同步代码块。
其语法格式如下:
1 2 3 synchronized ( 同步对象 ){ 需要同步的代码; }
注意: 在使用同步代码块时必须指定一个需要同步的对象,也称为锁对象,这里的锁对象可以是任意对象。但多个线程必须使用同一个锁对象。
同步代码块的使用
下面使用同步代码块解决上面的多线程售票程序中的线程安全问题
需要注意的是:
将有可能发生线程安全问题的代码包含在同步代码块中,同一时间只允许一个线程进入同步代码块中
synchronized代码块中的锁对象可以是任意对象,但必须只能是一个锁。
若使用this作为锁对象,需保证多个线程执行时,this指向的是同一个对象
**示例1: **
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package thread; public class SyncDemo2 { public static void main (String[] args) { Shop shop1 = new Shop (); Shop shop2 = new Shop (); Thread t1 = new Thread ("康荐文" ){ public void run () { shop1.buy(); } }; Thread t2 = new Thread ("薛亚杰" ){ public void run () { shop2.buy(); } }; t1.start(); t2.start(); } } class Shop { public void buy () { Thread t = Thread.currentThread(); try { System.out.println(t.getName()+":正在挑衣服..." ); Thread.sleep(5000 ); synchronized (this ){ System.out.println(t.getName()+":正在试衣服..." ); Thread.sleep(5000 ); } System.out.println(t.getName()+":正在结账离开..." ); } catch (InterruptedException e) { } } }
静态同步方法
示例1: SyncDemo3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package thread; public class SyncDemo3 { public static void main (String[] args) { Boo b1 = new Boo (); Boo b2 = new Boo (); Thread t1 = new Thread (){ @Override public void run () { b1.doSome(); } }; Thread t2 = new Thread (){ @Override public void run () { b2.doSome(); } }; t1.start(); t2.start(); } } class Boo { public static void doSome () { synchronized (Boo.class){ Thread t = Thread.currentThread(); System.out.println(t.getName()+":正在执行doSome方法..." ); try { Thread.sleep(5000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(t.getName()+":执行doSome方法完毕!!!" ); } } }
互斥锁
示例:SyncDemo4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package thread; public class SyncDemo4 { public static void main (String[] args) { Foo foo = new Foo (); Thread t1 = new Thread (){ @Override public void run () { foo.methodA(); } }; Thread t2 = new Thread (){ @Override public void run () { foo.methodB(); } }; t1.start(); t2.start(); } } class Foo { public synchronized void methodA () { Thread t = Thread.currentThread(); System.out.println(t.getName()+":正在执行A方法..." ); try { Thread.sleep(5000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(t.getName()+":执行A方法完毕..." ); } public synchronized void methodB () { Thread t = Thread.currentThread(); System.out.println(t.getName()+":正在执行B方法..." ); try { Thread.sleep(5000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(t.getName()+":执行B方法完毕..." ); } }
死锁
示例:DeadLockDemo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 package thread; public class DeadLockDemo { static Object chopsticks = new Object (); static Object spoon = new Object (); public static void main (String[] args) { Thread bf = new Thread () { @Override public void run () { try { System.out.println("北方人准备吃饭..." ); synchronized (chopsticks) { System.out.println("北方人拿筷子,开始吃饭" ); Thread.sleep(5000 ); System.out.println("北方人吃完了饭,去拿勺" ); synchronized (spoon){ System.out.println("北方人拿勺,开始喝汤" ); Thread.sleep(5000 ); System.out.println("北方人喝完了汤" ); } System.out.println("北方人放下勺" ); } System.out.println("北方人放下筷子,吃饭完毕!" ); } catch (InterruptedException e) { e.printStackTrace(); } } }; Thread nf = new Thread () { @Override public void run () { try { System.out.println("南方人准备吃饭..." ); synchronized (spoon) { System.out.println("南方人拿勺,开始喝汤" ); Thread.sleep(5000 ); System.out.println("南方人喝完了汤,去拿筷子" ); synchronized (chopsticks){ System.out.println("南方人拿起筷子,开始吃饭" ); Thread.sleep(5000 ); System.out.println("南方人吃完了饭" ); } System.out.println("南方人放下筷子" ); } System.out.println("南方人放下勺,吃饭完毕!" ); } catch (InterruptedException e) { e.printStackTrace(); } } }; nf.start(); bf.start(); } } **DeadLockDemo1** ```JAVA package thread; public class DeadLockDemo1 { static Object chopsticks = new Object (); static Object spoon = new Object (); public static void main (String[] args) { Thread bf = new Thread () { @Override public void run () { try { System.out.println("北方人准备吃饭..." ); synchronized (chopsticks) { System.out.println("北方人拿筷子,开始吃饭" ); Thread.sleep(5000 ); System.out.println("北方人吃完了饭" ); } System.out.println("北方人放下筷子,去拿勺" ); synchronized (spoon) { System.out.println("北方人拿勺,开始喝汤" ); Thread.sleep(5000 ); System.out.println("北方人喝完了汤" ); } System.out.println("北方人放下勺" ); System.out.println("吃饭完毕!" ); } catch (InterruptedException e) { e.printStackTrace(); } } }; Thread nf = new Thread () { @Override public void run () { try { System.out.println("南方人准备吃饭..." ); synchronized (spoon) { System.out.println("南方人拿勺,开始喝汤" ); Thread.sleep(5000 ); System.out.println("南方人喝完了汤" ); } System.out.println("南方人放下勺,去拿筷子" ); synchronized (chopsticks) { System.out.println("南方人拿起筷子,开始吃饭" ); Thread.sleep(5000 ); System.out.println("南方人吃完了饭" ); } System.out.println("南方人放下筷子" ); System.out.println("吃饭完毕!" ); } catch (InterruptedException e) { e.printStackTrace(); } } }; nf.start(); bf.start(); } }