一、银行取款引出的问题

模拟银行取钱的例子:

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. for循环内 执行$ajax(){}

    真是郁闷,在for 循环里添加了ajax异步传输之后,for循环是单线程处理,就是里面执行的是ajax,也不异步处理数据.而是执行完for循环的次数后,一起把ajax的数据处理掉. 解决办法.分开吧! ...

  2. ARM与x86之3--蝶变ARM

    http://blog.sina.com.cn/s/blog_6472c4cc0100lqr8.html 蝶变ARM 1929年开始的经济大萧条,改变了世界格局.前苏联的风景独好,使得相当多的人选择了 ...

  3. gcc杂谈

    1. -l选项自动给库文件名增加lib前缀和.a/.so后缀.所以如果你有一个lib叫做libusb.a,那么编译选项是-lusb.另一方面,如果你有一个文件叫做libusb.o(是目标文件而不是库文 ...

  4. Linux下编译OpenSSL

    编译环境 操作系统: Red Hat Enterprise Linux Server release 5.4 64-bit 编译工具: gcc (GCC) 4.1.2 20080704 (Red Ha ...

  5. 预定义宏__GNUC__和_MSC_VER

    一.预定义__GNUC__宏 1 __GNUC__ 是gcc编译器编译代码时预定义的一个宏.需要针对gcc编写代码时, 可以使用该宏进行条件编译. 2 __GNUC__ 的值表示gcc的版本.需要针对 ...

  6. POJ 3709 K-Anonymous Sequence

    题目大意:将一个升序的,有N个元素的序列,分组.要求每组的元素不少于K个,计算出组内各元素与最小元素的之差的和,将每组的这个值加起来,其和要最小. N<500000,K<N 分析: dp[ ...

  7. Haroopad 写 markdown文本

    很好用,推荐大家都来用. http://www.csdn.net/article/2014-05-05/2819623

  8. flash bulider 无法启动

    更新airsdk后无法启动,google下http://www.25kx.com/art/1826181有n种方法,怀疑是.metadata目录原因,可能是旧版的airsdk和新版的airsdk在.m ...

  9. Mysql游标的简明写法

    -- cursor 游标/*declare 声明; declare 游标名 cursor for select_statement;open 找开; open 游标名fetch 取值; fetch 游 ...

  10. C#使用ConditionalAttribute特性来实现代码调试

    转自:http://www.csharpwin.com/csharpspace/10729r8541.shtml #if/#endif条件编译常用来由同一份源代码生成不同的结果文件,最常见的有debu ...