1并发编程(1)
文章目录
1synchronized
1.1任何线程要执行下面的代码,必须先拿到o的锁
/**
* synchronized关键字
* 对某个对象加锁
* @author mashibing
*/
package yxxy.c_001;
public class T {
private int count = 10;
private Object o = new Object();
public void m() {
synchronized(o) { //任何线程要执行下面的代码,必须先拿到o的锁
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
o对象new了一个变量。在堆内存里边。即在堆内存new了一个对象。
执行synchronized里边的代码,首先要申请对象的锁。
当第二个线程去执行的时候,也要去申请o这把锁,发现o已经被第一个线程锁定了。第二个线程只能等着,等着这把锁释放了。
第一个线程执行完的时候,就会释放锁。第二个线程申请锁定就能锁定了。
发现这把锁锁定了,别的线程就不能在去锁定了,叫互斥锁。只要有一个线程拿到了,其他线程都拿不到。
1.2任何线程要执行下面的代码,必须先拿到this的锁
/**
* synchronized关键字
* 对某个对象加锁
* @author mashibing
*/
package yxxy.c_002;
public class T {
private int count = 10;
public void m() {
synchronized(this) { //任何线程要执行下面的代码,必须先拿到this的锁
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
synchronized锁定的是一个对象,并不是代码快
/**
* synchronized关键字
* 对某个对象加锁
* @author mashibing
*/
package yxxy.c_003;
public class T {
private int count = 10;
public synchronized void m() { //等同于在方法的代码执行时要synchronized(this)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
1.3synchronized 修饰静态
静态的属性不需要new出对象就可以访问,所以没有this。
写synchronized(this)是不可以的。
/**
* synchronized关键字
* 对某个对象加锁
* @author mashibing
*/
package yxxy.c_004;
public class T {
private static int count = 10;
public synchronized static void m() { //这里等同于synchronized(yxxy.c_004.T.class)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void mm() {
synchronized(T.class) { //考虑一下这里写synchronized(this)是否可以?
count --;
}
}
}
1.4 分析程序
/**
* 分析一下这个程序的输出
* @author mashibing
*/
package yxxy.c_005;
public class T implements Runnable {
private int count = 10;
public /*synchronized*/ void run() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
T t = new T();
for(int i=0; i<5; i++) {
new Thread(t, "THREAD" + i).start();
}
}
}
main方法中T t = new T(); 只 new了一个对象,好多线程共同访问一个对象。
栈内存有个t,指向堆内存的空间,new出来一个 T对象。T对象中有个count值,他的值是10
起了5个线程,每个线程访问的是同一个 T对象。则每个线程run方法里访问的都是一个count。
执行代码,看有什么问题?
线程重入问题,线程执行一半,另一个线程来来打断来。另一个线程来来,又打断了。
解决:
代码块加把锁。执行这两句代码的时候,不允许其他代码访问。那么这个值一定是正确的。
一个synchronized代码块相当于一个原子操作,原子就是不可分的。即执行这个代码块时是不能打断的,只有代码块执行完,其他线程才能继续执行相同代码
1.4 同步和非同步方法是否可以同时调用?
public class T {
public synchronized void m1() {
System.out.println(Thread.currentThread().getName() + " m1 start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m1 end");
}
public void m2() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m2 ");
}
public static void main(String[] args) {
T t = new T();
/*new Thread(()->t.m1(), "t1").start();
new Thread(()->t.m2(), "t2").start();*/
new Thread(t::m1, "t1").start();
new Thread(t::m2, "t2").start();
/*
new Thread(new Runnable() {
@Override
public void run() {
t.m1();
}
});
*/
}
}
同步和非同步方法是否可以同时调用?即
执行一个m1方法需要锁定对象,执行第二个方法m2不需要锁定当前对象。请问在执行m1的过程中,m2能否被执行?
可以
如果m1首先启动,假如5秒后m2能打印出来,则说明在m1执行过程之中
m2 是可以运行的。
原因:
只用synchronized 方法需要申请这个锁,其他方法是不需要这把锁的。所以可以执行
1.5 银行账户,对写进行加锁,对读不用加锁,这样行不行?
/**
* 对业务写方法加锁
* 对业务读方法不加锁
* 容易产生脏读问题(dirtyRead)
*/
package yxxy.c_008;
import java.util.concurrent.TimeUnit;
public class Account {
String name;
double balance;
public synchronized void set(String name, double balance) {
this.name = name;
//睡眠这两秒,意味这又其他业务执行,可能造成数据不一致。
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
public /*synchronized*/ double getBalance(String name) {
return this.balance;
}
public static void main(String[] args) {
Account a = new Account();
new Thread(()->a.set("zhangsan", 100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("zhangsan"));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("zhangsan"));
}
}
这个线程在执行过程之中,尽管在写的方法加了synchronized,锁定了这个对象,但是在锁定的过程之中,可能会被非锁定的方法去访问。
第一次访问的时候获取到了0 ,即balance的默认值,此时产生了脏读。
脏读为什么会产生?因为只对写进行了加锁,没有对读进行加锁。就读到了写还没有完成的数据。
解决: 在读的时候在锁
1.6 一个同步方法可不可以调用另外一个同步方法?一个线程已经拥有某个对象的锁,再次申请的时候是不是还可以得到该对象的锁?
/**
* 一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁.
* 也就是说synchronized获得的锁是可重入的
*/
package yxxy.c_009;
import java.util.concurrent.TimeUnit;
public class T {
synchronized void m1() {
System.out.println("m1 start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
m2();
}
synchronized void m2() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m2");
}
}
执行m1时,对T加了把锁,执行m1过程中调用了m2。调用m2过程中发现也需要加锁,而申请的锁,是自己已经持有的这把锁。同一线程是可以的,时锁上了两次。一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁.也就是说synchronized获得的锁是可重入的
1.7 子类调用父类的同步方法
/**
* 一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁.
* 也就是说synchronized获得的锁是可重入的
* 这里是继承中有可能发生的情形,子类调用父类的同步方法
* @author mashibing
*/
package yxxy.c_010;
import java.util.concurrent.TimeUnit;
public class T {
synchronized void m() {
System.out.println("m start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m end");
}
public static void main(String[] args) {
new TT().m();
}
}
class TT extends T {
@Override
synchronized void m() {
System.out.println("child m start");
super.m();
System.out.println("child m end");
}
}
1.8 程序在执行过程中,如果出现异常,默认情况锁会被释放
/**
* 程序在执行过程中,如果出现异常,默认情况锁会被释放
* 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。
* 比如,在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,
* 在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。
* 因此要非常小心的处理同步业务逻辑中的异常
*/
package yxxy.c_011;
import java.util.concurrent.TimeUnit;
public class T {
int count = 0;
synchronized void m() {
System.out.println(Thread.currentThread().getName() + " start");
while(true) {
count ++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count == 5) {
int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch,然后让循环继续
System.out.println(i);
}
}
}
public static void main(String[] args) {
T t = new T();
Runnable r = new Runnable() {
@Override
public void run() {
t.m();
}
};
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
好多线程访问一块数据,其中有一个线程持有锁正在运行,有两个属性,当改了一个属性时,抱了一个异常抛出,锁被释放了。另外的线程就进去了,读到的数据 是只改了一半的数据。
t1 发生异常,把锁释放了,t2 才能开始执行。
如果不想释放锁,加上try catch 再进行事物的回滚。
上一篇: Android内存泄漏实战解析
下一篇: 华为2016研发工程师编程题2/3