非线程安全会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是脏读,脏读即取到的数据是已经被其他线程更改过的。
一、线程安全
方法内的变量是线程安全。“非线程安全”问题存在于实例变量中,但如果是方法内部的私有变量则不存在“非线程安全”问题,则所得的结果就是“线程安全”的了。
/*
* 多个线程对num进行计算时。
*/
public class HasSelfPrivateNum {
//private int num=0;非方法内部的变量,则会发生“非线程安全”。
public void addI(String username){
int num=0;//方法内部的私有变量,则不会发生“非线程安全”。
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){
e.printStackTrace();
}
}
}
二、非线程安全
1.多个线程访问同一个对象中的同步方法时一定是线程安全的。同步方法即在方法前加上关键字synchronzied。
/*
*添加关键字synchronized解决非线程安全问题。
*/
synchronized 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){
e.printStackTrace();
}
}
}
2.两个线程分别访问同一个类的两个不同实例的相同名称的同步方法时,效果是以异步的方式运行的。
HasSelfPrivateNum.java如上代码。
Threada.java
public class Threada extends Thread{
private HasSelfPrivateNum numRef;
public Threada(HasSelfPrivateNum numRef){
super();
this.numRef=numRef;
}
public void run(){
super.run();
numRef.addI("a");
}
}
Threadb.java
public class Threadb extends Thread{
private HasSelfPrivateNum numRef;
public Threadb(HasSelfPrivateNum numRef){
super();
this.numRef=numRef;
}
public void run(){
super.run();
numRef.addI("b");
}
}
Run1.java
public class Run1 {
public static void main(String[] args){
HasSelfPrivateNum numRef1=new HasSelfPrivateNum();
HasSelfPrivateNum numRef2=new HasSelfPrivateNum();
Threada a=new Threada(numRef1);
a.start();
Threadb b=new Threadb(numRef2);
b.start();
}
}
j结果:
虽然在HasSelfPrivateNum中使用了同步的关键字,但是打印的顺序却是异步的。因为synchronized取得的锁是对象锁,不是把一段代码或者方法的函数当做锁,哪个线程先执行带有同步的方法,则此线程就持有该方法所属对象的lock,其他线程就属于等待状态直到此线程结束运行,因此synchronized生命的方法一定是排队运行的,但是前提是这多个线程访问的是同一个对象,结果如下图。但是如果多个线程访问多个对象,则jvm会创建多个锁,即一个对象一个锁,即同上代码运行的结果是异步的。
3.多个线程访问同一个对象中的不同的方法。
总结一:A线程先持有对象的lock,B线程可以以异步的方式调用对象中的非synchronized类型的方法。
总结二:A线程先持有对象的lock,B线程如果在这时想条用对象中的synchronized方法则需要等待,即同步。
MyObject1.java
public class MyObject1 {
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();
}
}
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();
}
}
}
MyObject包含两个方法,一个同步方法,一个非同步方法,线程A调用methodA,线程B调用methodB时,结果如下:
若把methodB变成同步方法,则结果如下:
即线程B要等待A 结束以后才开始运行。
4.synchronized锁重入
(1)synchronized锁重入即在一个synchronized方法内部调用本类的其他synchronized方法时,是永远可以得到锁的。
如下:
public class service{
synchronized public void service1(){
System.out.println("service1");
service2();
}
synchronized pulic void service2(){
System.out.println("service2");
service3();
}
synchronized public void service3(){
System.out.println("service3");
}
}
(2)可重入锁也支持在父子类继承的环境中。
Main.java
public class Main {
public int i=10;
synchronized public void MainMethod(){
try{
i=i-2;
System.out.println("main print i="+i);
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
Sub.java
public class Sub extends Main{
synchronized public void operateSub(){
try{
while(i>0){
i--;
System.out.println("sub print i="+i);
Thread.sleep(100);
this.MainMethod();
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
MyThread.java
public class MyThread extends Thread{
public void run(){
Sub sub=new Sub();
sub.operateSub();
}
}
Run2.java
public class Run2 {
public static void main(String[] args){
MyThread thread=new MyThread();
thread.start();
}
}
结果:
此实验说明,当存在父子类继承关系时,子类是完全可以通过可重入锁条用父类的同步方法的。
5.出现异常时,锁自动释放。
6.同步不具有继承性。即使父类的方法时同步方法,但那也是父类的,子类的方法要想也是同步的,则需要再子类的方法中子星添加关键字。