首页 > 程序开发 > 软件开发 > Java >

java多线程之对象的并发访问

2016-09-19

java多线程之对象的并发访问

1.synchronized同步方法

--1.1方法内的变量是线程安全的

解释:由于方法内的变量是私有的,本体访问的同时别人访问不了,所以是线程安全的。

--1.2实例变量是非线程安全的

解释:由于实例变量可以由多个线程访问,当本体操作变量过程中,别人也可以抢占资源操作变量,使数据不同步了,所以是非线程安全的。(之前博文已经实现过这种情况)

--1.3线程与锁

例:

public class Service {//服务类

	private int age=0;
	public synchronized void get(String username){//同步方法
		try{
			if(username.equals("张三")){
				age=20;
				System.out.println("张三获取年龄");
				Thread.sleep(2000);
			}else{
				age=30;
				System.out.println("李四获年龄");
			}
			System.out.println(username+",age="+age);
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

public class MyThread1 extends Thread{//线程1

    private Service service;
    
    public MyThread1(Service service) {
        super();
        this.service = service;
    }
    
    @Override
    public void run() {
        service.get("张三");
    }
}

public class MyThread2 extends Thread{//线程2

    private Service service;

    public MyThread2(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.get("李四");
    }
}

----1.3.1多个线程访问同一个锁(前提)

测试类1:

public static void main(String[] args) {

		Service service=new Service();
		MyThread1 myThread1=new MyThread1(service);
		myThread1.start();
		MyThread2 myThread2=new MyThread2(service);
		myThread2.start();
	}

解释:我们的前提是多个线程访问同一个锁,如果当前进来一个线程获得同步方法,那么当前这个线程就持有了该方法所属对象的锁,其他线程就只能处于等待态。

结果:

张三获取年龄

张三,age=20

李四获年龄

李四,age=30

----1.3.2多个线程访问多个锁

public static void main(String[] args) {

		Service service1=new Service();
		Service service2=new Service();
		MyThread1 myThread1=new MyThread1(service1);
		myThread1.start();
		MyThread2 myThread2=new MyThread2(service2);
		myThread2.start();
	}

结果:

张三获取年龄

李四获年龄

李四,age=30

张三,age=20

解释:我们的前提是多个线程访问多个锁,在当前线程获取其中一个锁时,对于同步方法内的代码是线程安全的。结果的顺序看出是异步的,在于不同锁之间的线程是不同步的。

----1.3.3总结:

(1).对于多个线程访问共享资源,才需要使用锁来进行同步化访问读写。并且对于synchronized修饰的方法为同步方法,是对象级别上的锁,多个线程访问时,必须 "排队运行"

(2).对于多个线程访问对象,如果线程1获取了该对象的同步方法的锁,那么其他对象只能排队等到线程1执行完释放锁,才可以再获取锁去执行同步方法。如果该对象中有多个同步方法,那么其他线程只能排队等线程1执行完所有的同步方法释放锁,其他线程才可获取锁再执行同步方法。如果该对象中既有同步方法又有非同步的方法,那么线程1执行同步方法的同时,其他线程是可以以异步的方式执行非同步的方法。

--1.4synchronized锁的重入

解释:就是当前线程已经得到了一个对象锁之后,再次请求此对象的锁可以再次得到该对象的锁。也就是说,synchronized方法体或代码块中再次调用类中其他synchronized方法和块时,同样也可获取锁。

例:

public class Service {

	public synchronized void getAge(String username){
		
			if(username.equals("张三")){
				System.out.println("张三年龄20");
				getSex(username);
			}else{
				System.out.println("李四年龄30");
				getSex(username);
			}
	}
	public synchronized void getSex(String username){
			if(username.equals("张三")){
				System.out.println("张三性别男!");
			}else{
				System.out.println("李四性别女!");
			}
	}
}

public class MyThread1 extends Thread{

    private Service service;
    private String username;
    public MyThread1(Service service ,String username) {
        super();
        this.service = service;
        this.username=username;
    }
    
    @Override
    public void run() {
        service.getAge(username);
    }
}

public class Test {

    public static void main(String[] args) {

        Service service=new Service();
        MyThread1 myThread1=new MyThread1(service,"张三");
        myThread1.start();
    }
} 

结果:

张三年龄20

张三性别男!

----1.4.1总结:

锁的重入也可用于父子类继承的环境中。子类完全可以通过锁的重入带哦用父类的同步方法。(节省篇幅,例子就不粘了)

--1.5出现异常,锁自动释放

解释:如果当前线程执行代码时出现了异常,那么当前线程所持有的锁也将会自动释放。

--1.6同步不具有继承性

解释:同步不可以继承,虽然父类声明了同步方法,但是子类重写父类的方法不具有同步的性质。

2.synchronized同步代码块

--2.1synchronized同步代码块

synchronized方法对当前对象进行加锁,synchronized代码块是对某一个对象进行加锁。

比较同步方法和同步代码块:

同步方法的特点就是只要是当前对象的同步方法,当前线程没执行完,其他线程只能等着。而同步代码块则是对于不同对象的同步代码块,线程1执行同步代码块1时,线程2也可以执行同步代码块2,这样效率会有所提升。而在我看来实际上两种方式只是细粒度上进一步得到了提升。同步代码块可以进行更加细微的操作。

--2.2半同步半异步

就是在synchronized代码块中就同步执行,不在synchronized代码块中就异步执行。

--2.3synchronized代码块的同步特点

synchronized代码块的同步性与synchronized同步方法是一样的,当一个线程访问对象的synchronized(this)代码块时,说明当前线程获取了对象级别的锁,那么其他的同步代码块synchronized(this)访问将被阻塞,就是不能访问,得需要当前线程释放对象锁,才可以访问。可以看出synchronized(this)代码块监视的是当前对象

例:

public class Service {

	public void getAge(String username){
		synchronized (this) {
			try {
				System.out.println(username);
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(username+"执行完毕!");
		}
	}
}

public class MyThread1 extends Thread{

    private Service service;
    private String username;
    public MyThread1(Service service ,String username){
        this.service=service;
        this.username=username;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        service.getAge(username);
    }
}

public class MyThread2 extends Thread{

    private Service service;
    private String username;
    public MyThread2(Service service ,String username){
        this.service=service;
        this.username=username;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        service.getAge(username);
    }
}

public class Test {

    public static void main(String[] args) {

        Service service=new Service();
        MyThread1 myThread1=new MyThread1(service,"张三");
        myThread1.start();
        MyThread2 myThread2=new MyThread2(service,"李四");
        myThread2.start();
    }
}

结果:

张三

张三执行完毕!

李四

李四执行完毕!

----2.3.1总结:

多个线程调用同一个对象的不同synchronized同步方法或者是synchronized(this)同步代码块时,调用都是顺序执行,也就是所谓的同步的,阻塞的。

<1>synchronized同步方法:

①.对于其他的synchronized同步方法或synchronized(this)调用都是呈阻塞状态。

②.同一个时间只能有一个线程获取锁对象,去执行synchronized同步方法中代码。

<2>synchronized(this)同步代码块:

①.对于其他的synchronized同步方法或synchronized(this)调用都是呈阻塞状态。

②.同一个时间只能有一个线程获取锁对象,去执行synchronized(this)同步代码块中代码。

--2.4将任意对象作为对象监视器(synchronized(Object)形式):

在多个线程持有"对象监视器"为同一对象的前提下,同一时间只能有一个线程执行synchronized(非this对象)同步代码块中的代码。

采用这种方式的优点:

如果一个类中有很多synchronized方法,虽然表面上实现了同步,但是由于同步产生的阻塞,效率很低。但是如果使用synchronized(Object)代码块其实与其他synchronized方法是异步的,不需要与其他锁this同步方法争抢this锁,可以提高效率。

例:

public class Service {

	private String keyObject=new String();
	public void startupThread1(){
		try{
			synchronized (keyObject) {
				System.out.println("thread1 begin!");
				Thread.sleep(3000);
				System.out.println("thread1 end");
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	public synchronized void startupThread2(){
		System.out.println("thread2 begin!");
		System.out.println("thread2 end");
	}
}

public class MyThread1 extends Thread{

    private Service service;
    public MyThread1(Service service){
        this.service=service;

    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        service.startupThread1();
    }
}

public class MyThread2 extends Thread{

    private Service service;
    public MyThread2(Service service){
        this.service=service;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        service.startupThread2();
    }
}

public class MyThread2 extends Thread{

    private Service service;
    public MyThread2(Service service){
        this.service=service;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        service.startupThread2();
    }
}
结果:

thread1 begin!

thread2 begin!

thread2 end

thread1 end

----2.4.1总结

同步代码块放在非同步synchronized方法中进行声明使用,不能保证调用方法的线程的执行同步/顺序性,也就是线程调用方法的顺序是无序的。

--2.5线程同步总结

<1>当多个线程同时执行synchronized(Object非this){......... }同步代码块时呈同步效果。

<2>当其他线程执行当前对象中synchronized同步方法时呈同步效果。

<3>当其他线程执行当前对象方法里面的synchronized(this)代码块时也呈现同步效果。

--2.6静态同步synchronized方法和synchronized(Class)代码块(类锁)

这两种给类上锁效果都是一样的。但是与非静态方法却有所不同。synchronized关键字修饰静态方法是给Class类上锁,而synchronized修饰非静态方法是给对象上锁。

3.volatile关键字使用

volatile作用使变量在多个线程间可见。

--3.1死循环例子

public class MyThread1 extends Thread{

	private boolean isRunning=true;
	
	public void setRunning(boolean isRunning) {
		this.isRunning = isRunning;
	}

	@Override
	public void run() {
		System.out.println("进入run了");
		while(isRunning ==true){
			
		}
		System.out.println("线程被停止了!");
	}
}

public class Test {
    public static void main(String[] args) {
        try{
            MyThread1 myThread1=new MyThread1();
            myThread1.start();
            Thread.sleep(1000);
            myThread1.setRunning(false);
            System.out.println("已经赋值为false");
        }catch(Exception e){
            e.printStackTrace();
        }
    }
} 
结果:

\

解释:虽然线程一直在私有堆栈中取得isRunning的值为true并且也执行了setRunning(false);但是却把公共堆栈的isRunning变量值更新为false,所以还是一直死循环。主要由于私有堆栈和公共堆栈里面的值不同步引起的。而volatile关键字修饰的变量,作用就是强制性地在公共堆栈中取值。也就解决上面的死循环。

private volatile boolean isRunning=true;

结果:

\

--3.2synchronized和volatile的区别

<1>volatile关键字是线程轻量级实现,volatile只能修饰于变量。

<2>多线程访问volatile不会阻塞(因为volatile不保证原子性操作),而synchronized会出现阻塞。

<3>volatile不保证数据的原子性操作,可以保证数据的可见性(可以强制从公共堆栈取值)。而synchronized可以保证原子性(前面说同步了),也可以间接的保证可见性,就让公共堆栈和私有堆栈的数据同步。

----3.2.1总结:

我们使用volatile关键字来解决变量在多个线程之间 "可见" ,而synchronized关键字来保证多个线程之间 "同步"

--3.3synchronized关键字实现间接可见性

死循环例子:

public class Service {
	private boolean isContinueRun=true;
	public void runMethod(){
		while(isContinueRun){
			
		}
		System.out.println("已经停下来了!");
	}
	public void stopMethod(){
		isContinueRun=false;
	}
}

public class MyThread1 extends Thread{

    private Service service;
    public MyThread1(Service service){
        this.service=service;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        service.runMethod();
    }
}

public class MyThread2 extends Thread{

    private Service service;
    public MyThread2(Service service){
        this.service=service;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        service.stopMethod();
    }
}

public class Test {
    public static void main(String[] args) {
        try{
            Service service=new Service();
            MyThread1 myThread1=new MyThread1(service);
            myThread1.start();
            Thread.sleep(1000);
            MyThread2 myThread2=new MyThread2(service);
            myThread2.start();
            System.out.println("已经发起停止命令了!");
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

结果:

\

解释:这种死循环,虽然MyThread2的调用了stopMethod();方法停止线程,但是根本没起作用。原因是MyThread1线程进入死循环中,一直在循环体未出来,MyThread1没看见MyThread2送过来的false,就是差在各个线程之间没有可见性。

为了保持线程之间的可见性,使用synchronized关键字,让两个线程都持有钥匙进入同步代码块。这样就可以保证两个线程之间可见了。

重新编写Service类:

public class Service {
	private boolean isContinueRun=true;
	public void runMethod(){
		while(isContinueRun){
			String keyString =new String();
			synchronized (keyString) {
				
			}
		}
		System.out.println("已经停下来了!");
	}
	public void stopMethod(){
		isContinueRun=false;
	}
}
解释:这样就实现了间接可见性。

-----------------------------------------------

4.总结:

关于线程的安全主要就是搞同步和可见两个方面。

相关文章
最新文章
热点推荐