Java 多线程(二) synchronized关键字
2018-01-25 17:19
357 查看
1.概要
线程安全:多个线程同时访问公共对象或者同一个对象时,采用了加锁的机制,对公共数据进行保护,直到线程对该数据使用完。非线程安全:多个线程同时访问公共对象或者同一个对象时,发生数据不一致或者数据污染
脏读:读到的数据其实是被更改过的,数据不一致或者数据污染。
2.Synchronized方法与锁对象
[b]synchronized[/b]1.监视器:每个对象都有一个监视器(monitor),它允许每个线程同时互斥和协作,就像每个对象都会有一块受监控的区域
(数据结构),当线程执行需要取到监控区域的数据时,首先验证是否有线程拥有监视器,已有线程拥有监视器则进入监视
器的monior entry list进行等待Thread.state:BLOCKED,直到释放退出监控区域且释放锁。以这种FIFO的方式等待。
对象锁:每个对象在堆内存中的头部都会维持一块锁区域,任何线程要同步执行对象数据都会放入监视器且都必须获锁。
一个线程可以允许多次对同一对象上锁.对于每一个对象来说,java虚拟机维护一个计数器,记录对象被加了多少次锁,没被锁的
对象的计数器是0,线程每加锁一次,计数器就加1,每释放一次,计数器就减1.当计数器跳到0的时候,锁就被完全释放了。
synchronized工作机制是这样的:Java中每个对象都有一把锁与之相关联,
锁控制着对象的synchronized代码。一个要执行对象的synchronized代码
的线程必须先获得那个对象的锁。
[b]synchronized 同步方法[/b]
一个对象只有一把对象锁
synchronized获取的是对象锁,保证线程顺序进入对象方法。
public class test { public void testSynchronized(){ try { System.out.println("start:"+Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("end"); } catch (Exception e) { // TODO: handle exception } } public static void main(String[] args) { test t = new test(); ThreadA threadA = new ThreadA(t); threadA.setName("A"); threadA.start(); ThreadB threadB = new ThreadB(t); threadB.setName("B"); threadB.start(); } }
执行结果:可以看出线程是并行运行的。
start:A start:B end end
在方法加入同步synchronized关键字,执行结果:线程同步顺序执行
start:A end start:B end
锁重入:关键字synchronized拥有锁重入的功能,当一个线程得到对象锁后在当前线程能够再次获得此对象锁,这说明一个
线程在得到对象锁后可以无限制的获得对象锁。
public class test { public void methodA(){ System.out.println("非synchronized方法"); } public synchronized void testSynchronized(){ try { System.out.println("start:"+Thread.currentThread().getName()); methodB(); } catch (Exception e) { // TODO: handle exception } } public synchronized void methodB() throws InterruptedException{ Thread.sleep(5000); System.out.println("end"); } public static void main(String[] args) { test t = new test(); ThreadA threadA = new ThreadA(t); threadA.setName("A"); threadA.start(); ThreadB threadB = new ThreadB(t); threadB.setName("B"); threadB.start(); } }
执行结果:自己可以再次获取自己的内部对象锁,当线程获取到对象锁后在其内部还可以获得对象锁,如果锁不可重入的话
就很容易造成死锁,因为外部对象锁还未释放,导致在内部永远获取不到对象锁,线程永远处于等待。
start:A end start:B end
出现异常,锁会自动释放:当一个线程执行代码是出现异常时,会自动释放所持有的对象锁。
同步不具有继承性:当父类方法进行同步,子类重写该方法,子类方法不具有同步性,需要添加synchronized关键字。
[b]synchronized同步代码块[/b]
用synchronized同步方法是有弊端的,当方法某一条语句执行时间过长,就会导致其他线程需要等待较长时间。所以同步 代码块可以相对提高效率。
同步代码块的使用
synchronized需要依赖于对象锁,同步代码块是需要一个锁对象,可以是当前对象(this),一般系统并发量很高不采用当
前对象,而采用任意其他一个对象,不然造成大量线程等待在该对象。
public class test { public void methodA(){ System.out.println("非synchronized方法"); } public void testSynchronized(){ try { System.out.println("start:"+Thread.currentThread().getName()); synchronized (this) { methodB(); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } public synchronized void methodB() throws Exception{ Thread.sleep(5000); System.out.println("end"); } public static void main(String[] args) { test t = new test(); ThreadA threadA = new ThreadA(t); threadA.setName("A"); threadA.start(); ThreadB threadB = new ThreadB(t); threadB.setName("B"); threadB.start(); } }
执行结果:线程A、B并发进入方法,但有一个线程等待在同步代码块前。
start:A start:B end end
任意对象锁:在同步代码块不取this(当前对象)锁,而采用任意对象锁,这样的好处是当有很多synchronized同步方法时
,如果用this对象锁,会造成阻塞,而采用任意锁,其他同步块则不会造成阻塞。
public class test { private Object obj = new Object(); public void methodA(){ System.out.println("非synchronized方法"); } public void testSynchronized(){ try { System.out.println("start:"+Thread.currentThread().getName()); synchronized (this) { methodB(); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } public synchronized void methodB() throws Exception{ Thread.sleep(5000); System.out.println("end"); } public void methodC(){ synchronized (obj) { try { System.out.println("start:"+Thread.currentThread().getName()); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public static void main(String[] args) { test t = new test(); ThreadA threadA = new ThreadA(t); threadA.setName("A"); threadA.start(); ThreadB threadB = new ThreadB(t); threadB.setName("B"); threadB.start();//执行methodC } }
执行结果:线程A使用当前this对象锁同步代码块,线程B能够同步执行OBJ对象锁进行同步代码块
start:A start:B end
同步代码块解锁无限等待问题:同步方法获得的是当前对象的对象锁,一单其中一个同步方法陷入死循环,该对象的其他同
步方法都无限等待,所以同步需要同步的部分代码块且使用任意对象锁。
[b]synchronized同步静态方法[/b]
关键字synchronized还可以修饰static方法,如果这样写,那是对当前的.java文件的class对象进行加锁。
与实例方法取得不同的对象锁
Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识,即所谓的RTTI。这项信息纪录了
每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类。
Class类封装一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建。
public class test { private Object obj = new Object(); public synchronized void methodA(){ System.out.println("start not static :"+Thread.currentThread().getName()); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("static end"); } public synchronized static void testSynchronized(){ try { System.out.println("start:"+Thread.currentThread().getName()); methodB(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } public static void methodB() throws Exception{ Thread.sleep(5000); System.out.println("end"); } public static void main(String[] args) { test t = new test(); ThreadA threadA = new ThreadA(t); threadA.setName("A"); threadA.start(); ThreadB threadB = new ThreadB(t); threadB.setName("B"); threadB.start(); } }
执行结果:两个线程获取的是不同的对象锁,一个是test.classs对象锁,一个是test对象锁
start not static :A start:B static end end
String常量池类型锁
在JVM中String有常量池缓存的特性
什么事常量池?:
常量池(constant
pool)在编译期间被指定,并被保存在已编译的.class文件当中,用于存储关于类、方法、接口中 的常量,也包括字符串直接量。
String与常量池
String str1 = new String("abc")
String str2 = "abc";
上面是两种创建字符串的方式,看起来没有什么区别,但实则有很大区别
第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。
而第二种是先在栈中创建一个对String类的对象引用变量str2,然后通过符号引用去字符串常量池里找有没有"abc",如果没有
,则将"abc"存放进字符串常量池,并令str2指向”abc”,如果已经有”abc”
则直接令str2指向“abc”。
所以如果String str3 = “abc”;str2 和str3是同一对象,String str4 = “adcd”;str4和str2是不同的对象。
结论:如果使用String对象作为对象锁,必须要注意是否对象会改变;这就是String常量池带来的问题,一般不会用String做
为对象锁,而改用其他,比如 new Object()。
[b]死锁[/b]
由于不同的线程都在等待永远不能被释放的锁,从而导致任务不能继续执行。在多线程中死锁是必须避免的,会导致线程的
假死。
public class test { public synchronized void methodA(){ System.out.println("start:"+Thread.currentThread().getName()); try { Thread.sleep(5000); while(true){ } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("static end"); } public static void main(String[] args) { test t = new test(); ThreadA threadA = new ThreadA(t); threadA.setName("A"); threadA.start(); ThreadB threadB = new ThreadB(t); threadB.setName("B"); threadB.start(); } }
执行结果:程序会一直等待。
我们可以通过jstack 命令来查看jvm内线程状态,来找到死锁的地方。
[b]volatile关键字[/b]
volatile关键字的主要作用是使变量在多个线程中可见。
强制从公共的堆栈中取得变量的值,而不是在线程的私有栈中取变量的值。
解决同步死循环:
public class RunThread implements Runnable{ private boolean isRun = true; public void setIsRun(boolean flag){ this.isRun = flag; } @Override public void run() { // TODO Auto-generated method stub while(isRun == true){ try { Thread.sleep(2000); System.out.println("当前线程:"+Thread.currentThread().getName()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("end"); } }
public class test { public static void main(String[] args) { RunThread runthread = new RunThread(); new Thread(runthread).start(); System.out.println("我要停止了"); runthread.setIsRun(false); } }
执行结果:上面代码运行在64bitJVM,-server模式程序陷入死循环。解决办法是使用volatile关键字,从公共堆栈中取得数
据,而不是线程私有栈中。
volatile非原子性
public class MyThread extends Thread{ volatile private static int count = 0; public void run(){ addCount(); } public void addCount(){ for(int i = 0;i<100;i++){ count++; } System.out.println(count); } }
主类:
public class test { public static void main(String[] args) { MyThread [] threadArr = new MyThread[100]; for(int i=0;i<100;i++){ threadArr[i] = new MyThread(); } for(int i =0;i<100;i++){ threadArr[i].start(); } } }
执行结果
8500 8900 9100 9204 9298 9398 9498 9598 9698 9798 9898 9998
关键字volatile主要使用场合是在多线程中可以感知变量值更改了,并且可以获得最新的值,每次取值都是从共享内存中
取的数据,而不是从线程的私有内存中取得数据。
但如果修改实例变量,比如i++,这样的操作并不是一个原子操作,也是非线程安全的。表达式的步骤是:
1)从内存中取得i的值
2)计算i的值
3)将i的值写到主存中
如果在第二步计算值时,其他线程也在修改i的值,就会出现脏读。解决办法是使用synchronized,volatile只能保证从
每次从主存中取得i的值。所以说volatile并不具有原子性。而是强制数据的读写影响到主存中去。
相关文章推荐
- Java 多线程----synchronized关键字详解
- Java 多线程(六) synchronized关键字详解
- Java 多线程(六) synchronized关键字详解
- Java 多线程 —— synchronized关键字
- Java 多线程编程之三:synchronized 关键字的使用
- Java——多线程总结及ThreadLocal、Volatile、synchronized、Atomic四个关键字
- 初学Java多线程:使用Synchronized关键字同步类方法
- Java 多线程(六) synchronized关键字详解
- Java 多线程(六) synchronized关键字详解
- 【java多线程 关键字】synchronized
- Java 多线程并发编程之 Synchronized 关键字
- Java 多线程 synchronized关键字详解
- Java 多线程之synchronized关键字详解
- Java 多线程之synchronized关键字详解
- Java多线程系列--"基础篇"04之 synchronized关键字
- Java多线程基础篇(03)-synchronized关键字
- Java 多线程(六) synchronized关键字详解
- java关键字volatile和synchronized在多线程中的应用
- java多线程编程之使用Synchronized关键字同步类方法
- Java 多线程(六) synchronized关键字详解