java多线程系列(二)---对象变量并发访问

xiaoxiao2021-02-28  41

对象变量的并发访问

前言:本系列将从零开始讲解java多线程相关的技术,内容参考于《java多线程核心技术》与《java并发编程实战》等相关资料,希望站在巨人的肩膀上,再通过我的理解能让知识更加简单易懂。

目录

认识cpu、核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线程系列(三)之等待通知机制 java多线程系列(四)之Lock的使用

线程安全

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据

局部变量并不会数据共享

public class T1 { public static void main(String[] args) { PrivateNum p=new PrivateNum(); MyThread threadA=new MyThread('A',p); MyThread threadB=new MyThread('B',p); threadA.start(); threadB.start(); }} class MyThread extends Thread { char i; PrivateNum p; public MyThread(char i,PrivateNum p) { this.i=i; this.p=p; } public void run() { p.test(i); } } class PrivateNum { public void test( char i) { try { int num=0; if(i=='A') { num=100; System.out.println("线程A已经设置完毕"); Thread.sleep(1000); } else { num=200; System.out.println("线程B已经设置完毕"); } System.out.println("线程"+i+"的值:"+num); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); }} } 线程A已经设置完毕 线程B已经设置完毕 线程B的值:200 线程A的值:100 在这段代码中,线程A和B先后对num进行赋值,当两个线程都赋值后再分别打印,但是输出的结果并不相同,后赋值的线程并没有对num的值进行覆盖,因为这里的num是在方法里面的,也就是局部变量,不同线程的num并不共享,所以并不会发生覆盖。

实例成员变量数据共享

public void test( char i) { int num=0; try { if(i=='A') { num=100; System.out.println("线程A已经设置完毕"); Thread.sleep(1000); } else { num=200; System.out.println("线程B已经设置完毕"); } System.out.println("线程"+i+"的值:"+num); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); }} 线程B已经设置完毕 线程A已经设置完毕 线程B的值:200 线程A的值:200 这里的代码只是将int num=0放到了方法的外面,但是输出的结果却不同,因为这时候线程AB访问的是同一个变量(指向同一个地址),所以这个时候会发生覆盖,同时这里出现了线程安全问题。

synchronized关键字可以避免线程安全问题

public synchronized void test( char i) { int num=0; try { if(i=='A') { num=100; System.out.println("线程A已经设置完毕"); Thread.sleep(1000); } else { num=200; System.out.println("线程B已经设置完毕"); } System.out.println("线程"+i+"的值:"+num); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); }} 线程A已经设置完毕 线程A的值:100 线程B已经设置完毕 线程B的值:200 这里只是上面代码的基础上增加了一个synchronized ,避免了线程安全问题

总结

如果多个线程访问的是同一个对象方法中的局部变量,那么这个变量并不共享,线程AB对此变量的操作将互不影响 如果多个线程访问的是同一个对象方法中的成员变量,那么这个变量共享,如果不处理好线程问题,可能会出现线程安全问题 通过synchronized关键字可以使方法同步

多个线程访问的是两个不同实例的同一个同步方法

public class T1 { public static void main(String[] args) { PrivateNum p1=new PrivateNum(); PrivateNum p2=new PrivateNum(); MyThread threadA=new MyThread('A',p1); MyThread threadB=new MyThread('B',p2); threadA.start(); threadB.start(); }} 线程A已经设置完毕 线程B已经设置完毕 线程B的值:200 线程A的值:100 这里的代码又是在上面的代码进行修改,这里我们添加了synchronized关键字,对方法上锁,但是却是异步执行的(同步的话,应该是这样输出 线程A已经设置完毕 线程A的值:100 ),这是因为这里是两个锁,创建了p1和p2对象,创建的是两个锁,锁对象不同不造成互斥作用。

多线程调用同一个实例的两个不同(一个同步,一个非同步)方法

