Java并发编程(三)
临界区Critical Section
- 问题分析,当两个线程同时对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,代码如下
static int counter = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
counter++;
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
counter--;
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("{}",counter);
}
分析上述的程序,输出的结果可能是正数、负数、零。因为 Java 中对静态变量的自增,自减并不是原子操作。当单线程顺序执行时,不会发生交错,输出结果为0,当多个线程时,容易发生指令交错,从而导致输出的结果错误
临界区的概念
- 一段代码块内如果存在对共享资源的多线程读写操作,则这段代码就称为临界区,上述的临界区的代码为
counter++; 与counter--;
竞态条件 Race Condition
- 多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
synchronized
- 解决上述问题的办法之一就是
synchronized 关键字,即俗称的【对象锁】,它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换
互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点
synchronized语法
synchronized(对象)
{
临界区
}
static int counter = 0;
static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (lock) {
counter++;
}
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (lock) {
counter--;
}
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("{}", counter);
}
synchronized中的对象就相当于一个房间,线程相当于需要进入这个房间的人,当t1线程执行到synchronized(lock)时就相当于进入房间执行自己的任务,这时候如果 t2 也运行到了 synchronized(lock) 时,它发现门被锁住了,只能在门外等待,发生了上下文切换,阻塞住了这中间即使 t1 的 cpu 时间片不幸用完,被踢出了门外(不要错误理解为锁住了对象就能一直执行下去),这时门还是锁住的,t1 仍拿着钥匙,t2 线程还在阻塞状态进不来,只有下次轮到 t1 自己再次获得时间片时才能开门进入当 t1 执行完 synchronized{} 块内的代码,这时候才会从 lock 房间出来并解开门上的锁,唤醒 t2 线程把钥匙给他。t2 线程这时才可以进入 lock房间,锁住了门拿上钥匙,执行它的 count-- 代码
方法上添加synchronized
- 在普通方法上添加synchronized关键字,相当于给当前的类增加synchronized关键字
class Test{
public synchronized void test() {
}
}
等价于
class Test{
public void test() {
synchronized(this) {
}
}
}
- 在static方法上增加synchronized关键字,相当于给这个实体类增加了关键字
class Test{
public synchronized static void test() {
}
}
等价于
class Test{
public static void test() {
synchronized(Test.class) {
}
}
}
线程八锁(判断synchrionized锁住的是哪个对象)
情况一
class Number{
public synchronized void a() {
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
}
- synchronized加在了非静态方法上,所以该synchronized加载了n1上,两个线程同时启动,所以输出可能为
先1后2 或者 先2后1
情况二
class Number{
public synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
}
- 与上述相同,锁加在了n1类上,所以输出为
1秒后12 或者 2一秒后1
情况三
class Number{
public synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
public void c() {
log.debug("3");
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
new Thread(()->{ n1.c(); }).start();
}
}
- 锁同样加在n1类上,但是方法c并未使用锁,所以输出可能为
先输出3后1s后输出12 ,或者 先输出23后1s后输出1 或者 先输出32后1s之后输出1
情况四
```class Number{
public synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
}
- 两个锁分别加在两个非静态方法上,所以相当于锁分别加在n1与n2上,两个锁互不干扰,所以输出为
先输出2后1s之后输出1
情况五
class Number{
public static synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
}
- a方法为静态方法,相当于锁加到了Number.class类上,b为非静态方法加锁相当于加到了n1类上,所以两个锁不一致,
先输出2以后1s后输出1
情况六
class Number{
public static synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
}
- ab方法都是静态方法,加锁都加在了Number.class类上,所以 输出为:
先输出2后1s后输出1 或者 1s后输出12
情况七
class Number{
public static synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
}
- 虽然声明了 n1 与 n2 但是方法a为静态方法,锁相当于加在Number…class类上,n2.b的锁相当于加在n2类上,两个锁不同,所以输出为
先输出2后1s后输出1
情况八
class Number{
public static synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
}
- ab两个方法都是静态方法,相当于锁都加在Number.class类上,虽然由不同的类调用,但是加锁的是同一个类,所以输出为
1s 后12 , 或 输出2后 1s后 1
黑马程序员多线程课程:JUC并发编程全套教程
|