Java多线程笔记(二)
亲爱的观众朋友们,你们好!这是多线程笔记的第二篇文章,这一章主要是学习一下对象以及变量的并发访问。
学习完本章主要掌握以下技术点:
- synchronized对象监视器Object时的使用。
- synchronized对象监视器Class时的使用。
- 非线程安全是如何出现的。
- 关键字volatile的主要作用。
- 关键字volatile与 synchronized的区别以及使用情况。
本章的类容较多,计划分三次更新,每次更新一部分。
1. synchronized同步方法
1.1 方法内的变量为线程安全
“非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全问题。”
public class HasSelfPrivateNum {
public void addI(String username) {
try {
int num = 0;
if (username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("a");
}
}
public class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("a");
}
}
public class Run {
public static void main(String[] args) {
HasSelfPrivateNum numRef = new HasSelfPrivateNum();
ThreadA athread = new ThreadA(numRef);
athread.start();
ThreadB bthread = new ThreadB(numRef);
bthread.start();
}
}
int num = 0;
num变量为方法内私有变量,不存在线程安全问题!
运行结果为:
a set over!
b set over!
a num=100
b num=200
1.2 实例变量非线程安全
如果多个线程共同访问1个对象中的实例变量,则有可能出现“非线程安全问题”。
public class HasSelfPrivateNum {
private int num = 0;
public void addI(String username) {
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("a");
}
}
public class ThreadB extends Thread {
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("b");
}
}
public class Run {
public static void main(String[] args) {
HasSelfPrivateNum numRef = new HasSelfPrivateNum();
ThreadA athread = new ThreadA(numRef);
athread.start();
ThreadB bthread = new ThreadB(numRef);
bthread.start();
}
}
结果如下:
a set over!
b set over!
a num=200
b num=200
上面这个例子中就会出现数据覆盖的情况,不是线程安全的!做修改后,就可以了!
public class HasSelfPrivateNum {
private int num = 0;
synchronized public void addI(String username) {
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果:
a set over!
a num=100
b set over!
b num=200
两个线程访问同一个对象中的同步方法时一定是线程安全的!
1.3 多个对象多个锁
public class HasSelfPrivateNum {
private int num = 0;
synchronized public void addI(String username) {
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
ThreadA与ThreadB唯有变化
public class Run {
public static void main(String[] args) {
HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
ThreadA athread = new ThreadA(numRef1);
athread.start();
ThreadB bthread = new ThreadB(numRef2);
bthread.start();
}
}
运行结果:
a set over!
b set over!
b num=200
a num=100
从执行结果上看是异步的。
关键字synchronized获取的锁都是对象锁,而不是一段代码或者方法当作锁,先执行带synchronized关键字的线程持有方法所属对象的Lock,其他线程只能等待,前提是多个线程访问的是同一个对象。
1.4chronized方法与对象锁
public class MyObject {
synchronized public void methodA() {
try {
System.out.println("begin methodA threadName="
+ Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private MyObject object;
public ThreadA(MyObject object) {
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodA();
}
}
public class ThreadB extends Thread {
private MyObject object;
public ThreadB(MyObject object) {
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodA();
}
}
public class Run {
public static void main(String[] args) {
MyObject object = new MyObject();
ThreadA a = new ThreadA(object);
a.setName("A");
ThreadB b = new ThreadB(object);
b.setName("B");
a.start();
b.start();
}
}
在methodA()方法前加入synchronized关键字前后的运行结果如下:
begin methodA threadName=A
begin methodA threadName=B
end
end
begin methodA threadName=A
end
begin methodA threadName=B
end
调用关键字synchronized声明的方法一定是排队运行的;只有共享资源的访问才需要同步化。
public class MyObject {
synchronized public void methodA() {
try {
System.out.println("begin methodA threadName="
+ Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end endTime=" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void methodB() {
try {
System.out.println("begin methodB threadName="
+ Thread.currentThread().getName() + " begin time="
+ System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private MyObject object;
public ThreadA(MyObject object) {
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodA();
}
}
public class ThreadB extends Thread {
private MyObject object;
public ThreadB(MyObject object) {
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodB();
}
}
public class Run {
public static void main(String[] args) {
MyObject object = new MyObject();
ThreadA a = new ThreadA(object);
a.setName("A");
ThreadB b = new ThreadB(object);
b.setName("B");
a.start();
b.start();
}
}
methodB()方法加上synchronized前后,运行结果如下:
begin methodA threadName=A
begin methodA threadName=B begin time=1403574891268
end
end endTime=1403574896269
begin methodA threadName=A
end endTime=1403575081547
begin methodA threadName=B begin time=1403575081547
end
A线程先持有object对象的锁,B线程可以以异步的方式调用object的非synchronized方法
A线程先持有object对象的锁,B线程可以以异步的方式调用object的synchronized方法则需要等待,也就是同步。
1.5 脏读
脏读--读取实例变量时,此值已经被其他线程更改过了。
public class PublicVar {
public String username = "A";
public String password = "AA";
synchronized public void setValue(String username, String password) {
try {
this.username = username;
Thread.sleep(5000);
this.password = password;
System.out.println("setValue method thread name="
+ Thread.currentThread().getName() + " username="
+ username + " password=" + password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void getValue() {
System.out.println("getValue method thread name="
+ Thread.currentThread().getName() + " username=" + username
+ " password=" + password);
}
}
public class ThreadA extends Thread {
private PublicVar publicVar;
public ThreadA(PublicVar publicVar) {
super();
this.publicVar = publicVar;
}
@Override
public void run() {
super.run();
publicVar.setValue("B", "BB");
}
}
public class Test {
public static void main(String[] args) {
try {
PublicVar publicVarRef = new PublicVar();
ThreadA thread = new ThreadA(publicVarRef);
thread.start();
Thread.sleep(200);//
publicVarRef.getValue();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
出现脏读是因为public void getValue() 不是同步的,加上synchronized关键字前后的运行结果如下:
getValue method thread name=main username=B password=AA
setValue method thread name=Thread-0 username=B password=BB
setValue method thread name=Thread-0 username=B password=BB
getValue method thread name=main username=B password=BB
1.6 synchronized锁重入
当一个线程得到一个对象锁后,再次请求该对象的锁时,是可以再次得到次对象的锁的。
public class Service {
synchronized public void service1() {
System.out.println("service1");
service2();
}
synchronized public void service2() {
System.out.println("service2");
service3();
}
synchronized public void service3() {
System.out.println("service3");
}
}
public class MyThread extends Thread {
@Override
public void run() {
Service service = new Service();
service.service1();
}
}
public class Run {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
运行结果如下:
service1
service2
service3
可重入锁--自己可以再次获取自己的内部锁。比如一个线程获取了一个对象锁,此时还未释放锁当其再次想获取该对象的锁时还是可以获取的,不然的话会造成死锁。
可重入锁支持在父子继承的环境中。
public class Main {
public int i = 10;
synchronized public void operateIMainMethod() {
try {
i--;
System.out.println("main print i=" + i);
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class Sub extends Main {
synchronized public void operateISubMethod() {
try {
while (i > 0) {
i--;
System.out.println("sub print i=" + i);
Thread.sleep(100);
this.operateIMainMethod();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyThread extends Thread {
@Override
public void run() {
Sub sub = new Sub();
sub.operateISubMethod();
}
}
public class Run {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
运行结果如下:
sub print i=9
main print i=8
sub print i=7
main print i=6
...
子类是完全可以通过“可重入锁”调用父类的同步方法!
1.7 出现异常,锁自动释放
当一个线程执行的代码出现异常的时候,其所持有的锁会自动释放。不影响其他线程的执行。
1.8 同步不具有继承性
public class Main {
synchronized public void serviceMethod() {
try {
System.out.println("int main 下一步sleep begin threadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("int main 下一步sleep end threadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Sub extends Main {
@Override
synchronized public void serviceMethod() {
try {
System.out.println("int sub 下一步isleep begin threadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("int sub 下一步sleep end threadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
super.serviceMethod();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class MyThreadA extends Thread {
private Sub sub;
public MyThreadA(Sub sub) {
super();
this.sub = sub;
}
@Override
public void run() {
sub.serviceMethod();
}
}
public class MyThreadB extends Thread {
private Sub sub;
public MyThreadB(Sub sub) {
super();
this.sub = sub;
}
@Override
public void run() {
sub.serviceMethod();
}
}
public class Test {
public static void main(String[] args) {
Sub subRef = new Sub();
MyThreadA a = new MyThreadA(subRef);
a.setName("A");
a.start();
MyThreadB b = new MyThreadB(subRef);
b.setName("B");
b.start();
}
}
子类的方法再加入synchronized前后的运行结果如下:
int sub 下一步 sleep begin in threadName=A time=1405324477242
int sub 下一步 sleep begin in threadName=B time=1405324477242
...
int sub 下一步 sleep begin in threadName=A time=1405324477242
int sub 下一步 sleep end threadName=B time=1405324477242
...
可以看出继承后重写的方法是不能继承同步属性的,需要重新声明。
最后请各位大佬激情支持!