多线程同步

1、why要同步?

在多线程环境中,可能有两个或是更多的线程试图同时访问一个有限的资源,必须对这种潜在的资源冲突进行预防。

2、how解决?

解决方法就是:在线程使用一个资源时为其加锁,其他资源要访问这个资源时必须要等待,直到资源被解锁。

多线程调用synchronized()方法遵守同步机制:当一个线程A使用这个方法时,其他线程使用这个方法时必须等待,直到线程A使用完该方法。

3、同步中的重要方法

在同步方法中使用wait()、notify()、notifyAll()方法

wait()方法可以中断方法的执行,使本线程等待,暂时让出CPU使用权,并允许其他线程使用这个同步方法。

用notifyAll()方法通知所有由于使用这个同步方法而处于等待的线程结束等待。曾中断的线程就会从刚才的中断处继续执行这个同步方法,并遵守“先中断先继续”的原则。

使用notify()方法只是通知处于等待中的线程的某一个结束等待

注意:wait()、notify()、notifyAll()都是Object类中的final()方法,被所有的类继承,且不允许重写方法。

4、synchronized的使用方式

1)synchronized同步类方法

--同步非静态方法

public synchronized void run()

对于同一Java类的对象实例,run方法只能被一个线程调用,并且在当前的run执行完后,才能被其他线程调用。

public class SynThread implements Runnable
{
	@Override
	synchronized public void run()
	{
		// TODO Auto-generated method stub
		for (int i = 0; i < 3; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}
	public static void main(String[] args) throws Exception
	{
      SynThread myThread=new SynThread();
      Thread thread1=new Thread(myThread);
      Thread thread2=new Thread(myThread);
      SynThread.method();
      thread1.start();
      thread2.start();
	}
}
--同步静态方法

synchronized public static void method() throws Exception
	{
		System.out.println(Thread.currentThread().getName()+"method");
		Thread.sleep(3000);
	}

因为静态方法可以直接通过类名进行调用,所以不存在多个对象实例的问题。

具体问题分析:线程不安全的单例模式

public class SingletonThread extends Thread
{
	@Override
	public void run()
	{
		Singleton singleton=Singleton.getInstance();
		
		System.out.println(singleton.hashCode());
	}
	public static void main(String[] args)
	{
		Thread[] threads=new Thread[5];
		for(int i=0;i<5;i++)
		{
			threads[i]=new SingletonThread();
		}
		for(int i=0;i<5;i++)
		{
			threads[i].start();
		}
	}
}

class Singleton
{
	private static Singleton instance;

	private Singleton()
	{
	}

	public static Singleton getInstance()
	{
		if (instance == null) {
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			instance = new Singleton();
		}
		return instance;
	}
}

运行结果:

22279806
10378355
9192299
31275026
22279806

生成了5个对象实例,因为在实例判断为空之后,将线程进行休眠,之后的线程到达时,也判断了实例为空,所以生成了多个实例。

解决方法:

一:在getInstance()方法上加上synchronized

 synchronized public static Singleton getInstance()
二:创建final instance

private static final  Singleton instance = new Singleton();

注意点:
--synchronized不能继承

--定义接口不能使用synchronized关键字

--构造方法不能使用synchronized关键字

--synchronized放的位置相对自由,可以放在public前后,可以放在static前后,但是不能放在方法的返回类型后面

--只能同步方法不能同步变量

2)synchronized块同步方法

语法:

public void method()  
{  
    … …  
    synchronized(表达式)  
    {  
        … …  
    }  
} 

实例:--同步非静态方法

