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一般都是成对出现,退出同步方法时,通知在等待的线程
}
}