一、银行取款引出的问题

模拟银行取钱的例子:

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. CSS3中样式顺序

    .box{ /*1*/ background: yellow; /*2*/ background: radial-gradient(ellise, yellow, red); } 就以上样式1和2的顺 ...

  2. 开始学习IOS

    最好的学习方式就是动手. 对于有编程经验的程序员来说,学习另外一门技术最好的方式就是coding,虽然基础知识和IDE都不熟悉,但是在写代码的过程中,不断的解决问题,不断查找资料,不断感悟代码,一切都 ...

  3. golang的ssh例子

    package main import ( "github.com/dynport/gossh" "log" ) func MakeLogger(prefix ...

  4. [系统开发] Squid 认证系统

    一.用途 用过 Squid 的用户认证模块的同事一定知道,它有个很麻烦的问题:每过一段时间就会跳出一个重新输入密码的窗口,用户不胜其烦,我查了网上的各种配置资料,始终没有找到一个圆满的解决方法,所以编 ...

  5. HDMI的CEC是如何控制外围互联设备的

    1. HDMI CEC算是一个相当庞大的系统,想了解还要从HDMI接口信号啊.物理地址啊.逻辑地址啊等等HDMI基础的东西说起. 2. 不过可以简单的这么理解,在HDMI CEC最小系统里,所有通过H ...

  6. 23. Merge k Sorted Lists

    Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity. = ...

  7. POJ题目分类

    POJ上的一些水题(可用来练手和增加自信) (poj3299,poj2159,poj2739,poj1083,poj2262,poj1503,poj3006,poj2255,poj3094)初期: 一 ...

  8. 黄聪:wordpress源码解析-目录结构-文件调用关系(转)

    Wordpress是一个单入口的文件,所有的前端处理都必须经过index.php,这是通过修改web服务器的rewrite规则来实现的.这种做法的好处是显而易见的,这样URL更好看,不必为每一个url ...

  9. (C/C++ interview) Static 详解

    C Static http://stackoverflow.com/questions/572547/what-does-static-mean-in-a-c-program Static could ...

  10. SDS查看部署在集成TOMCAT服务器中的项目目录结构