public void run()
	{
		synchronized (<span style="color:#ff0000;">this</span>) {
			// TODO Auto-generated method stub
			for (int i = 0; i < 3; i++) {
				System.out.println(Thread.currentThread().getName() + ":" + i);
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

	}
这种方式与synchronized直接同步方法的效果是一样的
实例:--同步静态方法

由于在调用静态方法的时候,对象实例不一定被创建,因此不能使用this来同步静态方法,而必须使用Class对象来同步静态方法。

synchronized public static void method() throws Exception
	{
		synchronized (<span style="color:#ff0000;">SynThread.class</span>) {
			System.out.println(Thread.currentThread().getName() + "method");
			Thread.sleep(3000);
		}
	}
实例:通过class对象使不同的类的静态方法同步

public class SynThread implements Runnable
{
	@Override
	public void run()
	{
		synchronized (this) {
			// TODO Auto-generated method stub
			for (int i = 0; i < 3; i++) {
				System.out.println(Thread.currentThread().getName() + ":" + i);
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

	}

	public static void method() throws Exception
	{
		<span style="color:#ff0000;">synchronized (Test.class)</span> {
			System.out.println(Thread.currentThread().getName() + "method");
			Thread.sleep(3000);
		}
	}

	public static void main(String[] args) throws Exception
	{
		SynThread myThread = new SynThread();
		Thread thread1 = new Thread(myThread);
		Thread thread2 = new Thread(myThread);
		<span style="color:#ff0000;">SynThread.method();
		Test.method1();</span>
		thread1.start();
		thread2.start();
	}
}

class Test
{
	public static void method1() throws Exception
	{
		<span style="color:#ff0000;">synchronized (SynThread.class)</span> {
			System.out.println(Thread.currentThread().getName() + "method1");
			Thread.sleep(3000);
		}
	}

}
3)synchronized块同步变量

public class SampleThread extends Thread
{
	private static String str = "";
	private String methodType = "";

	public SampleThread(String methodType)
	{
		this.methodType = methodType;
	}

	public static void method(String ss)
	{
		synchronized (str) {
			str = ss;
			System.out.println(str);
			while(true);
		}
	}

	public static void method1()
	{
		method("method1");
	}

	public void method2()
	{
		method("method2");
	}

	@Override
	public void run()
	{
		if (methodType.equals("method1")) {
			method1();
		} else if (methodType.equals("method2")) {
			method2();
		}
	}

	public static void main(String[] args)
	{
     SampleThread sample1=new SampleThread("method1");
     SampleThread sample2=new SampleThread("method2");
     sample1.start();
     sample2.start();
	}

}

预计输出结果:无限循环输出method1或是method2

实际输出结果:

method1
method2

原因分析:在使用String型变量时,只要给这个变量赋一次值,Java就会创建新的String类型的实例。所以当method1进入同步块时,str已经被赋值改变,和之前的就不是一个对象了,所以同步块被破坏。

注意:使用synchronized块时只能使用对象作为它的参数。如果是简单类型的变量,不能使用synchronized来同步。

5、实例

张三,李四上电影院买票,售票员只有2张5块,张三一张20元,李四一张5元钱,先让李四买,再让张三买。

public class TicketHouse implements Runnable
{
    int fiveAmount=2,tenAmountt=0,twentyAmount=0;
	@Override
	public void run()
	{
		// TODO Auto-generated method stub
		if(Thread.currentThread().getName().equals("张三"))
		{
			saleTicket(20);
		}else if(Thread.currentThread().getName().equals("李四"))
		{ 
			saleTicket(5);
		}
		
	}

	private synchronized void saleTicket(int money)
	{
		// TODO Auto-generated method stub
		if(money==5){
			fiveAmount=fiveAmount+1;
			System.out.println("给"+Thread.currentThread().getName()+"入场券");
		}else if(money==20)
		{
			System.out.println(Thread.currentThread().getName()+"靠边等");
			try
			{
				wait();
				System.out.println(Thread.currentThread().getName()+"继续买票");
			}
			catch (InterruptedException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			fiveAmount=fiveAmount-3;
			twentyAmount=twentyAmount+1;
			System.out.println("给"+Thread.currentThread().getName()+"入场券,给20找15元");
			
		}
		notifyAll();//wait和notify一般都是成对出现,退出同步方法时,通知在等待的线程
	}

}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值