亲爱的观众朋友们,你们好!这是多线程笔记的第二篇文章,这一章主要是学习一下对象以及变量的并发访问。
学习完本章主要掌握以下技术点:
- [ ] synchronized对象监视器Object时的使用。
- [ ] synchronized对象监视器Class时的使用。
- [ ] 非线程安全是如何出现的。
- [ ] 关键字volatile的主要作用。
- [ ] 关键字volatile与 synchronized的区别以及使用情况。
本章的类容较多,计划分三次更新,每次更新一部分。这是第二部分。
2 synchronized同步语句块
背景:用关键字synchronized声明方法是有弊端的,比如A线程顶用同步方法长时间执行任务,B线程就需要等待较长时间。在这种情况下可以使用synchronized同步语句块来解决。
2.1 synchronized方法的弊端
public class CommonUtils {
public static long beginTime1;
public static long endTime1;
public static long beginTime2;
public static long endTime2;
}
public class Task {
private String getData1;
private String getData2;
public synchronized void doLongTimeTask() {
try {
System.out.println("begin task");
Thread.sleep(3000);
getData1 = "长时间处理任务后从远程返回的值 1 hreadName="
+ Thread.currentThread().getName();
getData2 = "长时间处理任务后从远程返回的值 2 threadName="
+ Thread.currentThread().getName();
System.out.println(getData1);
System.out.println(getData2);
System.out.println("end task");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class MyThread1 extends Thread {
private Task task;
public MyThread1(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
CommonUtils.beginTime1 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime1 = System.currentTimeMillis();
}
}
public class MyThread2 extends Thread {
private Task task;
public MyThread2(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
CommonUtils.beginTime2 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime2 = System.currentTimeMillis();
}
}
public class Run {
public static void main(String[] args) {
Task task = new Task();
MyThread1 thread1 = new MyThread1(task);
thread1.start();
MyThread2 thread2 = new MyThread2(task);
thread2.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long beginTime = CommonUtils.beginTime1;
if (CommonUtils.beginTime2 < CommonUtils.beginTime1) {
beginTime = CommonUtils.beginTime2;
}
long endTime = CommonUtils.endTime1;
if (CommonUtils.endTime2 > CommonUtils.endTime1) {
endTime = CommonUtils.endTime2;
}
System.out.println("耗时: + ((endTime - beginTime) / 1000));
}
}
synchronized方法是对当前的对象进行加锁,而synchronized语句块是对某一个对象进行加锁。
2.2 synchronized同步代码块的使用
public class ObjectService {
public void serviceMethod() {
try {
synchronized (this) {
System.out.println("begin time=" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("end end=" + System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private ObjectService service;
public ThreadA(ObjectService service) {
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.serviceMethod();
}
}
public class ThreadB extends Thread {
private ObjectService service;
public ThreadB(ObjectService service) {
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.serviceMethod();
}
}
public class Run {
public static void main(String[] args) {
ObjectService service = new ObjectService();
ThreadA a = new ThreadA(service);
a.setName("a");
a.start();
ThreadB b = new ThreadB(service);
b.setName("b");
b.start();
}
}
当两个线程并发访问同一个对象object中的synchronized(this)同步代码块时,一段时间内只有一个线程被执行,另一个线程必须等待当前的线程执行完这个代码块以后才能执行该代码块
2.3 同步代码块解决同步方法的弊端
public class Task {
private String getData1;
private String getData2;
public void doLongTimeTask() {
try {
System.out.println("begin task");
Thread.sleep(3000);
getData1 = "长时间处理任务后从远程返回的值 1 hreadName="
+ Thread.currentThread().getName();
getData2 = "长时间处理任务后从远程返回的值 2 threadName="
+ Thread.currentThread().getName();
synchronized (this) {
getData1 = privateGetData1;
getData2 = privateGetData2;
}
System.out.println(getData1);
System.out.println(getData2);
System.out.println("end task");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
通过实验可以发现,一个线程访问object的synchronized(this)代码块时,另一个线程仍然可以访问非synchronized(this)同步代码。
与2.1相比虽然时间缩短,但是同步代码块仍然持有的是对象的锁。
2.4 一半异步,一半同步
public class Task {
public void doLongTimeTask() {
for (int i = 0; i < 100; i++) {
System.out.println("nosynchronized threadName="
+ Thread.currentThread().getName() + " i=" + (i + 1));
}
System.out.println("");
synchronized (this) {
for (int i = 0; i < 100; i++) {
System.out.println("synchronized threadName="
+ Thread.currentThread().getName() + " i=" + (i + 1));
}
}
}
}
public class MyThread1 extends Thread {
private Task task;
public MyThread1(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
task.doLongTimeTask();
}
}
public class MyThread2 extends Thread {
private Task task;
public MyThread2(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
task.doLongTimeTask();
}
}
public class Run {
public static void main(String[] args) {
Task task = new Task();
MyThread1 thread1 = new MyThread1(task);
thread1.start();
MyThread2 thread2 = new MyThread2(task);
thread2.start();
}
}
本实验证明,不在synchronized块中就是异步执行,在synchronized块中就是同步执行。
2.5 synchronized代码块间的同步性
public class ObjectService {
public void serviceMethodA() {
try {
synchronized (this) {
System.out.println("A begin time=" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("A end end=" + System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void serviceMethodB() {
synchronized (this) {
System.out.println("B begin time=" + System.currentTimeMillis());
System.out.println("B end end=" + System.currentTimeMillis());
}
}
}
public class ThreadA extends Thread {
private ObjectService service;
public ThreadA(ObjectService service) {
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.serviceMethodA();
}
}
public class ThreadB extends Thread {
private ObjectService service;
public ThreadB(ObjectService service) {
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.serviceMethodB();
}
}
public class Run {
public static void main(String[] args) {
ObjectService service = new ObjectService();
ThreadA a = new ThreadA(service);
a.setName("a");
a.start();
ThreadB b = new ThreadB(service);
b.setName("b");
b.start();
}
}
运行结果:
A begin time=xxx
A end end=xxx
B begin time=xxx
B end end=xxx
当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将受到阻塞。说明synchronized使用的“对象监视器”是一个。
2.6 验证同步synchronized(this)代码块是锁定的当前对象
public class Task {
synchronized public void otherMethod() {
System.out.println("------------------------run--otherMethod");
}
public void doLongTimeTask() {
synchronized (this) {
for (int i = 0; i < 10000; i++) {
System.out.println("synchronized threadName="
+ Thread.currentThread().getName() + " i=" + (i + 1));
}
}
}
}
public class MyThread1 extends Thread {
private Task task;
public MyThread1(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
task.doLongTimeTask();
}
}
public class MyThread2 extends Thread {
private Task task;
public MyThread2(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
task.otherMethod();
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
Task task = new Task();
MyThread1 thread1 = new MyThread1(task);
thread1.start();
Thread.sleep(100);
MyThread2 thread2 = new MyThread2(task);
thread2.start();
}
}
2.7 将任意对象作为对象监视器
这里说明synchronized同步方法或synchronized(this)同步代码块分别有两种作用。
(1)synchronized同步方法
1)对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
2)同一时间只有一个线程可以执行synchronized同步方法中的代码。
(2)synchronized同步代码块
1)对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
2)同一时间只有一个线程可以执行synchronized(this)同步方法中的代码。
java支持“任意对象”作为对象监视器。
应用格式为synchronized(非this对象x)。
在多个线程持有“对象监视器”为同一对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。
当持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。
synchronized(非this对象x)与synchronized方法是异步执行的。
2.8 细化验证3个结论
1)当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。
2)当其他线程执行x对象中synchronized同步方法时呈同步效果。
3)当其他线程执行x对象方法里面的synchronized(this)代码是呈同步效果。
2.9 静态同步synchronized方法与synchronized(class)代码块
关键字synchronized还可以应用在static静态方法上,这样做就是对当前的*.java文件对应的Class类进行持锁。
2.10 数据类型String的常量池特性
在jvm中具有String常量池缓存的功能。切勿在线程间使用“123等内容传递,应该新建String对象。
2.11 同步synchronized方法无限等待与解决
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.methodA();
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.methodB();
}
}
public class Run {
public static void main(String[] args) {
Service service = new Service();
ThreadA athread = new ThreadA(service);
athread.start();
ThreadB bthread = new ThreadB(service);
bthread.start();
}
}
public class Service {
synchronized public void methodA() {
System.out.println("methodA begin");
boolean isContinueRun = true;
while (isContinueRun) {
}
System.out.println("methodA end");
}
synchronized public void methodB() {
System.out.println("methodB begin");
System.out.println("methodB end");
}
}
public class Service {
Object object1 = new Object();
public void methodA() {
synchronized(object1){
System.out.println("methodA begin");
boolean isContinueRun = true;
while (isContinueRun) {
}
}
System.out.println("methodA end");
}
Object object2 = new Object();
public void methodB() {
synchronized(object2){
System.out.println("methodB begin");
System.out.println("methodB end");
}
}
}
更改前是无限等待,更改后是异步执行。
2.12 多线程的死锁
public class DealThread implements Runnable {
public String username;
public Object lock1 = new Object();
public Object lock2 = new Object();
public void setFlag(String username) {
this.username = username;
}
@Override
public void run() {
if (username.equals("a")) {
synchronized (lock1) {
try {
System.out.println("username = " + username);
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("按lock1->lock2顺序执行了");
}
}
}
if (username.equals("b")) {
synchronized (lock2) {
try {
System.out.println("username = " + username);
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("按lock2->lock1按");
}
}
}
}
}
public class Run {
public static void main(String[] args) {
try {
DealThread t1 = new DealThread();
t1.setFlag("a");
Thread thread1 = new Thread(t1);
thread1.start();
Thread.sleep(100);
t1.setFlag("b");
Thread thread2 = new Thread(t1);
thread2.start();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
2.13 内置类与静态内之类
public class OutClass {
static class Inner {
public void method1() {
synchronized ("132") {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + " i="
+ i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
}
public synchronized void method2() {
for (int i = 11; i <= 20; i++) {
System.out
.println(Thread.currentThread().getName() + " i=" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
}
}
public class Run {
public static void main(String[] args) {
final Inner inner = new Inner();
Thread t1 = new Thread(new Runnable() {
public void run() {
inner.method1();
}
}, "A");
Thread t2 = new Thread(new Runnable() {
public void run() {
inner.method2();
}
}, "B");
t1.start();
t2.start();
}
}
实验1:内置类中有两个不同的锁,执行结果是异步的
public class OutClass {
static class InnerClass1 {
public void method1(InnerClass2 class2) {
String threadName = Thread.currentThread().getName();
synchronized (class2) {
System.out.println(threadName + " 进入InnerClass1类中的method1方法);
for (int i = 0; i < 10; i++) {
System.out.println("i=" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
System.out.println(threadName + " 离开InnerClass1类中method1方法");
}
}
public synchronized void method2() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " 进入InnerClass1类中method2方法");
for (int j = 0; j < 10; j++) {
System.out.println("j=" + j);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
System.out.println(threadName + " 离开InnerClass1类中method2方法");
}
}
static class InnerClass2 {
public synchronized void method1() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " 进入InnerClass2类中method1方法");
for (int k = 0; k < 10; k++) {
System.out.println("k=" + k);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
System.out.println(threadName + " 离开InnerClass2类中method1方法");
}
}
}
实验2:同步代码块synchronized(class2)对class上锁后,其他线程只能以同步的方式调用class2中的静态同步方法。
2.16 锁对象的改变
提醒:只要对象不变,即使对象的属性被改变,运行的结果还是同步的。