java多线程系列(二)---对象变量并发访问
对象变量的并发访问
前言:本系列将从零开始讲解java多线程相关的技术,内容参考于《java多线程核心技术》与《java并发编程实战》等相关资料,希望站在巨人的肩膀上,再通过我的理解能让知识更加简单易懂。
目录
- 认识cpu、核心与线程
- java多线程系列(一)之java多线程技能
- java多线程系列(二)之对象变量的并发访问
- java多线程系列(三)之等待通知机制
- java多线程系列(四)之ReentrantLock的使用
- java多线程系列(五)之synchronized ReentrantLock volatile Atomic 原理分析
- java多线程系列(六)之线程池原理及其使用
线程安全
- 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
局部变量并不会数据共享
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
我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)
作者:jiajun 出处: http://www.cnblogs.com/-new/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。
java多线程系列(二)---对象变量并发访问的更多相关文章
- java多线程系列(二)
对象变量的并发访问 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...
- (Java多线程系列二)线程间同步
Java多线程间同步 1.什么是线程安全 通过一个案例了解线程安全 案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果. 先来看一个线程不安全的例子 class Sell ...
- Java多线程系列二——Thread类的方法
Thread实现Runnable接口并实现了大量实用的方法 public static native void yield(); 此方法释放CPU,但并不释放已获得的锁,其它就绪的线程将可能得到执行机 ...
- 【Java多线程系列二】Thread类的方法
Thread实现Runnable接口并实现了大量实用的方法. /* * 此方法释放CPU,但并不释放已获得的锁,其它就绪的线程将可能得到执行机会,它自己也有可能再次得到执行机会 */ public s ...
- Java多线程基础——对象及变量并发访问
在开发多线程程序时,如果每个多线程处理的事情都不一样,每个线程都互不相关,这样开发的过程就非常轻松.但是很多时候,多线程程序是需要同时访问同一个对象,或者变量的.这样,一个对象同时被多个线程访问,会出 ...
- Java多线程系列--“JUC线程池”03之 线程池原理(二)
概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...
- Java多线程系列--“JUC锁”04之 公平锁(二)
概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...
- Java多线程系列
一.参考文献 1.:Java多线程系列目录 (一) 基础篇 01. Java多线程系列--“基础篇”01之 基本概念 02. Java多线程系列--“基础篇”02之 常用的实现多线程的两种方式 03. ...
- java多线程系列(一)
java多线程技能 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...
随机推荐
- php命名空间的设计思想和缺点
相比C#等语言,你可以在PHP函数里面随意定义变量并赋值,而不用担心覆盖了全局变量,或者类变量:你也可以随意的定义类变量,而不用担心会和函数名冲突,因为变量前面都有个$. php的命名空间和全局变量. ...
- 第八章 计时器(BEEPER2)
/*------------------------------------- BEEPER2.C -- Timer Demo Program No.1 (c) Charles Petzold, 19 ...
- spark搭建部署
基础环境准备 安装JDK1.8+,并设置环境变量 搭建zookeeper集群 搭建Hadoop集群 Spark local模式 上传编译完成的spark安装程序到服务器上,并解压到指定目录 [root ...
- Redis info参数总结
可以看到,info的输出结果是分几块的,有Servers.Clients.Memory等等,通过info后面接这些参数,可以指定输出某一块数据. 下面是针对info的输出在旁边注释了,因为对Redis ...
- Java基础知识强化107:DecimalFormat
1. 引入: 如何控制输出数据的精度? >1. 使用Math.round方法 (1)Java如何把一个float(double)四舍五入到小数点后2位,4位,或者其它指定位数 ? 答:比如,如下 ...
- 自定义控件(视图)2期笔记14:自定义视图之View事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程
1. 这里我们先从案例角度说明dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程: (1)首先我们重写一个MyButton 继承自 Button ...
- 【转】numpy.random.randn()与rand()的区别
转自: https://blog.csdn.net/u010758410/article/details/71799142 numpy中有一些常用的用来产生随机数的函数,randn()和rand()就 ...
- 你都用python来做什么?
首页发现话题 提问 你都用 Python 来做什么? 关注问题写回答 编程语言 Python 编程 Python 入门 Python 开发 你都用 Python 来做什么? 发现很 ...
- SharePoint document 右键菜单和【...】菜单不一致的解决办法
[问题]在sharepoint 2016环境中,当用户只有read权限,访问文档库,会发现文档的右键菜单和[…]菜单的内如是不一致的,而更高权限用户都是一致的. [分析]这个跟微软询问过,是个bug, ...
- BAT面试总结——iOS开发高级工程师
序言 之前也面试别人,现在轮到自己找工作,怎么说呢,现在轮到自己出去面试,怎么说呢,其实还是挺紧张的,原以为自己不会因此紧张或者焦虑,实际上,还是有的,在没找到合适的工作的时候,甚至晚上有点睡不着觉, ...