public class T1 { public static void main(String[] args) { PrivateNum p1=new PrivateNum(); MyThread threadA=new MyThread('A',p1); MyThread2 threadB=new MyThread2('B',p1); threadA.start(); threadB.start(); }} class MyThread extends Thread { char i; PrivateNum p; public MyThread(char i,PrivateNum p) { this.i=i; this.p=p; } public void run() { p.test(i); } } class MyThread2 extends Thread { char i; PrivateNum p; public MyThread2(char i,PrivateNum p) { this.i=i; this.p=p; } public void run() { p.test2(i); } } class PrivateNum { int num=0; public void test2(char i) { System.out.println("线程"+i+"执行,线程A并没有同步执行"); } public synchronized void test( char i) { try { if(i=='A') { num=100; System.out.println("线程A已经设置完毕"); Thread.sleep(100); } else { num=200; System.out.println("线程B已经设置完毕"); } System.out.println("线程"+i+"的值:"+num); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); }} } 线程A已经设置完毕 线程B执行,线程A并没有同步执行 线程A的值:100 线程B的值:200 这里的代码我们给PrivateNum添加了一个非同步的test2方法,MyThreadrun中调用的同步的test方法,MyThread2中调用的是非同步的test2方法,实验表明线程B可以异步调用非同步的方法。

多线程调用同一个实例的两个不同的同步方法

public synchronized void test2(char i) { System.out.println("线程"+i+"执行,线程A同步执行"); } 线程A已经设置完毕 线程A的值:100 线程B执行,线程A同步执行 线程B的值:200 这里的代码我们只是给test2方法添加一个synchronized关键字,这个时候两个线程调用的方法同步执行。

总结

多个线程调用的不同实例的同步方法,线程不互斥。 如果两个线程的锁对象一样(都是p1),两个线程分别调用同步方法和非同步方法,线程不会同步执行。 但是如果调用两个不同的同步方法,因为锁对象一致,两个线程同步执行。 设想一个情况,有一个实例有两个方法,一个修改值(synchronized),一个读值(非synchronized),此时两个线程一个修改值,一个读取值,这个时候因为这两个线程并不会挣抢锁,两个线程互不影响,那么此时可能就会出现一种情况,线程A还没修改完,线程B就读取到没有修改的值。这就是所谓的脏读。

重入锁

public class T1 { public static void main(String[] args) { MyThread3 thread=new MyThread3(); thread.start(); }} class MyThread3 extends Thread { Service s=new Service(); public void run() { s.service1(); } } class Service { public synchronized void service1() { System.out.println("服务1并没有被锁住"); service2(); } public synchronized void service2() { System.out.println("服务2并没有被锁住"); service3(); } public synchronized void service3() { System.out.println("服务3并没有被锁住"); } } 服务1并没有被锁住 服务2并没有被锁住 服务3并没有被锁住 我们可能会这么认为,thread线程执行了同步的service1方法,这个时候把锁占住,如果这个时候要执行另一个同步方法service2方法,必须先执行完service1方法,然后把锁让出去才行,但是实验证明锁是可以重入的,一个线程获得锁后,还没释放后可以再次获取锁。

出现异常会释放锁

如果同步方法里面出现异常,会自动将锁释放

同步方法不会继承

public class T1 { public static void main(String[] args) { Service3 s=new Service3(); MyThread4 t1=new MyThread4(s,'1'); MyThread4 t2=new MyThread4(s,'2'); t1.start(); t2.start(); }} class MyThread4 extends Thread { Service3 s; char name; public MyThread4(Service3 s,char name) { this.s=s; this.name=name; } public void run() { s.service(name); } } class Service2 { public synchronized void service(char name) { for (int i = 3; i >0; i--) { System.out.println(i); } } } class Service3 extends Service2 { public void service(char name) { for (int i = 5; i >0; i--) { System.out.println("线程"+name+":"+i); } } } 线程1:5 线程2:5 如果父类的方法是同步的,如果子类重载同步方法,但是没有synchronized关键字,那么是没有同步作用的。

总结

重入锁,一个获得的锁的线程没执行完可以继续获得锁。 线程占用锁的时候,如果执行的同步出现异常,会将锁让出。 父类方法同步,子类重写该方法(没有synchronized关键字修饰),是没有同步作用的。

同步代码块

