Java基础教程——线程同步

线程同步

synchronized:同步的

例:取钱

不做线程同步的场合,假设骗子和户主同时取钱,可能出现这种情况:

  • 【骗子】取款2000:账户余额1000
  • 【户主】取款2000:账户余额1000
  • 结果是社会财富增加1000,银行不开心。

代码如下所示:

// 账户类
class Account {
    private int accountBalance = 2000;
    public void withdraw(String userName, int amount) {
        System.out.println(userName + "===in===");
        if (accountBalance >= amount) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            accountBalance -= amount;// 取钱
            System.out.println(userName + "取款" + amount + ",余额:" + accountBalance);
        } else {
            System.out.println(userName + "企图取款" + amount + ",但余额只有:" + accountBalance);
        }
        System.out.println(userName + "===out===");
    }
}
class MyThread extends Thread {
    @Override
    public void run() {
        取钱Demo.act.withdraw(getName(), 1000);
    }
}
public class 取钱Demo {
    static Account act = new Account();// 账户就一份
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.setName("户主");
        t2.setName("骗子");
        t2.start();
        t1.start();
    }
}


想要银行开心,就需要对线程进行同步处理,避免出现重复取款的情况。


线程同步

方法一:锁对象

    public void withdraw(String userName, int amount) {
        synchronized (this) {
          ……
        }
    }

如果是静态方法,没有this,则是锁住【类名.class】

    public static void withdraw(String userName, int amount) {
        synchronized (Account.class) {
          ……
        }
    }

方法二:锁方法

当方法被调用时,调用线程必须获得当前对象的锁,否则将等待下去。
方法结束后,锁会被释放。

public synchronized void withdraw(String userName, int amount) {...}

方法三:ReentrantLock重入锁

ReentrantLock是java.util.concurrent.locks.Lock接口的一个实现类。(reentrant:[r?‘entr?nt]再进去)

一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大(可以中断、定时)。

API文档上建议的用法:

建议总是 立即实践,使用 lock 块来调用 try,在之前/之后的构造中,最典型的代码如下: 

 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

应用示例:

