实现 Runnable 接口
- public class TestThread2 {
- public static void main(String [] args){
- Windows Windows=new Windows();
- Thread thread1=new Thread(Windows,"窗口一");
- Thread thread2=new Thread(Windows,"窗口二");
- Thread thread3=new Thread(Windows,"窗口三");
- thread1.start();
- thread2.start();
- thread3.start();
- }
- }
- class Windows implements Runnable{
- int ticket=50;
- @Override
- public void run(){
- while (true){
- if(ticket> 0){
- try {
- Thread.currentThread().sleep(100);// 模拟卖票需要一定的时间
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+"售票, 票号为:"+ticket--);
- }else {
- break;
- }
- }
- }
- }
运行结果:
窗口二售票, 票号为: 13
窗口三售票, 票号为: 12
窗口一售票, 票号为: 11
窗口二售票, 票号为: 10
窗口一售票, 票号为: 10
窗口三售票, 票号为: 10
窗口三售票, 票号为: 9
窗口一售票, 票号为: 8
窗口二售票, 票号为: 7
窗口三售票, 票号为: 6
窗口一售票, 票号为: 5
窗口二售票, 票号为: 4
窗口三售票, 票号为: 3
窗口一售票, 票号为: 2
窗口二售票, 票号为: 1
窗口三售票, 票号为: 0
窗口一售票, 票号为:-1
结果分析: 这里出现了票数为 0 和负数还有重票的情况, 这在现实生活中肯定是不存在的, 那么为什么会出现这样的情况呢?
当票号为 10 时: A 线程, B 线程, C 线程同时进入到 if(ticket> 0) 的代码块中, A 线程已经执行了打印输出语句, 但是还没有做 ticket-- 操作;
这时 B 线程就开始执行了打印操作, 那么就会出现两个线程打印票数一样, 即卖的是同一张票
当票号为 1 时: A 线程, B 线程, C 线程同时进入到 if(ticket> 0) 的代码块中, A 线程执行了打印语句, 并且已经做完了 ticket-- 操作, 则此时 ticket=0;
B 线程再打印时就出现了 0 的情况, 同理 C 线程打印就会出现 - 1 的情况.
解决办法: 即我们不能同时让超过两个以上的线程进入到 if(ticket> 0) 的代码块中, 不然就会出现上述的错误. 必须让一个线程操作共享数据完毕以后, 其他线程才有机会参与共享数据的操作. 我们可以通过以下两个办法来解决:
1, 使用 同步代码块
2, 使用 同步方法
使用 同步代码块
- synchronized(同步监视器){
- // 需要被同步的代码块 (即为操作共享数据的代码)
- }
同步监视器: 由任意一个类的对象来充当, 哪个线程获取此监视器, 谁就执行大括号里被同步的代码. 俗称: 锁
要求: 1, 所有的线程必须公用同一把锁! 不能相对于线程是变化的对象;
2, 并且只需锁住操作共享数据的代码, 锁多了或少了都不行;
实例:
1, 实现的方式
- public class TestWindow {
- public static void main(String [] args){
- Window1 Windows=new Window1();
- Thread thread1=new Thread(Windows,"窗口一");
- Thread thread2=new Thread(Windows,"窗口二");
- Thread thread3=new Thread(Windows,"窗口三");
- thread1.start();
- thread2.start();
- thread3.start();
- }
- }
- class Window1 implements Runnable{
- int ticket=100;// 共享数据
- @Override
- public void run(){
- while (true){
- synchronized (this){//this 表示当前对象, 此时表示创建的 Windows
- if(ticket> 0){
- try {
- // 模拟卖票需要一定的时间
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+"售票, 票号为:"+ticket--);
- }
- }
- }
- }
- }
注意: 在实现的方式中, 考虑同步的话, 可以使用 this 充当锁, 但在继承的方式中, 会创建多个对象, 慎用 this
2, 继承的方式
- public class TestWindow1 {
- public static void main(String [] args){
- Window2 window1=new Window2();
- Window2 window2=new Window2();
- window1.start();
- window2.start();
- }
- }
- class Window2 extends Thread{
- static int ticket=100;// 共享数据; 注意声明为 static, 表示几个窗口共享
- static Object object=new Object();// 用 static 可以表示唯一
- @Override
- public void run(){
- while (true){
- //synchronized (this){//this 表示当前对象, 此时表示创建的 window1 和 window2
- synchronized (object){// 锁必须是唯一, 不能每个线程都使用自己的一把锁
- if(ticket> 0){
- try {
- Thread.sleep(200);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+"售票, 票号为:"+ticket--);
- }
- }
- }
- }
- }
注意: 1, 继承的方式会创建多个实例, 所以共享资源需要用 static 来修饰, 表示共享
2, 继承的方式会创建多个实例, 所以 this 表示不同的实例对象, 这里表示 widow1 和 window2, 所以不能使用 this 当锁, 此时可以定义一个 static 修饰的对象当锁
使用 同步方法
语法: 即用 synchronized 关键字修饰方法
将操作共享数据的方法声明为 synchronized. 即此方法为同步方法, 能够保证当其中一个线程执行此方法时, 其他线程再外等待直至此线程执行完此方法. 注意: 同步方法的锁: this
实例:
1, 实现的方式
- public class TestWindow2 {
- public static void main(String [] args){
- Window3 Windows=new Window3();
- Thread thread1=new Thread(Windows,"窗口一");
- Thread thread2=new Thread(Windows,"窗口二");
- Thread thread3=new Thread(Windows,"窗口三");
- thread1.start();
- thread2.start();
- thread3.start();
- }
- }
- class Window3 implements Runnable{
- int ticket=100;// 共享数据
- @Override
- public void run(){
- while (true){
- show();
- }
- }
- public synchronized void show(){//this 充当锁, 此时表示创建的 Windows;
- // 如果用继承的方式, 使用同步方法, 这里表示创建的 window1 和 window2, 继承的方式不要使用同步方法
- if(ticket> 0){
- try {
- Thread.currentThread().sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+"售票, 票号为:"+ticket--);
- }
- }
- }
注意: 1,synchronized 的锁为 this, 这里表示创建的对象实例 Windows;
2, 继承的时候 t this 表示创建的 window1 和 window2, 继承的方式不要使用同步方法.
来源: https://www.cnblogs.com/java-chen-hao/p/9896254.html