public class T1 { public static void main(String[] args) { Service2 s=new Service2(); MyThread t1=new MyThread(s,'A'); MyThread t2=new MyThread(s,'B'); t1.start(); t2.start(); } } class Service2 { public void service(char name) { synchronized(this) { for (int i = 3; i >0; i--) { System.out.println(name+":"+i); } } } } class MyThread extends Thread { Service2 s=new Service2(); char name; public MyThread(Service2 s,char name) { this.s=s; this.name=name; } public void run() { s.service(name); } } A:3 A:2 A:1 B:3 B:2 B:1 当多个线程访问同一个对象的synchronized(this)代码块时,一段时间内只有一个线程能执行

同步代码块的锁对象

class Service2 { String s=new String("锁"); public void service(char name) { synchronized(s) { for (int i = 3; i >0; i--) { System.out.println(name+":"+i); } } } } 将this换成自己创建的锁(一个对象),同样可以实现同步功能

部分同步,部分异步

public void service(char name) { for (int i = 6; i >3; i--) { System.out.println(name+":"+i); } synchronized(this) { for (int i = 3; i >0; i--) { System.out.println(name+":"+i); } } } A:6 B:6 A:5 B:5 A:4 B:4 A:3 A:2 A:1 B:3 B:2 B:1 不在同步代码块中的代码可以异步执行,在同步代码块中的代码同步执行

不同方法里面的synchronized代码块同步执行

public class T1 { public static void main(String[] args) { Service2 s=new Service2(); MyThread t1=new MyThread(s,'A'); MyThread2 t2=new MyThread2(s,'B'); t1.start(); t2.start(); } } class Service2 { public void service(char name) { synchronized(this) { for (int i = 3; i >0; i--) { System.out.println(name+":"+i); } } } public void service2(char name) { synchronized(this) { for (int i = 6; i >3; i--) { System.out.println(name+":"+i); } } } } class MyThread extends Thread { Service2 s=new Service2(); char name; public MyThread(Service2 s,char name) { this.s=s; this.name=name; } public void run() { s.service(name); } } class MyThread2 extends Thread { Service2 s=new Service2(); char name; public MyThread2(Service2 s,char name) { this.s=s; this.name=name; } public void run() { s.service2(name); } } A:3 A:2 A:1 B:6 B:5 B:4 两个线程访问同一个对象的两个同步代码块,这两个代码块是同步执行的

锁不同没有互斥作用

class Service2 { Strign s=new String(); public void service(char name) { synchronized(s) { for (int i = 3; i >0; i--) { System.out.println(name+":"+i); } } } public void service2(char name) { synchronized(this) { for (int i = 6; i >3; i--) { System.out.println(name+":"+i); } } } } 将this改成s,也就是改变锁对象,发现两个方法并不是同步执行的

synchronized方法和synchronized(this)代码块是锁定当前对象的

public void service(char name) { synchronized(this) { for (int i = 3; i >0; i--) { System.out.println(name+":"+i); } } } public synchronized void service2(char name) { for (int i = 6; i >3; i--) { System.out.println(name+":"+i); } } 将service2的代码块改成synchronized 方法,发现输出结果是同步的的,说明锁定的都是当前对象

总结

同步代码块的锁对象可以是本对象,也可以是其他对象。同一个锁对象可以产生互斥作用,不同锁对象不能产生互斥作用 一个方法中有同步代码块和非同步代码块,同步代码块的代码是同步执行的(块里的代码一次执行完),而非同步代码块的代码可以异步执行 一个对象中的不同同步代码块是互斥的,执行完一个代码块再执行另一个代码块 同步代码块(this)和synchronized方法的锁定的都是当前对象 this

syncronized static 同步静态方法

class Service2 { public synchronized static void service() { for (int i = 3; i >0; i--) { System.out.println(name+":"+i); } } } 在这里锁对象就不是service对象,而是Class(Class(和String Integer一样)是一个类)

Class锁

class Service2 { public static void service() { synchronized(Service.class) { for (int i = 3; i >0; i--) { System.out.println(name+":"+i); } } } } 这里的效果和上面静态的synchronized一样

静态类中非静态同步方法