import java.util.concurrent.locks.ReentrantLock;
// 账户类
class Account {
    private int accountBalance = 2000;
    private ReentrantLock lock = new ReentrantLock();
    public void withdraw(String userName, int amount) {
        synchronized (Account.class) {
            lock.lock();
            try {
                System.out.println(userName + "===in===");
                if (accountBalance >= amount) {
                    Thread.sleep(500);
                    accountBalance -= amount;// 取钱
                    System.out.println(userName + "取款" + amount + ",余额:" + accountBalance);
                } else {
                    System.out.println(userName + "企图取款" + amount + ",但余额只有:" + accountBalance);
                }
                System.out.println(userName + "===out===");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

练习:买票

(未做线程同步,请实现线程同步)

import java.util.Random;
public class 卖票 {
    public static void main(String[] args) {
        // 一个Runnable实例对象
        SellTicket st = new SellTicket();
        // 创建三个线程对象
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");
        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}
class SellTicket implements Runnable {
    // 定义票数
    private int tickets = 100;
    private void sell() {
        if (tickets > 0) {
            // 模拟售票过程
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String name = Thread.currentThread().getName();
            System.out.println(name + "正在出售第" + (tickets--) + "张票");
        }
    }
    @Override
    public void run() {
        while (tickets > 0) {
            sell();
            // 模拟空闲过程
            try {
                Thread.sleep(new Random().nextInt(11) * 100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

原文地址:https://www.cnblogs.com/tigerlion/p/11179366.html

时间: 2024-08-01 18:13:55

Java基础教程——线程同步的相关文章

黑马程序员--Java基础--多线程|线程同步

--Java培训.Android培训.iOS培训..Net培训 期待与您共同交流!-- 多线程基础.线程同步 1. 多线程基础 1.1. 进程和线程 1.1.1. 什么是进程 所谓进程(process)就是一块包含了某些资源的内存区域.操作系统利用进程把它的工作划分为一些功能单元.进程中所包含的一个或多个执行单元称为线程(thread).进程还拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问.线程只能归属于一个进程并且它只能访问该进程所拥有的资源.当操作系统创建一个进程后,该进程会自动

Java基础教程——线程池

启动新线程,需要和操作系统进行交互,成本比较高. 使用线程池可以提高性能-- 线程池会提前创建大量的空闲线程,随时待命执行线程任务.在执行完了一个任务之后,线程会回到空闲状态,等待执行下一个任务.(这个任务,就是Runnable的run()方法,或Callable的call()方法). Java 5之前需要手动实现线程池,Java 5之后直接支持线程池. Java.util.concurrent. Executors:用于创建线程池 |--|--Executors.newFixedThreadP

Java基础教程——线程状态

线程状态 JAVA定义了6种线程状态: Thread.State public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; } 分别是: 新建状态·NEW 运行状态·RUNNABLE 阻塞状态·BLOCKED 无限等待状态·WAITING 休眠状态·TIMED_WAITING 死亡状态·TERMINATED 线程不可能一直运行,除非瞬间执行结束. 为了给其他线程执行的机会,正在执行的线程会被中断

java基础:线程同步

一:先看程序 看输出结果: Thread-0,你是第2个使用Timer的线程 Thread-1,你是第2个使用Timer的线程 结果明显有问题: 这是因为两个线程访问的是同一个Timer对象,其中一个线程调用Timer的add方法时,num++,这个时候,另外一个线程也有可能调用add方法,num又++,然后线程1输出,线程2输出,输出的是同一个num = 2 二:解决办法 add方法上加上synchronized关键字:public synchronized void add(String n

Java基础教程:多线程基础(2)——线程间的通信

Java基础教程:多线程基础(2)--线程间的通信 使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时还会使程序员对各线程任务在处理的过程中进行有效的把控与监督. 线程间的通信 思维导图 等待中 等待/通知机制 不使用等待/通知机制 我们可以使用使用sleep()与 whle(true) 死循环来实现多个线程间的通信. 虽然两个线程实现了通信,但是线程B必须不断的通过while语句轮训机制来检测某一个条件,这样会浪费CPU资源. 如果轮询间隔较小,更浪费时间间隔.如果轮训

Java基础教程:HashTable与HashMap比较

Java基础教程:HashTable与HashMap比较 1.  关于HashMap的一些说法: a)  HashMap实际上是一个"链表散列"的数据结构,即数组和链表的结合体.HashMap的底层结构是一个数组,数组中的每一项是一条链表. b)  HashMap的实例有俩个参数影响其性能: "初始容量" 和 装填因子. c)  HashMap实现不同步,线程不安全.  HashTable线程安全 d)  HashMap中的key-value都是存储在Entry中的

Java基础教程:内部类

Java基础教程:内部类 内部类 内部类,是指在一个类的内部定义的类.就像下面这样: public class EnclosingClass {   . . .   public class NestedClass {   . . .     } } 内部类拥有访问外部类所有元素(包括private.static)的访问权.当某个外部类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用.然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员. 内部类

Java基础教程 - 组合

1. 什么是组合? 如果一个类的对象和另一个类满足"has-a"关系的话,我们就可以在一个类中,把另一个类的对象作为其对象成员. 什么是"has-a"关系,举个例子:现在有一个类LapTop.class,还有一个是Moniter.class.好显然,Laptop "has-a" Moniter,也就是说,他们是满足"has-a"关系的.这时候,我们就可以把Moniter作为Laptop的一个数据成员. class Laptop

(CZ深入浅出Java基础)线程笔记

一.线程的引入 1.多线程概述 1.1.进程 a.正在运行的程序,是系统进行资源分类和调用的独立单位. b.每个进程都有它自己的内存空间和系统资源. 1.2.线程 a.是进程中的单个顺序控制流,是一条执行路径. b.一个进程如果只有一条路径,则称为单线程程序. c.一个进程如果有多条执行路径,则称为多线程程序. 1.3.小结 线程多的进程抢到CPU执行权的概率大,但是仍具有随机性. 2.Java程序运行原理 2.1.Java运行 Java命令会启动Java虚拟机,启动JVM,等于启动了一个应用程