文章目录
思考题
问:A线程正在执行一个对象的同步方法,B线程是否可以执行同一个对象中的非同步方法?
synchronized:
用于同步方法或者代码块,使得多个线程在试图并发执行同一个代码块的时候,串行地执行。以达到线程安全的目的。
允许重入:
在多线程的时候是这样的,但是对于单线程,是允许重入的,每重入一次,计数器加1,当退出代码块时,计数器减1。
/**
* ClassName: Account
* Date: 2020/2/25 1:16
* author: Oh_MyBug
* version: V1.0
* 两个线程分别访问同个对象的同步方法和非同步方法
*/
public class Account {
String name;
double balance;
public synchronized void set(String name, double balance) {
System.out.println(Thread.currentThread().getName() + ":start synchronize...");
this.name = name;
try {
Thread.sleep(1000 * 2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
System.out.println(Thread.currentThread().getName() + ":finish synchronize...");
}
public void getBalance(String name) {
System.out.println(Thread.currentThread().getName() + ":start general method...");
try {
Thread.sleep(1000 * 2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":finish general method...");
System.out.println(this.balance);
}
public static void main(String[] args) {
Account account = new Account();
new Thread(()->account.set("zhangsan", 100.0), "Thread-1").start();
new Thread(()->account.getBalance("zhangsan"), "Thread-2").start();
}
}
// 输出:
Thread-2:start general method...
Thread-1:start synchronize...
Thread-2:finish general method...
Thread-1:finish synchronize...
100.0
答案:A线程正在执行一个对象的同步方法,B线程可以执行同一个对象中的非同步方法。
/**
* ClassName: Account
* Date: 2020/2/25 1:16
* author: Oh_MyBug
* version: V1.0
* 两个线程访问两个对象的同步方法
*/
public class Account1 {
String name;
double balance;
public synchronized void set(String name, double balance) {
System.out.println(Thread.currentThread().getName() + ":start synchronize...");
this.name = name;
try {
Thread.sleep(1000 * 2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
System.out.println(Thread.currentThread().getName() + ":finish synchronize...");
}
public synchronized void getBalance(String name) {
System.out.println(Thread.currentThread().getName() + ":start synchronized method...");
try {
Thread.sleep(1000 * 2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":finish synchronized method...");
System.out.println(this.balance);
}
public static void main(String[] args) {
Account1 account = new Account1();
new Thread(()->account.set("zhangsan", 100.0), "Thread-1").start();
new Thread(()->account.getBalance("zhangsan"), "Thread-2").start();
}
}
// 输出:
Thread-1:start synchronize...
Thread-1:finish synchronize...
Thread-2:start synchronized method...
Thread-2:finish synchronized method...
100.0
/**
* ClassName: Account2
* Date: 2020/2/25 2:05
* author: Oh_MyBug
* version: V1.0
* 两个线程分别访问非同步静态方法和同步非静态方法
*/
public class Account2 {
String name;
double balance;
static String bank = "ABC";
public synchronized void set(String name, double balance) {
System.out.println(Thread.currentThread().getName() + ":start synchronize...");
this.name = name;
try {
Thread.sleep(1000 * 2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
System.out.println(Thread.currentThread().getName() + ":finish synchronize...");
}
public static void getBank() {
System.out.println(Thread.currentThread().getName() + ":start static method...");
try {
Thread.sleep(1000 * 2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":finish static method...");
System.out.println(bank);
}
public static void main(String[] args) {
Account2 account = new Account2();
new Thread(()->account.set("zhangsan", 100.0), "Thread-1").start();
new Thread(()->account.getBank(), "Thread-2").start();
}
}
// 输出:
Thread-1:start synchronize...
Thread-2:start static method...
Thread-1:finish synchronize...
Thread-2:finish static method...
ABC
总结:
- 非静态方法之间,锁住的是本类的对象,所以,当一个线程在执行这个对象的非静态方法时,就会锁住整个对象,其他线程就不能进行处理这个对象中的其他非静态方法。 静态方法不属于本类对象,属于栈内存的方法区,全局只有一份。 对象处于堆内存,全局中可有多份实例。
- 当方法是静态方法时,线程拿到的静态方法的锁和非静态方法的锁不是一个。 静态方法的锁是唯一的,非静态方法的锁属于对象的,一个对象可以有多个不同的实例对象。当一个线程进入一个对象的一个synchronized的静态方法后,其它线程可进入此对象的其它方法,不管此方法是静态的还是非静态的。
问:同上,B线程是否可以同时执行同一个对象中的另一个同步方法?
答案:A线程正在执行一个对象的同步方法,B线程不可以执行同一个对象中的另一个同步方法。
问:线程抛出异常会释放锁吗?
/**
* ClassName: Account
* Date: 2020/2/25 1:16
* author: Oh_MyBug
* version: V1.0
* 两个线程访问两个对象的同步方法
*/
public class Account1 {
String name;
double balance;
public synchronized void set(String name, double balance) throws Exception {
System.out.println(Thread.currentThread().getName() + ":start synchronize...");
this.name = name;
try {
Thread.sleep(1000 * 2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
throw new Exception(Thread.currentThread().getName() + ":Exception...");
}
public synchronized void getBalance(String name) {
System.out.println(Thread.currentThread().getName() + ":start synchronize...");
try {
Thread.sleep(1000 * 2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":finish synchronize...");
System.out.println(this.balance);
}
public static void main(String[] args) throws Exception {
Account1 account = new Account1();
new Thread(()-> {
try {
account.set("zhangsan", 100.0);
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread-1").start();
new Thread(()->account.getBalance("zhangsan"), "Thread-2").start();
}
}
// 输出:
Thread-1:start synchronize...
Thread-2:start synchronize...
java.lang.Exception: Thread-1:Exception...
at Account1.set(Account1.java:21)
at Account1.lambda$main$0(Account1.java:39)
at java.base/java.lang.Thread.run(Thread.java:830)
Thread-2:finish synchronize...
100.0
答:在程序执行的过程中如果出现异常默认锁会被释放,在并发处理的过程中,有异常的情况需要多加小心,可能会出现数据不一致的情况,多个servlet线程访问同一个资源时,如果第一个线程抛出异常,其他线程就会进入同步代码块,有可能会访问到异常产生时的数据,出现数据不一致的情况。
问:volatile和synchronize区别?
首先需要理解线程安全的两个方面:执行控制和内存可见。
执行控制的目的是控制代码执行(顺序)及是否可以并发执行。
内存可见控制的是线程执行结果在内存中对其它线程的可见性。根据Java内存模型的实现,线程在具体执行时,会先拷贝主存数据到线程本地(CPU缓存),操作完成后再把结果从线程本地刷到主存。
答:
- volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
- volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
- volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
- volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
- volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
安利文章:
全面理解Java内存模型
volatile和synchronized的区别
问:写一个程序,证明Atomic原子类比synchronize更高效
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* ClassName: TestSyncMethods
* Date: 2020/2/25 4:08
* author: Oh_MyBug
* version: V1.0
*/
public class AtomicTest {
int count = 0;
AtomicInteger count1 = new AtomicInteger(0);
synchronized void m() {
for (int i = 0; i < 10; i++) {
count++;
}
}
void n() {
for (int i = 0; i < 10; i++) {
count1.incrementAndGet();
}
}
public static void main(String a[]) {
long start = System.currentTimeMillis();
AtomicTest t = new AtomicTest();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 50; i++) {
threads.add(new Thread(t::m, "thread " + i));
}
threads.forEach((o) -> o.start());
threads.forEach((o) -> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("increase-->" + t.count);
System.out.println(System.currentTimeMillis() - start);
System.out.println("----------------------------------");
long start1 = System.currentTimeMillis();
AtomicTest t1 = new AtomicTest();
List<Thread> thread = new ArrayList<>();
for (int i = 0; i < 50; i++) {
thread.add(new Thread(t1::n, "thread " + i));
}
thread.forEach((o) -> o.start());
thread.forEach((o) -> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("atomic-->" + t1.count1);
System.out.println(System.currentTimeMillis() - start1);
}
}
// 输出:
increase-->500
76
----------------------------------
atomic-->500
24
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* ClassName: TestSyncMethods
* Date: 2020/2/25 4:08
* author: Oh_MyBug
* version: V1.0
*/
public class AtomicTest {
int count = 0;
AtomicInteger count1 = new AtomicInteger(0);
synchronized void m() {
for (int i = 0; i < 10; i++) {
count++;
}
}
void n() {
for (int i = 0; i < 10; i++) {
count1.incrementAndGet();
}
}
public static void main(String a[]) {
long start = System.currentTimeMillis();
AtomicTest t = new AtomicTest();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
threads.add(new Thread(t::m, "thread " + i));
}
threads.forEach((o) -> o.start());
threads.forEach((o) -> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("increase-->" + t.count);
System.out.println(System.currentTimeMillis() - start);
System.out.println("----------------------------------");
long start1 = System.currentTimeMillis();
AtomicTest t1 = new AtomicTest();
List<Thread> thread = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
thread.add(new Thread(t1::n, "thread " + i));
}
thread.forEach((o) -> o.start());
thread.forEach((o) -> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("atomic-->" + t1.count1);
System.out.println(System.currentTimeMillis() - start1);
}
}
// 输出:
increase-->10000
609
----------------------------------
atomic-->10000
640
总结:
- 在中低程度的竞争下,原子类提供更高的伸缩性;在高强度的竞争下,锁能更好的帮助我们避免竞争。
- 若资源竞争规模不大,控制粒度较小,使用原子类比使用锁更好,能提高效率与性能
问:Atomic原子类可以保证可见性吗?请写一个程序来证明
import java.util.concurrent.atomic.AtomicInteger;
/**
* ClassName: AtomicTest1
* Date: 2020/2/25 4:47
* author: Oh_MyBug
* version: V1.0
*/
public class AtomicTest1 {
AtomicInteger atomicInteger = new AtomicInteger(0);
int interger = 0;
public void m(){
System.out.println(Thread.currentThread().getName());
for (int i = 0; i < 10000; i ++) {
atomicInteger.incrementAndGet();
interger++;
}
System.out.println(Thread.currentThread().getName());
}
public void n(){
interger++;
}
public void getnum() {
System.out.println("Atomic:" + atomicInteger.get());
System.out.println("increase:" + interger);
}
public static void main(String[] args) {
AtomicTest1 atomicTest1 = new AtomicTest1();
Thread thread1 = new Thread(()->atomicTest1.m(), "Thread-1");
Thread thread2 = new Thread(()->atomicTest1.m(), "Thread-2");
thread1.start();
thread2.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicTest1.getnum();
}
}
// 输出:
Thread-2
Thread-1
Thread-1
Thread-2
Atomic:20000
increase:18732
答:Atomic原子类可以保证可见性
问:写一个程序证明Atomic原子类的多个方法并不构成原子性
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* ClassName: AtomicTest2
* Date: 2020/2/25 5:05
* author: Oh_MyBug
* version: V1.0
*/
public class AtomicTest2 {
AtomicInteger atomicInteger = new AtomicInteger(0);
public void m(){
for (int i = 0; i < 10000; i ++) {
if (atomicInteger.get()<1000)
atomicInteger.incrementAndGet();
}
}
public int getnum(){
return atomicInteger.get();
}
public static void main(String[] args) {
AtomicTest2 atomicTest2 = new AtomicTest2();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 100; i++) {
threads.add(new Thread(()->atomicTest2.m(), "thread " + i));
}
threads.forEach((o) -> o.start());
threads.forEach((o) -> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(atomicTest2.getnum());
}
}
// 输出:
1001
问:写一个程序模拟死锁
产生条件:
-
互斥条件
指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
-
请求和保持条件
指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
-
不剥夺条件
指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
-
环路等待条件
指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
避免死锁:理论上是只要打破四个必要条件之一就能有效预防死锁的发生
- 加锁顺序(线程按照一定的顺序加锁)
- 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
- 破坏“循环等待”条件:将系统中的所有资源统一编号,进程可在任何时刻提出资源申请,但所有申请必须按照资源的编号顺序(升序)提出。这样做就能保证系统不出现死锁。
/**
* ClassName: DeadLock
* Date: 2020/2/25 5:42
* author: Oh_MyBug
* version: V1.0
*/
public class DeadLock {
public static String resource1 = "resource1";
public static String resource2 = "resource2";
public static void main(String[] args) {
Thread thread1 = new Thread(new BusinessA());
Thread thread2 = new Thread(new BusinessB());
thread1.start();
thread2.start();
}
static class BusinessA implements Runnable {
@Override
public void run() {
try{
System.out.println("BusinessA启动");
while(true){
synchronized(DeadLock.resource1){
System.out.println("BusinessA拿到了resource1的锁");
Thread.sleep(3000);//获取resource1后先等一会儿,让BusinessB有足够的时间锁住resource2
System.out.println("BusinessA想拿resource2的锁。。。。");
synchronized(DeadLock.resource2){
System.out.println("BusinessA获得到了resource2的锁");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
static class BusinessB implements Runnable {
@Override
public void run(){
try{
System.out.println("BusinessB启动");
while(true){
synchronized(DeadLock.resource2){
System.out.println("BusinessB拿得到了resource2的锁");
Thread.sleep(3000);//获取resource2后先等一会儿,让BusinessA有足够的时间锁住resource1
System.out.println("BusinessB想拿resource1的锁。。。。");
synchronized(DeadLock.resource1){
System.out.println("BusinessB获得到了resource1的锁");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
}
// 输出:
BusinessA启动
BusinessA拿到了resource1的锁
BusinessB启动
BusinessB拿得到了resource2的锁
BusinessA想拿resource2的锁。。。。
BusinessB想拿resource1的锁。。。。