之前几个文章中我已经说明了”线程安全“和”非线程安全“。
”非线程安全“其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是”脏读“,也就是取到的数据其实是被更改过得。而”线程安全“就是以获得的实例变量的值是进过同步处理的,不会出现藏独的现象。
1.方法内的变量为线程安全
”非线程安全“问题存在于”实例变量“中,如果是方法内部的私有变量,则不存在”非线程安全“问题,所得结果也就是”线程安全“的了。
下面我以一段代码说明,在实现方法内部声明一个变量时,是不存在”非线程安全“问题得。
示例代码:
方法代码:
package test;
/**
* @Author LiBinquan
*/
public class HasSelfPrivateNum {
public void addNum(String userName){
try{
int num = 0;
if ("a".equals(userName)){
num = 100;
System.out.println("a set over");
Thread.sleep(1000);
}else{
num = 200;
System.out.println("b set over");
}
System.out.println(userName +" num = "+num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
线程1:
package test;
import java.util.Random;
/**
* @Author LiBinquan
*/
public class ThreadTest1 extends Thread{
private HasSelfPrivateNum name;
public ThreadTest1(HasSelfPrivateNum name){
super();
this.name = name;
}
@Override
public void run() {
super.run();
name.addNum("a");
}
}
线程2:
package test;
import java.util.Random;
/**
* @Author LiBinquan
*/
public class ThreadTest2 extends Thread{
private HasSelfPrivateNum name;
public ThreadTest2(HasSelfPrivateNum name){
super();
this.name = name;
}
@Override
public void run() {
super.run();
name.addNum("b");
}
}
运行类代码:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
HasSelfPrivateNum num = new HasSelfPrivateNum();
ThreadTest1 threadTest1 = new ThreadTest1(num);
threadTest1.start();
ThreadTest2 threadTest2 = new ThreadTest2(num);
threadTest2.start();
}
}
输出:
由输出可见,方法中的变量不存在非线程安全问题,永远都是线程安全的。这是方法内部的变量时私有的特性造成的。
2.实例变量非线程安全
如果多个线程共同访问1个对象中的实例变量,则有可能出现”非线程安全“问题。
用线程访问的对象中如果有多个实例变量,则运行的结果有可能出现交叉情况。
我们将HasSelfPrivateNum类中的方法修改一下,
代码如下:
package test;
/**
* @Author LiBinquan
*/
public class HasSelfPrivateNum {
private int num =0;
public void addNum(String userName){
try{
if ("a".equals(userName)){
num = 100;
System.out.println("a set over");
Thread.sleep(1000);
}else{
num = 200;
System.out.println("b set over");
}
System.out.println(userName +" num = "+num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
输出结果:
由输出的内容我们可以看出,如果两个线程同时操作业务对象中的实例变量,则有可能出现”非线程安全“问题。这边我们只需要将HasSelfPrivateNum类中的方法加上synchronized即可。
代码如下:
package test;
/**
* @Author LiBinquan
*/
public class HasSelfPrivateNum {
private int num =0;
synchronized public void addNum(String userName){
try{
if ("a".equals(userName)){
num = 100;
System.out.println("a set over");
Thread.sleep(1000);
}else{
num = 200;
System.out.println("b set over");
}
System.out.println(userName +" num = "+num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
输出:
结论:在两个线程范文同一个对象中的同步方法时一定是线程安全的。
3.多个对象多个锁
这个我们还是以上面的测试代码为例,我们只需要将输出类改为:
代码如下:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
HasSelfPrivateNum num1 = new HasSelfPrivateNum();
HasSelfPrivateNum num2 = new HasSelfPrivateNum();
ThreadTest1 threadTest1 = new ThreadTest1(num1);
threadTest1.start();
ThreadTest2 threadTest2 = new ThreadTest2(num2);
threadTest2.start();
}
}
输出:
由上输出可得出,两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果却是以一部的方式运行的。这个测试用例中创建了2个业务对象,在系统中产生出2个锁,所以运行结果是异步的,打印的效果就是先打印b,然后打印a。
从上面程序运行结果看,虽然在HasSelfPrivateNum中使用了synchronized关键字,但打印的顺序确实不同步的,是交叉的。
从这边可以看出synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当做锁,所以在上面的示例中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。
但如果多个线程访问多个对象,则JVM会创建多个锁。
4.synchronized方法与锁对象
下面我们来证明一下线程锁的是对象。
测试代码:
方法类:
package test;
/**
* @Author LiBinquan
*/
public class MyObject {
public void methodA(){
try {
System.out.println("开始 methodA 线程名称 = "+Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("结束");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
线程1 :
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest1 extends Thread{
private MyObject object;
public ThreadTest1(MyObject object){
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodA();
}
}
线程2:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest2 extends Thread{
private MyObject object;
public ThreadTest2(MyObject object){
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodA();
}
}
运行类:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
MyObject object = new MyObject();
ThreadTest1 threadTest1 = new ThreadTest1(object);
threadTest1.setName("A");
ThreadTest2 threadTest2 = new ThreadTest2(object);
threadTest2.setName("B");
threadTest1.start();
threadTest2.start();
}
}
输出:
我们在MyObject类中的方法加上synchronized,
代码如下:
package test;
/**
* @Author LiBinquan
*/
public class MyObject {
synchronized public void methodA(){
try {
System.out.println("开始 methodA 线程名称 = "+Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("结束");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
输出结果
通过上面的测试,可以得出结论,调用关键字synchronized声明的方法一定是排队运行的。另外需要记住”共享“这2个字,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本就没有同步的必要。
那其他的方法在被调用是的效果,和如何查看Lock锁对象的效果。
测试代码:
方法类:
package test;
/**
* @Author LiBinquan
*/
public class MyObject {
synchronized public void methodA(){
try {
System.out.println("开始 methodA 线程名称 = "+Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("结束时间 = "+System.currentTimeMillis());
}catch (InterruptedException e){
e.printStackTrace();
}
}
public void methodB(){
try{
System.out.println("开始 methodB 线程名称 = "+Thread.currentThread().getName());
System.out.println("开始时间 = "+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("end");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
线程1 :
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest1 extends Thread{
private MyObject object;
public ThreadTest1(MyObject object){
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodA();
}
}
线程2:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest2 extends Thread{
private MyObject object;
public ThreadTest2(MyObject object){
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodB();
}
}
运行类:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
MyObject object = new MyObject();
ThreadTest1 threadTest1 = new ThreadTest1(object);
threadTest1.setName("A");
ThreadTest2 threadTest2 = new ThreadTest2(object);
threadTest2.setName("B");
threadTest1.start();
threadTest2.start();
}
}
输出:
通过输出我们可以得知,虽然线程A线程持有object对象锁,但是线程B完全可以异步调用非synchronized类型的方法。
接下来我们家methodB()方法前加上synchronized关键字,
代码如下:
package test;
/**
* @Author LiBinquan
*/
public class MyObject {
synchronized public void methodA(){
try {
System.out.println("开始 methodA 线程名称 = "+Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("结束时间 = "+System.currentTimeMillis());
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized public void methodB(){
try{
System.out.println("开始 methodB 线程名称 = "+Thread.currentThread().getName());
System.out.println("开始时间 = "+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("end");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
输出:
有输出可以得出2个结论:
1.A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。
2.A线程先持有object对象Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需要等待,也就是同步。
5.脏读
在上面的我已经实现多个线程调用同一个方法时,为了避免数据出现交叉的情况,使用synchronized关键字来进行同步。
虽然在复制时进行了同步,但在取值时有可能出现一些意想不到的意外,这种情况就是脏读,发生脏读的情况是在读取实例变量时,此值已经被其它线程更改过了。
示例:
package test;
/**
* @Author LiBinquan
*/
public class PublicVar {
public String userName = "A";
public String passWard = "AA";
synchronized public void setValue(String userName,String passWard){
try{
this.userName = userName;
Thread.sleep(5000);
this.passWard = passWard;
System.out.println("setValue threadName = "+Thread.currentThread().getName()+" userName = "+userName + " passWard = "+passWard);
}catch (InterruptedException e){
e.printStackTrace();
}
}
public void getValue(){
System.out.println("getValue threadName = "+Thread.currentThread().getName()+" userName = "+userName+" passWard = "+passWard);
}
}
线程类:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest1 extends Thread{
private PublicVar publicVar;
public ThreadTest1(PublicVar publicVar){
super();
this.publicVar = publicVar;
}
@Override
public void run() {
super.run();
publicVar.setValue("B","BB");
}
}
运行类:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
PublicVar publicVar = new PublicVar();
ThreadTest1 threadTest1 = new ThreadTest1(publicVar);
threadTest1.start();
Thread.sleep(200);//打印的结果受到此值大小影响
publicVar.getValue();
}
}
输出:
出现脏读是因为public void getValue()方法并不是同步的,所以可以在任意时候进行调用,解决办法是在getValue方法前加上synchronized关键字
修改后代码如下:
package test;
/**
* @Author LiBinquan
*/
public class PublicVar {
public String userName = "A";
public String passWard = "AA";
synchronized public void setValue(String userName,String passWard){
try{
this.userName = userName;
Thread.sleep(5000);
this.passWard = passWard;
System.out.println("setValue threadName = "+Thread.currentThread().getName()+" userName = "+userName + " passWard = "+passWard);
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized public void getValue(){
System.out.println("getValue threadName = "+Thread.currentThread().getName()+" userName = "+userName+" passWard = "+passWard);
}
}
输出:
可见,方法setValue和getValue被依次执行。通过这个案例不仅要知道脏读是通过synchronized关键字解决的,还要知道如下内容:
当A线成调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确的讲,是获得了对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,但B线程可以随意调用其他非synchronized同步方法。
当A线成调用anyObject对象加入synchronized关键字的X方法时,A线成就获得了X方法所在对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,必须等A线程将X方法执行完,也就是释放对象锁后才可以调用。这时A线程已经执行了一个完整的任务,也就是说userName和passWard这两个实例变量已经同时被赋值,不存在脏读的基本环境。
脏读一定会出现操作实例变量的情况下,这就是不同线程”争抢“实例变量的结果。
6.synchronized锁重入
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象后,再次请求此对象锁时,是可以再次得到该对象的锁的。这也证明在一个synchronized方法/块的内部调用本类的其它synchronized方法/块时,是永远可以得到锁的。
方法类:
package test;
/**
* @Author LiBinquan
*/
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");
}
}
线程类:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest extends Thread{
@Override
public void run() {
Service service = new Service();
service.service1();
}
}
运行类:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
ThreadTest threadTest = new ThreadTest();
threadTest.start();
}
}
输出:
”可重入锁“的概念是:自己可以再次获取自己的内部锁。比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。
可重入锁也支持在父子类继承的环境中。
父类代码:
package test;
/**
* @Author LiBinquan
*/
public class Main {
public int i = 10;
synchronized private void operateIMainMethod(){
try{
i--;
System.out.println("main print i="+i);
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
子类代码:
package test;
/**
* @Author LiBinquan
*/
public class Sub extends Main{
synchronized public void operateIMainMethod(){
try{
while (i > 0){
i--;
System.out.println("sub print i=" +i);
Thread.sleep(100);
this.operateIMainMethod();
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
线程类:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest extends Thread{
@Override
public void run() {
Sub sub = new Sub();
sub.operateIMainMethod();
}
}
运行类:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
ThreadTest threadTest = new ThreadTest();
threadTest.start();
}
}
运行结果:
这个示例说明,当存在父子类继承关系时,自雷是完全可以通过“可重入锁”调用父类的同步方法的。
7.出现异常,锁自动释放
当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
示例代码:
方法类:
package test;
/**
* @Author LiBinquan
*/
public class Service {
synchronized public void testMethod() {
if ("A".equals(Thread.currentThread().getName())) {
System.out.println("ThreadName = " + Thread.currentThread().getName() + " run beginTime = " + System.currentTimeMillis());
int i = 1;
while (i == 1) {
if (("" + Math.random()).substring(0, 8).equals("0.123456")) {
System.out.println("ThreadName = " + Thread.currentThread().getName() + " run exceptionTime = " + System.currentTimeMillis());
Integer.parseInt("a");//
}
}
}else{
System.out.println("Thread B run Time = " + System.currentTimeMillis());
}
}
}
线程1:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest1 extends Thread{
private Service service;
public ThreadTest1(Service service){
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
线程2:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest2 extends Thread{
private Service service;
public ThreadTest2(Service service){
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
运行类:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
ThreadTest1 threadTest1 = new ThreadTest1(service);
threadTest1.setName("A");
threadTest1.start();
Thread.sleep(500);
ThreadTest2 threadTest2 = new ThreadTest2(service);
threadTest2.setName("B");
threadTest2.start();
}
}
运行结果:
由输出我们可以看出,线程A出现异常并释放了锁,线程B进入方法正常打印,可以得出我们的结论就是,出现异常时锁会被自动释放。
8.同步不具有继承性
同步不可以继承。
测试代码:
父类代码:
package test;
/**
* @Author LiBinquan
*/
public class Main {
synchronized public void serviceMethod(){
try{
System.out.println("main 下一步 sleep begin threadName = "+Thread.currentThread().getName()+" time ="+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("main 下一步 sleep begin threadName = "+Thread.currentThread().getName()+" time ="+System.currentTimeMillis());
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
子类代码:
package test;
/**
* @Author LiBinquan
*/
public class Sub extends Main{
public void serviceMethod(){
try{
System.out.println("main 下一步 sleep begin threadName = "+Thread.currentThread().getName()+" time ="+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("main 下一步 sleep begin threadName = "+Thread.currentThread().getName()+" time ="+System.currentTimeMillis());
super.serviceMethod();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
线程1:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest1 extends Thread{
private Sub sub;
public ThreadTest1(Sub sub){
super();
this.sub = sub;
}
@Override
public void run() {
sub.serviceMethod();
}
}
线程2:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest2 extends Thread{
private Sub sub;
public ThreadTest2(Sub sub){
super();
this.sub = sub;
}
@Override
public void run() {
sub.serviceMethod();
}
}
运行类:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
Sub sub = new Sub();
ThreadTest1 threadTest1 = new ThreadTest1(sub);
threadTest1.setName("A");
threadTest1.start();
Thread.sleep(500);
ThreadTest2 threadTest2 = new ThreadTest2(sub);
threadTest2.setName("B");
threadTest2.start();
}
}
输出:
从此处我们可以得出,同步不能继承,所以还得在子类中的方法加上synchronized关键字,
加上后的输出:
可以看到这样就同步了
转载:https://blog.csdn.net/qq_27581753/article/details/105900080