public class T1 { public static void main(String[] args) { Service.Service2 s=new Service.Service2(); Thread t1=new Thread(new Runnable() {public void run(){s.service();}}); Thread t2=new Thread(new Runnable() {public void run(){Service.Service2.service2();}}); t1.start(); t2.start(); } } class Service{ static class Service2 { public synchronized void service() { for (int i = 20; i >10; i--) { System.out.println(i); } } public static synchronized void service2() { for (int i = 9; i >3; i--) { System.out.println(i); } } }} //不同步执行 这里service方法的锁还是service对象,

总结

Class类也可以是锁,Class锁的实现可以通过静态的synchronizd方法,也可以通过静态方法里面的同步代码块(锁对象为Class) 静态类的同步方法锁对象还是该类的一个实例

死锁

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代码顺序执行了"); } } } } } 线程AB开始执行时,因为锁不同,所以不互斥,A当执行到另一个同步代码块(锁2)的时候,由于这个时候锁给线程B占有了,所以只能等待,同样B线程也是如此,AB互相抢对方的锁,但是所以造成了死锁。

锁对象发生改变

修改锁对象的属性不印象结果,比如此时锁对象为user对象,我把user的name设为jiajun,此时不影响结果

volatile

public class Tee { public static void main(String[] args) { try { RunThread thread = new RunThread(); thread.start(); Thread.sleep(1000); thread.setRunning(false); System.out.println("已经赋值为false"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class RunThread extends Thread { private boolean isRunning = true; public boolean isRunning() { return isRunning; } public void setRunning(boolean isRunning) { this.isRunning = isRunning; } @Override public void run() { System.out.println("进入run了"); while (isRunning == true) { } System.out.println("线程被停止了!"); } } 在这里,把vm运行参数设置为-server(右键运行配置,自变量那里可以设置,server参数可以提高运行性能),结果发现虽然我们将值设置为false,但是却仍然进入死循环。 isRunning变量存放在公共堆栈和线程的私有堆栈中,我们对他赋值为false时,只对公共堆栈进行更新,而但我们设置为-server后,读取的是线程私有栈的内容,所以也就造成了死循环。我们可以在isRunning变量前加上volatite关键字,这个时候访问的是公共堆栈,就不会造成死循环了。 以前我们使用单线程的时候,这种情况情况不会发生,但是当多个线程进行读写操作的时候就可能爆发出问题,这是因为我们没有用同步机制来保证他,这是我们需要注意的一点。 public class Tee { public static void main(String[] args) { MyThread[] mythreadArray = new MyThread[100]; for (int i = 0; i < 100; i++) { mythreadArray[i] = new MyThread(); } for (int i = 0; i < 100; i++) { mythreadArray[i].start(); } } class MyThread extends Thread { volatile public static int count; private static void addCount() { for (int i = 0; i < 100; i++) { count++; } System.out.println("count=" + count); } @Override public void run() { addCount(); } } 在这里我们只count前添加volatile,但是最终结果输出的并不是10000,说明并没有同步的作用,volatile不处理数据原子性(i++不是原子操作) 我们将volatile去掉,将addcount方法用synchronized修饰,发现输出了10000,说明了synchronized的同步作用不仅保证了对同一个锁线程的互斥,还保证了数据的同步。

总结

volitate增加了实例变量在对个线程之间的可见性,保证我们获得的是变量的最新值。 volatile在读上面保持了同步作用,但是在写上面不保持同步 synchronized的同步作用不仅保证了对同一个锁线程的互斥,还保证了数据的同步

volatile对比synchronized

两者修饰的不同,volatile修饰的是变量,synchronized修饰的是方法和代码块 两者的功能不同。volatile保证数据的可见性,synchronized是线程同步执行(间接保证数据可见性,让线程工作内存的变量和公共内存的同步) volatile性能比synchronized性能高

用原子类实现i++同步

class MyThread extends Thread { static AtomicInteger count=new AtomicInteger(0); private static void addCount() { for (int i = 0; i < 100; i++) { count.incrementAndGet(); } System.out.println(count.get()); } @Override public void run() { addCount(); } } 将上面的count++进行用原子类AtomicInteger改变,最后输出了1000

我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)

转载请注明原文地址: https://www.6miu.com/read-44792.html

最新回复(0)