一、银行取款引出的问题

模拟银行取钱的例子:

public class ThreadDemo06 {
public static void main(String[] args) {
Bank bank = new Bank();
Runnable runnable = new MoneyThread(bank);
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable); thread1.start();
thread2.start();
}
} class Bank {
private int money = 1000; public int get(int number) {
if (number < 0) {
return -1;
} else if (number > money) {
return -2;
} else if (money < 0) {
return -3;
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money -= number; System.err.println("还剩:" + money);
return number;
}
}
} class MoneyThread implements Runnable {
private Bank bank; public MoneyThread(Bank bank) {
this.bank = bank;
} @Override
public void run() {
bank.get(800);
}
}

运行可能的结果:

还剩:200
还剩:-600

造成此类问题的根本原因在于,多个线程在操作共享的数据或者操作共享数据的线程代码有多行,当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程的安全问题的产生

二、问题的解决方案

在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程就不能再使用那个资源,除非被解锁。同一时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行。

将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码时其他线程是不可以参与运算的,必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算;

保证取钱和修改余额同时完成:

1)使用同步代码块,synchronized(obj){},还需要一个同步监听对象;

2)使用同步方法,使用synchronized去修饰需要同步的方法;

方法一:同步方法

在Java中通过synchronized关键字来完成对对象的加锁。

上述代码加锁的解决方案如下:

class Bank {
private int money = 1000; public synchronized int get(int number) {
if (number < 0) {
return -1;
} else if (number > money) {
return -2;
} else if (money < 0) {
return -3;
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money -= number; System.err.println("还剩:" + money);
return number;
}
}
}

方法二:同步代码块

synchronized块写法:

synchronized(object){

} //表示线程在执行的时候会对object对象上锁

这里的object可以是任意对象,但必须保证多个线程持有的这个object是同一个对象;

synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;synchronized块则是一种细粒度的并发控制,只有将块中的代码同步,位于方法内、synchronized块之外的代码是可以被多个线程同时访问到的。

三、线程同步的关键知识点

1)Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法再去访问该对象的synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他线程才可能再去访问该对象的其他synchronized方法

public class ThreadDemo07 {
public static void main(String[] args) {
Example example = new Example();
Runnable runnable = new TheThread(example); //同一对象 Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable); thread1.start();
thread2.start();
}
} class Example {
public synchronized void execute() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute :" + i);
}
}
} class TheThread implements Runnable {
private Example example;
public TheThread(Example example) {
this.example = example;
} public void run() {
example.execute();
}
}

上述代码的执行:由于是对同一对象产生的线程,当两个不同线程进行访问的时候,谁先进入synchronized方法就将该example对象上锁了,其他线程就没有办法再进入该对象的任何同步方法了,所以只有当一个线程执行完毕或者抛出异常后第二个线程才能进行访问。

public class ThreadDemo08 {
public static void main(String[] args) {
Runnable runnable = new TheThread(new Example());
Runnable runnable2 = new TheThread(new Example()); Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable2); thread1.start();
thread2.start();
}
} class Example {
public synchronized void execute() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute :" + i);
}
}
} class TheThread implements Runnable {
private Example example;
public TheThread(Example example) {
this.example = example;
} public void run() {
example.execute();
}
}

上述代码的执行:由于是两个线程对两个不同对象进行访问操作。那么这2个线程就没有任何关联,各自访问各自的对象,互不干扰。

public class ThreadDemo09 {
public static void main(String[] args) {
Example example = new Example();
Runnable runnable = new TheThread(example);//同一对象 Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable); thread1.start();
thread2.start();
}
} class Example {
public synchronized void execute() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute :" + i);
}
} public synchronized void execute2() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute2 :" + i);
}
}
} class TheThread implements Runnable {
private Example example;
public TheThread(Example example) {
this.example = example;
} public void run() {
example.execute();
}
} class TheThread2 implements Runnable {
private Example example;
public TheThread2(Example example) {
this.example = example;
} public void run() {
example.execute2();
}
}

