锁与并发
一、线程安全概念
当多个线程对同一对象的同一个实例进行操作时,出现值被修改、值不同步问题,线程锁机制就是解决线程并发时的同步问题。非线程安全是基于实例变量而言,方法内部的私有变量不在线程安全的讨论范围之内,因为方法内部的变量是私有的。
二、非线程安全举例
从下面这个小例子可以看出当线程threadA执行过程中,password被线程threadB篡改了,造成了线程threadA输出的password为bb。
service类
@Data
public class UnThreadSafeService {
private String username;
private String password;
public void getUsernameAndPassword(String username){
if ("AA".equals(username)){
try {
this.password = "aa";
Thread.sleep(3000);
System.out.println("usernmae = " + username + " password = " + password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if ("BB".equals(username)){
try {
this.password = "bb";
Thread.sleep(3000);
System.out.println("usernmae = " + username + " password = " + password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程类
public class UnThreadSafeA extends Thread{
private UnThreadSafeService service;
public UnThreadSafeA(UnThreadSafeService service) {
this.service = service;
}
@Override
public void run() {
service.getUsernameAndPassword("AA");
}
}
public class UnThreadSafeB extends Thread{
private UnThreadSafeService service;
public UnThreadSafeB(UnThreadSafeService service) {
this.service = service;
}
@Override
public void run() {
service.getUsernameAndPassword("BB");
}
}
测试类
@SpringBootTest
public class UnThreadSafeTest {
@Test
public static void main(String[] args) {
try {
UnThreadSafeService service = new UnThreadSafeService();
UnThreadSafeA threadA = new UnThreadSafeA(service);
threadA.start();
Thread.sleep(1000);
UnThreadSafeB threadB = new UnThreadSafeB(service);
threadB.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
控制台输出结果
usernmae = AA password = bb
usernmae = BB password = bb
三、synchronized同步方法
1、线程安全的解决
学习同步方法之前,先解决上面一节非线程安全的小例子。只需要在申明的方法前面增加synchronized关键字就可实现线程安全。
修改service类
@Data
public class UnThreadSafeService {
private String username;
private String password;
synchronized public void getUsernameAndPassword(String username){
if ("AA".equals(username)){
try {
this.password = "aa";
Thread.sleep(3000);
System.out.println("usernmae = " + username + " password = " + password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if ("BB".equals(username)){
try {
this.password = "bb";
Thread.sleep(3000);
System.out.println("usernmae = " + username + " password = " + password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
控制台输出结果
usernmae = AA password = aa
usernmae = BB password = bb
2、对象锁的概念
synchronized线程锁是基于对象而言的,而不是基于方法。当使用多个线程同时处理多个对象,那么这些线程将以异步的方式运行。例子如下:
修改service类
@Data
public class UnThreadSafeService {
private String username;
private String password;
synchronized public void getUsernameAndPassword(String username){
if ("AA".equals(username)){
try {
System.out.println("线程:" + Thread.currentThread().getName() + "开始运行," + "时间:" + System.currentTimeMillis());
this.password = "aa";
Thread.sleep(3000);
System.out.println("usernmae = " + username + " password = " + password);
System.out.println("线程:" + Thread.currentThread().getName() + "结束运行," + "时间:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if ("BB".equals(username)){
try {
System.out.println("线程:" + Thread.currentThread().getName() + "开始运行," + "时间:" + System.currentTimeMillis());
this.password = "bb";
Thread.sleep(3000);
System.out.println("usernmae = " + username + " password = " + password);
System.out.println("线程:" + Thread.currentThread().getName() + "结束运行," + "时间:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程类
public class UnThreadSafeA extends Thread{
private UnThreadSafeService service;
public UnThreadSafeA(UnThreadSafeService service) {
this.service = service;
}
@Override
public void run() {
service.getUsernameAndPassword("AA");
}
}
public class UnThreadSafeB extends Thread{
private UnThreadSafeService service;
public UnThreadSafeB(UnThreadSafeService service) {
this.service = service;
}
@Override
public void run() {
service.getUsernameAndPassword("BB");
}
}
测试类
@SpringBootTest
public class ObjSynLockTest {
@Test
public static void main(String[] args) {
UnThreadSafeService service = new UnThreadSafeService();
UnThreadSafeA threadA = new UnThreadSafeA(service);
threadA.setName("A");
threadA.start();
UnThreadSafeB threadB = new UnThreadSafeB(service);
threadB.setName("B");
threadB.start();
}
}
控制台输出结果
线程:A开始运行,时间:1598427272868
线程:B开始运行,时间:1598427272869
usernmae = AA password = aa
线程:A结束运行,时间:1598427275870
usernmae = BB password = bb
线程:B结束运行,时间:1598427275870
3、非同步方法
当一个线程持有一个对象的锁时,另外一个线程可以调用该对象的非同步方法(非cynchronized方法)。
service类
public class UnSynService {
synchronized public void methodA(){
try {
System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "开始执行methodA");
Thread.sleep(3000);
System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "推出执行methodA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void methodB(){
System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "开始执行methodB");
System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "推出执行methodB");
}
}
线程类
public class UnSynThreadA extends Thread{
private UnSynService service;
public UnSynThreadA(UnSynService service) {
this.service = service;
}
@Override
public void run() {
service.methodA();
}
}
public class UnSynThreadB extends Thread {
private UnSynService service;
public UnSynThreadB(UnSynService service) {
this.service = service;
}
@Override
public void run() {
service.methodB();
}
}
测试类
@SpringBootTest
public class UnSynTest {
@Test
public static void main(String[] args) {
try {
UnSynService service = new UnSynService();
UnSynThreadA threadA = new UnSynThreadA(service);
threadA.setName("A");
threadA.start();
Thread.sleep(1000);
UnSynThreadB threadB = new UnSynThreadB(service);
threadB.setName("B");
threadB.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
控制台输出结果
线程:A在1598430351543开始执行methodA
线程:B在1598430352544开始执行methodB
线程:B在1598430352544退出执行methodB
线程:A在1598430354544退出执行methodA
4、脏读
脏读是在线程读取实例变量的时候,实例变量的值已经被其他线程更改过。
service类
@Data
public class DirtyReadService {
public String username = "AA";
public String password = "aa";
synchronized public void setUsernameAndPassword(String username, String password) {
try {
this.username = username;
Thread.sleep(3000);
this.password = password;
System.out.println("username = " + username + " password = " + password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void getUsernameAndPassword(){
System.out.println("username = " + username + " password = " + password);
}
}
线程类
public class DirtyReadThreadA extends Thread{
private DirtyReadService service;
public DirtyReadThreadA(DirtyReadService service) {
this.service = service;
}
@Override
public void run() {
service.setUsernameAndPassword("BB","bb");
}
}
测试类
@SpringBootTest
public class DirtyReadTest {
@Test
public static void main(String[] args) {
try {
DirtyReadService service = new DirtyReadService();
DirtyReadThreadA threadA = new DirtyReadThreadA(service);
threadA.start();
Thread.sleep(1000);
DirtyReadThreadB threadB = new DirtyReadThreadB(service);
service.getUsernameAndPassword();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
控制台输出结果
username = BB password = aa
username = BB password = bb
5、锁重入
synchronized锁重入是指当一个线程得到一个对象锁后,当再次请求此对象锁时可再次获得该对象锁。当线程在一个synchronized方法内部调用其他synchronized方法时,永远可以得到锁。
service类
public class ClassLockService {
synchronized public static void printA(){
try {
System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
Thread.sleep(3000);
printB();
System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
}catch (Exception e){
e.printStackTrace();
}
}
synchronized public static void printB(){
try {
System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
Thread.sleep(3000);
System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
}catch (Exception e){
e.printStackTrace();
}
}
}
测试类
@SpringBootTest
public class SynRepeatTest {
@Test
public static void main(String[] args) {
try {
ClassLockService service = new ClassLockService();
Thread threadA = new Thread(){
@Override
public void run() {
service.printA();
}
};
threadA.setName("A");
threadA.start();
Thread.sleep(1000);
Thread threadB = new Thread(){
@Override
public void run() {
service.printB();
}
};
threadB.setName("B");
threadB.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
控制台输出结果
线程名称为:A在1598439196945进入printA
线程名称为:A在1598439199949进入printB
线程名称为:A在1598439202949离开printB
线程名称为:A在1598439202949离开printA
线程名称为:B在1598439202949进入printB
线程名称为:B在1598439205949离开printB
6、synchronized不具有继承性
当子类继承了父类的同步方法,子类没有申明同步时,此时子类的方法将不具有同步性。因此,synchronized不具有继承性。
父子类
public class Main {
synchronized public void inheritMethod(){
try {
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入父类inheritMethod方法时间:" + System.currentTimeMillis());
Thread.sleep(3000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出父类inheritMethod方法时间:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Sub extends Main {
@Override
public void inheritMethod() {
try {
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入子类inheritMethod方法时间:" + System.currentTimeMillis());
Thread.sleep(3000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出子类inheritMethod方法时间:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试类
@SpringBootTest
public class SynInheritTest {
@Test
public static void main(String[] args) {
try {
Sub sub = new Sub();
Thread threadA = new Thread(){
@Override
public void run() {
sub.inheritMethod();
}
};
threadA.setName("A");
threadA.start();
Thread.sleep(1000);
Thread threadB = new Thread(){
@Override
public void run() {
sub.inheritMethod();
}
};
threadB.setName("B");
threadB.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
控制台输出结果
当前线程:A进入子类inheritMethod方法时间:1598440920606
当前线程:B进入子类inheritMethod方法时间:1598440921606
当前线程:A退出子类inheritMethod方法时间:1598440923606
当前线程:B退出子类inheritMethod方法时间:1598440924608
子类申明同步解决同步问题
子类继承了父类的同步方法,如果子类的方法也要同步,则子类也需要申明同步。如下运行结果可以看出AB两个线程以同步的方式运行。
public class Sub extends Main {
@Override
synchronized public void inheritMethod() {
try {
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入子类inheritMethod方法时间:" + System.currentTimeMillis());
Thread.sleep(3000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出子类inheritMethod方法时间:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
子类申明同步后,控制台输出结果
当前线程:A进入子类inheritMethod方法时间:1598441109382
当前线程:A退出子类inheritMethod方法时间:1598441112382
当前线程:B进入子类inheritMethod方法时间:1598441112382
当前线程:B退出子类inheritMethod方法时间:1598441115383
四、synchronized同步语句块
当一个线程调用对象中一个需要比较长时间处理的同步方法时,其他线程就需要进行长时间的等待,等待该线程释放锁后其他线程才能获得这个对象的锁。这样可以使用synchronized同步语句块解决,synchronized方法是对当前对象加锁,synchronized代码块是对某个对象加锁。也就是说,当一个线程访问对象一个方法内部的同步代码块时,其他线程仍然可以访问这个方法的非同步代码块部分的代码。
值得说明的是,在synchronized代码块中的程序时通过不执行,不在synchronized代码块中的程序时异步执行。从以下这个例子可以看出,使用了同步代码块后,AB两个线程的总用时变短了。
1、同步方法问题例举
service类
public class SynQuestionService {
synchronized public void methodA(){
try {
System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "开始运行methodA方法!");
Thread.sleep(3000);
System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "退出运行methodA方法!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
时间记录工具类
public class TimeUtil {
public static long beginTime1;
public static long beginTime2;
public static long endTime1;
public static long endTime2;
}
线程类
public class SynQuestionThreadA extends Thread{
private SynQuestionService service;
public SynQuestionThreadA(SynQuestionService service) {
this.service = service;
}
@Override
public void run() {
super.run();
TimeUtil.beginTime1 = System.currentTimeMillis();
service.methodA();
TimeUtil.endTime1 = System.currentTimeMillis();
}
}
public class SynQuestionThreadB extends Thread{
private SynQuestionService service;
public SynQuestionThreadB(SynQuestionService service) {
this.service = service;
}
@Override
public void run() {
super.run();
TimeUtil.beginTime2 = System.currentTimeMillis();
service.methodA();
TimeUtil.endTime2 = System.currentTimeMillis();
}
}
测试类
@SpringBootTest
public class SynQuestionTest {
@Test
public static void main(String[] args) {
SynQuestionService service = new SynQuestionService();
SynQuestionThreadA threadA = new SynQuestionThreadA(service);
threadA.setName("A");
threadA.start();
SynQuestionThreadB threadB = new SynQuestionThreadB(service);
threadB.setName("B");
threadB.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long beginTime = TimeUtil.beginTime1;
if (TimeUtil.beginTime1 > TimeUtil.beginTime2){
beginTime = TimeUtil.beginTime2;
}
long endTime = TimeUtil.endTime1;
if (TimeUtil.endTime1 < TimeUtil.endTime2){
endTime = TimeUtil.endTime2;
}
System.out.println("A线程开始时间:" + TimeUtil.beginTime1 + ";A线程结束时间:" + TimeUtil.endTime1
+ ";B线程开始时间:" + TimeUtil.beginTime2 + ";B线程结束时间:" + TimeUtil.endTime2);
System.out.println("beginTime=" + beginTime + " endTime=" + endTime);
System.out.println("总耗时:" + (endTime - beginTime)/1000);
}
}
控制台输出结果
线程:A在1598492837397开始运行methodA方法!
线程:A在1598492840398退出运行methodA方法!
线程:B在1598492840398开始运行methodA方法!
线程:B在1598492843398退出运行methodA方法!
A线程开始时间:1598492837396;A线程结束时间:1598492840398;B线程开始时间:1598492837397;B线程结束时间:1598492843398
beginTime=1598492837396 endTime=1598492843398
总耗时:6
修改service方法,改用同步代码块
当将同步方法改为同步代码块后,非同步代码块的程序将以异步的形式运行,因此大大减少了程序的运行时间,提高程序运行效率。将原来需要6s的运行时常缩短为3s。
public class SynQuestionService {
public void methodA(){
try {
System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "开始运行methodA方法!");
Thread.sleep(3000);
synchronized(this) {
System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "退出运行methodA方法!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用同步代码块后,控制台输出结果
线程:A在1598492894091开始运行methodA方法!
线程:B在1598492894091开始运行methodA方法!
线程:A在1598492897092退出运行methodA方法!
线程:B在1598492897092退出运行methodA方法!
A线程开始时间:1598492894090;A线程结束时间:1598492897092;B线程开始时间:1598492894090;B线程结束时间:1598492897092
beginTime=1598492894090 endTime=1598492897092
总耗时:3
2、同步代码块锁定对象
与synchronzied同步方法类似,synchronized(this)同步代码块时对this对象加锁。
任务类
public class TaskA {
synchronized public void methodA(){
System.out.println("-------------运行methodA方法---------------");
}
public void methodB(){
synchronized(this){
for (int i = 0; i < 1000; i++){
System.out.println("线程:" + Thread.currentThread().getName() + ";i = " + i + ";");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程类
public class SynObjThreadA extends Thread{
private TaskA task;
public SynObjThreadA(TaskA task) {
this.task = task;
}
@Override
public void run() {
super.run();
task.methodA();
}
}
public class SynObjThreadB extends Thread{
private TaskA task;
public SynObjThreadB(TaskA task) {
this.task = task;
}
@Override
public void run() {
super.run();
task.methodB();
}
}
测试类
@SpringBootTest
public class SynObjTest {
@Test
public static void main(String[] args) {
TaskA task = new TaskA();
SynObjThreadB threadB = new SynObjThreadB(task);
threadB.setName("B");
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
SynObjThreadA threadA = new SynObjThreadA(task);
threadA.setName("A");
threadA.start();
}
}
控制台输出结果
...
线程:B;i = 7;
线程:B;i = 8;
线程:B;i = 9;
-------------运行methodA方法---------------
线程:B;i = 10;
线程:B;i = 11;
线程:B;i = 12;
...
将methodA方法添加同步
将methodA方法添加同步后,threadA将一直处于阻塞状态,没有获取锁,没有得到运行机会。
public class TaskA {
synchronized public void methodA(){
System.out.println("-------------运行methodA方法---------------");
}
public void methodB(){
synchronized(this){
for (int i = 0; i < 1000; i++){
System.out.println("线程:" + Thread.currentThread().getName() + ";i = " + i + ";");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
控制台输出结果
线程:B;i = 0;
线程:B;i = 1;
线程:B;i = 2;
线程:B;i = 3;
线程:B;i = 4;
线程:B;i = 5;
线程:B;i = 6;
线程:B;i = 7;
线程:B;i = 8;
线程:B;i = 9;
线程:B;i = 10;
线程:B;i = 11;
线程:B;i = 12;
线程:B;i = 13;
线程:B;i = 14;
线程:B;i = 15;
线程:B;i = 16;
线程:B;i = 17;
线程:B;i = 18;
线程:B;i = 19;
线程:B;i = 20;
...
3、对象监听器,非this锁
使用非this锁可以提高代码运行效率,因为使用同步代码块锁非this对象,synchronzied(非this)代码块中的程序与同步方法时异步运行的。
代码略...
4、同步方法的脏读与同步代码块的解决
使用不同方法时,如遇到判断分支,理论上有概率会引发脏读的产生,此时可使用同步代码块进行同步进而解决。
list类
public class MyList {
private List list = new ArrayList();
synchronized public void addList(String str){
list.add(str);
}
synchronized public int getSize(){
return list.size();
}
}
service类
public class AsyDirtyReadService {
public void addList(MyList list, String str){
try {
if (list.getSize() < 1){
Thread.sleep(2000);
list.addList(str);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程类
public class AsyDirtyReadThreadA extends Thread {
private MyList list;
public AsyDirtyReadThreadA(MyList list) {
this.list = list;
}
@Override
public void run() {
AsyDirtyReadService service = new AsyDirtyReadService();
service.addList(list, "A");
}
}
public class AsyDirtyReadThreadB extends Thread {
private MyList list;
public AsyDirtyReadThreadB(MyList list) {
this.list = list;
}
@Override
public void run() {
AsyDirtyReadService service = new AsyDirtyReadService();
service.addList(list, "B");
}
}
测试类
@SpringBootTest
public class AsyDirtyReadTest {
@Test
public static void main(String[] args) {
try {
MyList list = new MyList();
AsyDirtyReadThreadA threadA = new AsyDirtyReadThreadA(list);
threadA.start();
Thread.sleep(1000);
AsyDirtyReadThreadB threadB = new AsyDirtyReadThreadB(list);
threadB.start();
Thread.sleep(5000);
System.out.println("listSize = " + list.getSize());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
控制台输出结果
listSize = 2
从上面的输出结果可以看出,两条线程都对list执行了addList方法,ThreadA线程再判断完size后并没有立即执行addList方法,因此在threadB线程对list的size做判断的时候listSize依然是0。因此,两条线程都对list执行了addList方法。解决办法是在service类中对list进行同步。
service类修改
public class AsyDirtyReadService {
public void addList(MyList list, String str){
try {
synchronized(list){
if (list.getSize() < 1){
Thread.sleep(2000);
list.addList(str);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
控制台输出结果
listSize = 1
5、对象锁与Class锁
synchronized关键字加到static静态方法中是给class类上锁,synchronized关键字加到非static静态方法上是给对象加锁。
service类
public class ClassLockService {
synchronized public static void printA(){
try {
System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
Thread.sleep(3000);
System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
}catch (Exception e){
e.printStackTrace();
}
}
synchronized public static void printB(){
try {
System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
Thread.sleep(3000);
System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
}catch (Exception e){
e.printStackTrace();
}
}
synchronized public void printC(){
try {
System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printC");
Thread.sleep(3000);
System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printC");
}catch (Exception e){
e.printStackTrace();
}
}
}
线程类
public class ThreadA extends Thread{
private ClassLockService service;
public ThreadA(ClassLockService service) {
this.service = service;
}
@Override
public void run() {
super.run();
service.printA();
}
}
public class ThreadB extends Thread{
private ClassLockService service;
public ThreadB(ClassLockService service) {
this.service = service;
}
@Override
public void run() {
super.run();
service.printB();
}
}
public class ThreadC extends Thread{
private ClassLockService service;
public ThreadC(ClassLockService service) {
this.service = service;
}
@Override
public void run() {
super.run();
service.printC();
}
}
测试类
@SpringBootTest
public class ClassLockTest {
@Test
public static void main(String[] args) {
ClassLockService service = new ClassLockService();
ThreadA a = new ThreadA(service);
a.setName("a");
a.start();
ThreadB b = new ThreadB(service);
b.setName("b");
b.start();
ThreadC c = new ThreadC(service);
c.setName("c");
c.start();
}
}
控制台输出结果
线程名称为:a在1598583085437进入printA
线程名称为:c在1598583085438进入printC
线程名称为:a在1598583088438离开printA
线程名称为:b在1598583088438进入printB
线程名称为:c在1598583088439离开printC
线程名称为:b在1598583091439离开printB
6、Class锁
synchronized(class)的作用和synchronized的静态方法一样,是对类加锁。类锁可以对同一类的所有对象实例起作用。同类的所有实例将都以同步方式运行。从以下测试类可以看出,新建了两个不同的ClassLockService类,但是threadA和threadB两个线程以同步的方式运行,因为ClassLockService加了类锁。修改上个例子如下。
测试类
@SpringBootTest
public class ClassLockTest {
@Test
public static void main(String[] args) {
ClassLockService service1 = new ClassLockService();
ClassLockService service2 = new ClassLockService();
ThreadA a = new ThreadA(service1);
a.setName("a");
a.start();
ThreadB b = new ThreadB(service2);
b.setName("b");
b.start();
}
}
控制台输出结果
线程名称为:a在1598593236107进入printA
线程名称为:a在1598593239114离开printA
线程名称为:b在1598593239115进入printB
线程名称为:b在1598593242116离开printB
7、死锁
死锁是因为不同线程都持有彼此的锁,而且都再等待其他线程能够释放锁,陷入长期的彼此等待中。从而导致程序无法继续运行下去。
service类
public class DeadSynService {
private Object lockA;
private Object lockB;
public DeadSynService(Object lockA, Object lockB){
this.lockA = lockA;
this.lockB = lockB;
}
public void methodA(Object lockA){
synchronized (lockA){
System.out.println("线程:" + Thread.currentThread().getName() + "进入methodA方法。");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.methodB(lockB);
}
}
public void methodB(Object lockB){
synchronized (lockB){
System.out.println("线程:" + Thread.currentThread().getName() + "进入methodB方法。");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.methodA(lockA);
}
}
}
线程类
public class DeadSynThreadA extends Thread {
private Object lockA;
private DeadSynService service;
public DeadSynThreadA(DeadSynService service, Object lockA){
this.service = service;
this.lockA = lockA;
}
@Override
public void run(){
service.methodA(lockA);
}
}
public class DeadSynThreadB extends Thread {
private Object lockB;
private DeadSynService service;
public DeadSynThreadB(DeadSynService service, Object lockB){
this.service = service;
this.lockB = lockB;
}
@Override
public void run(){
service.methodB(lockB);
}
}
测试类
@SpringBootTest
public class DeadSynTest {
@Test
public static void main(String[] args) {
Object lockA = new Object();
Object lockB = new Object();
DeadSynService service = new DeadSynService(lockA, lockB);
DeadSynThreadA threadA = new DeadSynThreadA(service,lockA);
threadA.setName("A");
threadA.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
DeadSynThreadB threadB = new DeadSynThreadB(service,lockB);
threadB.setName("B");
threadB.start();
}
}
控制台输出结果
线程:A进入methodA方法。
线程:B进入methodB方法。
jstack检查死锁
1、win+r命令进入cmd;
2、切换至JDK的bin文件夹下;
3、执行jps命令查询线程id;
4、jstack -l +线程id 查询死锁情况;
8、内部类的同步
内部类和普通类在多线程的引用上类似,只是在申明内部类的时候不一样。1、当一个线程获取了内部类的对象锁,其他线程还是可以访问该内部类的非同步方法,两个线程异步运行;2、两个线程争取同个对象锁时,两个线程同步运行;
sevice类
public class ParentClass {
public class SonClassA{
synchronized public void method1(){
for (int i = 0; i < 10 ; i++){
System.out.println("线程:" + Thread.currentThread().getName() + ";i = " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void method2(){
for (int j = 0; j < 10 ; j++){
System.out.println("线程:" + Thread.currentThread().getName() + ";i = " + j);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
测试类
@SpringBootTest
public class InnerClassTest {
@Test
public static void main(String[] args) {
ParentClass parent = new ParentClass();
ParentClass.SonClassA sonA =parent.new SonClassA();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
sonA.method1();
}
},"A");
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
sonA.method1();
}
},"B");
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
sonA.method2();
}
},"C");
threadA.start();
threadB.start();
threadC.start();
}
控制台输出结果
线程:C;i = 0
线程:B;i = 0
线程:C;i = 1
线程:B;i = 1
线程:B;i = 2
线程:C;i = 2
线程:B;i = 3
线程:C;i = 3
线程:B;i = 4
线程:C;i = 4
线程:C;i = 5
线程:B;i = 5
线程:C;i = 6
线程:B;i = 6
线程:C;i = 7
线程:B;i = 7
线程:C;i = 8
线程:B;i = 8
线程:C;i = 9
线程:B;i = 9
线程:A;i = 0
线程:A;i = 1
线程:A;i = 2
线程:A;i = 3
线程:A;i = 4
线程:A;i = 5
线程:A;i = 6
线程:A;i = 7
线程:A;i = 8
线程:A;i = 9
内部类的线程同步和普通类的线程同步用法基本雷士。当一个线程获得了类锁,那么其他线程即使获得其他对象锁,也需要同步运行,需要等待前面一个线程结束后才能运行;
service类
public class ParentClass {
public class SonClassA{
public void method3(SonClassB classB){
synchronized(classB){
for (int k = 0; k < 10 ; k++){
System.out.println("线程:" + Thread.currentThread().getName() + ";i = " + k);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class SonClassB{
public synchronized void method4(){
for (int m = 0; m < 10 ; m++){
System.out.println("线程:" + Thread.currentThread().getName() + ";i = " + m);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
测试类
@SpringBootTest
public class InnerClassTest {
@Test
public static void main(String[] args) {
ParentClass parent = new ParentClass();
ParentClass.SonClassA sonA =parent.new SonClassA();
ParentClass.SonClassB sonB =parent.new SonClassB();
Thread threadD = new Thread(new Runnable() {
@Override
public void run() {
sonA.method3(sonB);
}
},"D");
Thread threadE = new Thread(new Runnable() {
@Override
public void run() {
sonB.method4();
}
},"E");
threadD.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadE.start();
}
}
控制台输出结果
线程:D;i = 0
线程:D;i = 1
线程:D;i = 2
线程:D;i = 3
线程:D;i = 4
线程:D;i = 5
线程:D;i = 6
线程:D;i = 7
线程:D;i = 8
线程:D;i = 9
线程:E;i = 0
线程:E;i = 1
线程:E;i = 2
线程:E;i = 3
线程:E;i = 4
线程:E;i = 5
线程:E;i = 6
线程:E;i = 7
线程:E;i = 8
线程:E;i = 9
9、改变锁对象
当线程运行过程中,锁对象改变了,其他线程可以获得该锁。另外,当锁对象未改变,只是改变了锁对象的属性,线程并不会释放锁。
service类
public class ChangeLockService {
private Object lock1;
public ChangeLockService(Object lock1) {
this.lock1 = lock1;
}
public void setMethod(){
synchronized (lock1) {
System.out.println("线程:" + Thread.currentThread().getName() + ",开始时间:" + System.currentTimeMillis() + "。");
try {
Thread.sleep(50);
lock1 = new Object();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + ",结束时间:" + System.currentTimeMillis() + "。");
}
}
}
线程类
public class ChangeLockThread extends Thread {
private ChangeLockService service;
public ChangeLockThread(ChangeLockService service) {
this.service = service;
}
@Override
public void run() {
super.run();
service.setMethod();
}
}
测试类
@SpringBootTest
public class changeLockTest {
@Test
public static void main(String[] args) {
Object lock1 = new Object();
ChangeLockService service = new ChangeLockService(lock1);
ChangeLockThread threadA = new ChangeLockThread(service);
threadA.setName("A");
threadA.start();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
ChangeLockThread threadB = new ChangeLockThread(service);
threadB.setName("B");
threadB.start();
}
}
控制台输出结果
从控制台输出结果可以看出,ThreadA在运性过程中将锁对象改变为新new出来的锁,ThreadB获得了线程锁。因此,ThreadA和ThreadB同步运行的。
线程:A,开始时间:1598838244210。
线程:B,开始时间:1598838244261。
线程:A,结束时间:1598838244261。
线程:B,结束时间:1598838244312。