1:线程安全示例
线程1:a=5 执行 a – 此时 a=4 cpu切换线程执行
线程2: a=4 执行a-- 此时 a=3 打印a=3
线程1:打印,此时a=3
线程3:a=3 执行a-- 此时 a=2,打a=2
线程4:a=2执行a-- 此时 a=1,打a=1
线程5:a=1 执行a-- 此时 a=0,打a=0
可以看出线程1还未结束a的值已经被线程2减一等于3了。
可以在方法上加 synchronized 关键字 解决线程安全问题,如下
2、同步方法在执行中是否可以调用非同步方法?
package sync;
/**
* 同步方法在执行中是否可以调用非同步方法?
* 答案是可以的,线程2执行testMechod方法
* 在线程1没有结束syncMethod方法时就执行完毕了
* 而没有说去等到线程1执行syncMethod方法完毕后
* 再去执行testMechod方法,这就说明同步方法中是可以调用非同步方法的
* @author xzq
*/
public class Synchronize02 {
public static synchronized void syncMethod() throws InterruptedException{
String threadName=Thread.currentThread().getName();
System.out.println(threadName+":开始执行syncMethod方法……");
//休眠5秒,模拟处理业务逻辑
Thread.sleep(5000);
System.out.println(threadName+":执行syncMethod方法结束!");
}
public static void testMechod() throws InterruptedException{
String threadName=Thread.currentThread().getName();
System.out.println(threadName+":开始执行testMechod方法……");
//休眠3秒,模拟处理业务逻辑
Thread.sleep(3000);
System.out.println(threadName+":执行testMechod方法结束!");
}
public static void main(String[] args) {
new Thread("线程1"){
@Override
public void run() {
try {
syncMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
new Thread("线程2"){
@Override
public void run() {
try {
testMechod();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
}
}
- 答案是可以的,线程2执行testMechod方法
- 在线程1没有结束syncMethod方法时就执行完毕了
- 而没有说去等到线程1执行syncMethod方法完毕后
- 再去执行testMechod方法,这就说明同步方法中是可以调用非同步方法的
3、一个同步方法是否可以调用另一个同步方法?
package sync;
/**
* 一个同步方法是否可以调用另一个同步方法?
* 这个要分情况讨论:
* 1、如果在同一个线程的同一把锁,那么就是可以的。
* 2、如果在同一个线程的不是同一把锁那么就需要看锁是否
* 被其它线程持有了。
* 3、不同线程的同一把锁,那么就会产生排队现象(同步)
* @author xzq
*/
public class Synchronize03 {
//锁的是Synchronize03.class对象锁
public static synchronized void syncMethod01() throws InterruptedException{
String threadName=Thread.currentThread().getName();
System.out.println(threadName+":开始执行syncMethod01方法……");
//休眠3秒,模拟处理业务逻辑
Thread.sleep(3000);
//调用同一个锁对象的同步方法
syncMethod02();
System.out.println(threadName+":执行syncMethod01方法结束!");
}
//锁的也是Synchronize03.class对象锁 所以锁的是同一个锁
// 因为是同一个锁,所以就可以直接进入需要要在外等着排队 可重入锁
public static synchronized void syncMethod02() throws InterruptedException{
String threadName=Thread.currentThread().getName();
System.out.println(threadName+":开始执行syncMethod02方法……");
//休眠1秒,模拟处理业务逻辑
Thread.sleep(1000);
System.out.println(threadName+":执行syncMethod02方法结束!");
}
public static void main(String[] args) {
new Thread("线程1"){
@Override
public void run() {
try {
syncMethod01();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
}
}
这个要分情况讨论:
- 1、如果在同一个线程的同一把锁,那么就是可以的。
- 2、如果在同一个线程的不是同一把锁那么就需要看锁是否被其它线程持有了。
- 3、不同线程的同一把锁,那么就会产生排队现象(同步)
案例中的synchronized 是同一把锁 锁的都是Synchronize03.class对象
4、线程在执行过程中,出现异常,锁会被释放么?
package sync;
/**
* 线程在执行过程中,出现异常,锁会被释放么?
* 答:synchronized默认抛出异常,锁是会被释放的,
* 下面代码中,run方法为同步方法,线程1中的run在死循环中
* 抛出了异常,如果锁不被释放,线程2是无法进入run方法继续执行的。
*
* 所以,在多线程并发执行过程中,对出现的异常一定要做妥善处理,
* 否则就可能会导致数据不一致的情况;
* 下面的代码中,线程2拿到的a的值就是从3开始的,这和预期的从0开始
* 是不一致的,因为异常了数据应该回滚 a应该回归到a=0
* 如果此时如果对a的操作处在一个事务中,那么就应该在出现异常的时候将
* a的值进行重置(做事务的回滚)
* @author xzq
*/
public class Synchronize05 {
public static void main(String[] args) throws InterruptedException {
Runnable r = new Runnable() {
private int a = 0;
@Override
public synchronized void run() {
try {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "开始执行线程的run方法……");
while (true) {
a++;
System.out.println(threadName + ":a的值为:" + a);
// 模拟业务逻辑执行,休息1秒
Thread.sleep(1000);
if (a == 3) {
throw new RuntimeException();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
new Thread(r,"线程1").start();
// 2秒后又新建一个线程进行执行同样的逻辑
Thread.sleep(2000);
new Thread(r,"线程2").start();
}
}
答:synchronized默认抛出异常,锁是会被释放的。
-
下面代码中,run方法为同步方法,线程1中的run在死循环 抛出了异常,如果锁不被释放,线程2是无法进入run方法继续执行的。
-
所以,在多线程并发执行过程中,对出现的异常一定要做妥善处理, 否则就可能会导致数据不一致的情况;
-
下面的代码中,线程2拿到的a的值就是从3开始的,这和预期的从0开始是不一致的,因为异常了数据应该回滚 a应该回归到a=0
-
如果此时如果对a的操作处在一个事务中,那么就应该在出现异常的时候将 a的值进行重置(做事务的回滚)
5、从synchronized思考合理地设置锁的粒度
package sync;
/**
* 从synchronized思考合理地设置锁的粒度
* 尽量使synchronized的代码区域小,减小锁的粒度
* @author xzq
*/
public class Synchronize06 {
public static void main(String[] args) {
//创建服装店对象
final Test clothingStore=new Test();
//创建第一个线程:模拟第一个人进入服装店
new Thread("线程1"){
@Override
public void run() {
try {
clothingStore.fineGrain();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
//创建第二个线程:模拟第二个人进入服装店
new Thread("线程2"){
@Override
public void run() {
try {
clothingStore.fineGrain();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
static class Test{
public void coarseGrain(){
synchronized (this) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName+":1、进入服装店……");
System.out.println(threadName+":2、挑选衣服……");
System.out.println(threadName+":3、进入试衣间换衣服……");
System.out.println(threadName+":4、离开试衣间并付款……");
System.out.println(threadName+":5、离开服装店……");
}
}
public void fineGrain() throws InterruptedException {
String threadName = Thread.currentThread().getName();
System.out.println(threadName+":1、进入服装店……");
System.out.println(threadName+":2、挑选衣服……");
synchronized (this) {
System.out.println(threadName+":3、进入试衣间换衣服……");
Thread.sleep(5000);
System.out.println(threadName+":4、离开试衣间并付款……");
}
System.out.println(threadName+":5、离开服装店……");
}
}
}
锁试衣间而不是锁服装店
6、锁定一个对象f,如果这个对象的属性发生改变,会不会影响到锁的使用呢
package sync;
/**
* 锁定一个对象f,如果这个对象的属性发生改变,
* 会不会影响到锁的使用呢?
*
* 答:是不影响这个锁对象的正常使用的,但是如果这个对象f的
* 引用发生改变去指向了另一个对象了,那么这个锁的对象
* 会变成新指向的那个对象了。
* 所以应该杜绝将锁定对象的引用去指向另外的对象,才能达到
* 我们软件想要的结果。
*
* @author xzq
*/
public class Synchronize07 {
static class Test_1{
public Test_2 test_2=new Test_2();
public void testMethod() throws InterruptedException{
synchronized (test_2) {
String threadName = Thread.currentThread().getName();
while(true){
Thread.sleep(1000);
test_2.a++;
System.out.println(threadName+":a="+test_2.a);
// System.out.println(threadName+":你好,我是复读机,我每1秒中复读一次!");
}
}
}
}
static class Test_2{
public int a=0;
}
public static void main(String[] args) throws InterruptedException {
final Test_1 test=new Test_1();
//创建第一个线程并启动
new Thread("线程1"){
@Override
public void run() {
try {
test.testMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
Thread.sleep(3000);
//创建第二个线程并启动
Thread t2 = new Thread("线程2"){
@Override
public void run() {
try {
test.testMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
/*
* 这里是将lock这个引用去指向了另一个新创建出来的对象
* 所以两个线程持有的不是同一个锁对象,所以不能形成互斥现象
* 和去掉锁效果一样 两个线程共享一个对象
*/
test.test_2=new Test_2();
/*如果修改属性 是不影响锁的性质的 线程2无法进入
*/
// test.lock.a=2;
t2.start();
}
}
如果是改变对象的属性不影响锁的性质,如下测试线程2无法进入
7、不要以字符串常量或者其它常量作为锁定对象
package sync;
/**
* 不要以字符串常量或者其它常量作为锁定对象
*
* 这样做会产生几个问题:
* 1、导致引入我们项目作为jar包的上游方,如果在
* 同步的时候锁定的是同一个我们已锁定的常量,那么
* 就会产生死锁现象。
*
* 2、如果是我们引入下游方的jar包,而下游方在逻辑中
* 有对某个常量进行加锁,但是我们由于读不到其源码,
* 然后我们在加锁的时候也去用到了这个常量,那么也会
* 产生死锁现象。
*
* 结论:这样的做法会导致排错极为困难,会造成软件的
* 可维护性和可靠性极差。
* @author xzq
*/
public class Synchronize08 {
static class Test{
/**
* //存入常量池 str_1和str_2是同一个对象
* new String();不会存入常量池中
*/
private String str_1="字符串";
private String str_2="字符串";
private Integer itg_1=50;
private Integer itg_2=50;//127以下的数字都会被存入常量池中
private Integer itg_3=128;//不存在常量池,不会互斥
private Integer itg_4=128;
public void testMethod_1() throws InterruptedException{
synchronized (itg_1) {
String threadName = Thread.currentThread().getName();
while(true){
Thread.sleep(1000);
System.out.println(threadName+":我是"+itg_1+"存在常量池中,所以互斥");
}
}
}
public void testMethod_2() throws InterruptedException{
synchronized (itg_2) {
String threadName = Thread.currentThread().getName();
while(true){
Thread.sleep(1000);
System.out.println(threadName+":我是"+itg_2+"存在常量池中,所以互斥");
}
}
}
}
public static void main(String[] args) throws InterruptedException {
final Test test=new Test();
//创建第一个线程并启动
new Thread("线程1"){
@Override
public void run() {
try {
test.testMethod_1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
Thread.sleep(3000);
//创建第二个线程并启动
Thread t2 = new Thread("线程2"){
@Override
public void run() {
try {
test.testMethod_2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t2.start();
}
}
private String str_1=“字符串”;
private String str_2=“字符串”;
str_1和str_2会存入常量池,所以二者为同一个对象,锁定会导致互斥
127以下的数字包括127都会被存入常量池中,127以上的不会放入常量池
所以锁定127一下的数字会导致互斥包括127,127以上的不会
下面案例锁定数字 128