上述代码的执行结果:是由同一对象生成的两个不同的线程,当两个不同的线程访问同一对象不同的synchronized方法时,谁先进入第一个synchronized方法,那么该线程就将该对象上锁了,其他线程是没有办法再对该对象的任何synchronized方法进行访问。

public class ThreadDemo10 {
public static void main(String[] args) {
Example example = new Example();
Runnable runnable = new TheThread(example); Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable); thread1.start();
thread2.start();
}
} class Example {
public synchronized static void execute() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute :" + i);
}
} public synchronized static void execute2() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute2 :" + i);
}
}
}

上述代码的执行结果:由于静态方法是属于类级别的,当一个方法锁住后,只有等第一个线程执行完毕以后第二个线程才能进入。

2)synchronized方法使用了static关键字进行修饰,表示将该对象的Class对象加锁

public class ThreadDemo11 {
public static void main(String[] args) {
Runnable runnable = new TheThread(new Example());
Runnable runnable2 = new TheThread(new Example()); Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable2); thread1.start();
thread2.start();
}
} class Example {
public synchronized static void execute() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute :" + i);
}
} public synchronized static void execute2() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute2 :" + i);
}
}
}

上述代码的执行结果:虽然是针对两个不同对象生成的不同的线程,但是由于synchronized方法使用了static关键字进行修饰,表示将该对象的Class对象加锁。所以只有等一个线程执行完毕后,其他线程才能进入访问。

3) 如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的

4)如果某个synchronized方法是static的,那么当线程访问该方法时,它的锁并不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象,因为Java中无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static, synchronized方法时,他们的执行顺序也是有顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行

public class ThreadDemo02 {
public static void main(String[] args) {
C c = new C();
Thread t1 = new T1(c);
Thread t2 = new T2(c);
t1.start(); try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
} class C {
public synchronized static void hello() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello");
}
public synchronized void world() {
System.out.println("world");
}
} class T1 extends Thread {
private C c;
public T1(C c) {
this.c = c;
}
@Override
public void run() {
c.hello();
}
} class T2 extends Thread {
private C c;
public T2(C c) {
this.c = c;
}
@Override
public void run() {
c.world();
}
}

执行结果:先执行world,然后才输出hello,原因是static是给当前对象的Class对象上锁,而没有static的是给当前对象上锁,两把锁锁的对象不同,所以并没有影响。

四、线程同步总结

synchronized修饰方法

1)非静态方法:默认的同步监听器对象是this;

2))静态方法:默认的同步监听器对象是该方法所在类的Class对象。

若线程是实现方式

1)同步代码块:同步监听对象可以选this、这个方法所在类的Class对象、任一不变对象;

2)同步方法:此时可以使用synchronized直接修饰run方法,因为同步监听器是this。

若线程是继承方式

1)同步代码块:同步监听器可以选用该方法所在类的Class对象、任一不变对象;

2)同步方法:此时不能使用synchronized直接修饰run方法;

3)总结:只要是继承方式,不论是同步代码块还是同步方法均不能使用this。

同步的利弊

1)好处:解决了线程的安全问题;

2)弊端:相对降低了效率,因为同步外的线程的都会判断同步锁;

3)前提:同步中必须有多个线程并使用同一个锁。

jdk线程的同步问题的更多相关文章

  1. jdk线程池主要原理

    本文转自:http://blog.csdn.net/linchengzhi/article/details/7567397 正常创建一个线程的时候,我们是这样的:new thread(Runnable ...

  2. 牛客网Java刷题知识点之为什么HashMap不支持线程的同步,不是线程安全的?如何实现HashMap的同步?

    不多说,直接上干货! 这篇我是从整体出发去写的. 牛客网Java刷题知识点之Java 集合框架的构成.集合框架中的迭代器Iterator.集合框架中的集合接口Collection(List和Set). ...

  3. java之线程(线程的创建方式、java中的Thread类、线程的同步、线程的生命周期、线程之间的通信)

    CPU:10核 主频100MHz 1核  主频    3GHz 那么哪一个CPU比较好呢? CPU核不是越多越好吗?并不一定.主频用于衡量GPU处理速度的快慢,举个例子10头牛运送货物快还是1架飞机运 ...

  4. jdk线程池ThreadPoolExecutor工作原理解析(自己动手实现线程池)(一)

    jdk线程池ThreadPoolExecutor工作原理解析(自己动手实现线程池)(一) 线程池介绍 在日常开发中经常会遇到需要使用其它线程将大量任务异步处理的场景(异步化以及提升系统的吞吐量),而在 ...

  5. Java线程:线程的同步-同步方法

    Java线程:线程的同步-同步方法   线程的同步是保证多线程安全访问竞争资源的一种手段. 线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源.什么时候需要考虑同步,怎么同步等等问 ...

  6. Java线程:线程的同步与锁

    一.同步问题提出 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 例如:两个线程ThreadA.ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据. public ...

  7. java 线程数据同步

    java 线程数据同步 由买票实例 //java线程实例 //线程数据同步 //卖票问题 //避免重复卖票 //线程 class xc1 implements Runnable{ //定义为静态,可以 ...

  8. Java多线程-线程的同步与锁

    一.同步问题提出 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏.例如:两个线程ThreadA.ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据. package ...

  9. C++ 11 线程的同步与互斥

    这次写的线程的同步与互斥,不依赖于任何系统,完全使用了C++11标准的新特性来写的,就连线程函数都用了C++11标准的lambda表达式. /* * thread_test.cpp * * Copyr ...

随机推荐

  1. 每日学习心得:CustomValidator验证控件验证用户输入的字符长度、Linq 多字段分组统计、ASP.NET后台弹出confirm对话框,然后点击确定,执行一段代码

    2013-9-15 1.    CustomValidator验证控件验证用户输入的字符长度 在实际的开发中通常会遇到验证用户输入的字符长度的问题,通常的情况下,可以写一个js的脚本或者函数,在ASP ...

  2. JS URL 使用base64加密与解密

    JS编码方式: <script type="text/javascript"> document.write(encodeURI("http://www.w3 ...

  3. 五大Android布局方式浅析

    Android布局是应用界面开发的重要一环,在Android中,共有五种布局方式,分别是:FrameLayout(框架布局),LinearLayout (线性布局),AbsoluteLayout(绝对 ...

  4. [svn]svn merge

    转:http://blog.csdn.net/keda8997110/article/details/21813035 Step by Step 完成merge 目录: Branch的必要性 1.本地 ...

  5. 单源最短路径——dijkstra算法

    dijkstra算法与prim算法的区别   1.先说说prim算法的思想: 众所周知,prim算法是一个最小生成树算法,它运用的是贪心原理(在这里不再证明),设置两个点集合,一个集合为要求的生成树的 ...

  6. 访问修饰符private

    private(C# 参考) private 关键字是一个成员访问修饰符. 私有访问是允许的最低访问级别. 私有成员只有在声明它们的类和结构体中才是可访问的,如下例所示: class Employee ...

  7. [JS]Javascript对象与JSON的互转

    var obj = JSON.parse(json); //由JSON字符串转换为JSON对象 var json=JSON.stringify(obj); //将JSON对象转化为JSON字符 //此 ...

  8. 使用Spring的命名空间p装配属性-摘自《Spring实战(第3版)》

    使用<property>元素为Bean的属性装配值和引用并不太复杂.尽管如此,Spring的命名空间p提供了另一种Bean属性的装配方式,该方式不需要配置如此多的尖括号. 命名空间p的sc ...

  9. Form_Form树形结构HTree的介绍(案例)

    2013-02-09 Created By BaoXinjian

  10. BEvent_客制化Event Agent通道(案例)(待整理)

    2014-09-09 Created By BaoXinjian