Java多线程初学者指南(10):利用Synchronized要害字同步类要领
副标题#e#
要想办理“脏数据”的问题,最简朴的要领就是利用synchronized要害字来使run要领同步,代码如下:
public synchronized void run()
{
}
从上面的代码可以看出,只要在void和public之间加上synchronized要害字,就可以使run要领同步,也就是说,对付同一个Java类的工具实例,run要领同时只能被一个线程挪用,并当前的run执行完后,才气被其他的线程挪用。纵然当前线程执行到了run要领中的yield要领,也只是暂停了一下。由于其他线程无法执行run要领,因此,最终照旧会由当前的线程来继承执行。先看看下面的代码:
sychronized要害字只和一个工具实例绑定
class Test
{
public synchronized void method()
{
}
}
public class Sync implements Runnable
{
private Test test;
public void run()
{
test.method();
}
public Sync(Test test)
{
this.test = test;
}
public static void main(String[] args) throws Exception
{
Test test1 = new Test();
Test test2 = new Test();
Sync sync1 = new Sync(test1);
Sync sync2 = new Sync(test2);
new Thread(sync1).start();
new Thread(sync2).start();
}
在Test类中的method要领是同步的。但上面的代码成立了两个Test类的实例,因此,test1和test2的method要领是别离执行的。要想让method同步,必需在成立Sync类的实例时向它的结构要领中传入同一个Test类的实例,如下面的代码所示:
Sync sync1 = new Sync(test1);
不只可以利用synchronized来同步非静态要领,也可以利用synchronized来同步静态要领。如可以按如下方法来界说method要领:
class Test
{
public static synchronized void method() { }
}
成立Test类的工具实譬喻下:
Test test = new Test();
#p#副标题#e#
对付静态要领来说,只要加上了synchronized要害字,这个要领就是同步的,无论是利用test.method(),照旧利用Test.method()来挪用method要领,method都是同步的,并不存在非静态要领的多个实例的问题。
在23种设计模式中的单件(Singleton)模式假如按传统的要领设计,也是线程不安详的,下面的代码是一个线程不安详的单件模式。
package test;
// 线程安详的Singleton模式
class Singleton
{
private static Singleton sample;
private Singleton()
{
}
public static Singleton getInstance()
{
if (sample == null)
{
Thread.yield(); // 为了放大Singleton模式的线程不安详性
sample = new Singleton();
}
return sample;
}
}
public class MyThread extends Thread
{
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 < threads.length; i++)
threads[i] = new MyThread();
for (int i = 0; i < threads.length; i++)
threads[i].start();
}
}
在上面的代码挪用yield要领是为了使单件模式的线程不安详性表示出来,假如将这行去掉,上面的实现仍然是线程不安详的,只是呈现的大概性小得多。
措施的运行功效如下:
25358555
26399554
7051261
29855319
5383406
上面的运行功效大概在差异的运行情况上有所有同,但一般这五行输出不会完全沟通。从这个输出功效可以看出,通过getInstance要领获得的工具实例是五个,而不是我们期望的一个。这是因为当一个线程执行了Thread.yield()后,就将CPU资源交给了别的一个线程。由于在线程之间切换时并未执行到建设Singleton工具实例的语句,因此,这几个线程都通过了if判定,所以,就会发生了成立五个工具实例的环境(大概建设的是四个或三个工具实例,这取决于有几多个线程在建设Singleton工具之前通过了if判定,每次运行时大概功效会纷歧样)。
要想使上面的单件模式酿成线程安详的,只要为getInstance加上synchronized要害字即可。代码如下:
#p#分页标题#e#
public static synchronized Singleton getInstance() { }
虽然,尚有更简朴的要领,就是在界说Singleton变量时就成立Singleton工具,代码如下:
private static final Singleton sample = new Singleton();
然后在getInstance要领中直接将sample返回即可。这种方法固然简朴,但不知在getInstance要领中建设Singleton工具机动。读者可以按照详细的需求选择利用差异的要领来实现单件模式。
在利用synchronized要害字时有以下四点需要留意:
1. synchronized要害字不能担任。
固然可以利用synchronized来界说要领,但synchronized并不属于要领界说的一部门,因此,synchronized要害字不能被担任。假如在父类中的某个要领利用了synchronized要害字,而在子类中包围了这个要领,在子类中的这个要领默认环境下并不是同步的,而必需显式地在子类的这个要领中加上synchronized要害字才可以。虽然,还可以在子类要领中挪用父类中相应的要领,这样固然子类中的要领不是同步的,但子类挪用了父类的同步要领,因此,子类的要领也就相当于同步了。这两种方法的例子代码如下:
在子类要领中加上synchronized要害字
class Parent
{
public synchronized void method() { }
}
class Child extends Parent
{
public synchronized void method() { }
}
在子类要领中挪用父类的同步要领
class Parent
{
public synchronized void method() { }
}
class Child extends Parent
{
public void method() { super.method(); }
}
2. 在界说接口要领时不能利用synchronized要害字。
3. 结构要领不能利用synchronized要害字,但可以利用下节要接头的synchronized块来举办同步。
4. synchronized可以自由安排。
在前面的例子中利用都是将synchronized要害字放在要领的返回范例前面。但这并不是synchronized可安排独一位置。在非静态要领中,synchronized还可以放在要领界说的最前面,在静态要领中,synchronized可以放在static的前面,代码如下:
public synchronized void method();
synchronized public void method();
public static synchronized void method();
public synchronized static void method();
synchronized public static void method();
但要留意,synchronized不能放在要领返回范例的后头,如下面的代码是错误的:
public void synchronized method();
public static void synchronized method();
synchronized要害字只能用来同步要领,不能用来同步类变量,如下面的代码也是错误的。
public synchronized int n = 0;
public static synchronized int n = 0;
固然利用synchronized要害字同步要领是最安详的同步方法,但大量利用synchronized要害字会造成不须要的资源耗损以及机能损失。固然从外貌上看synchronized锁定的是一个要领,但实际上synchronized锁定的是一个类。也就是说,假如在非静态要领method1和method2界说时都利用了synchronized,在method1未执行完之前,method2是不能执行的。静态要领和非静态要领的环境雷同。但静态和非静态要领不会相互影响。看看如下的代码:
#p#分页标题#e#
package test;
public class MyThread1 extends Thread
{
public String methodName;
public static void method(String s)
{
System.out.println(s);
while (true)
;
}
public synchronized void method1()
{
method("非静态的method1要领");
}
public synchronized void method2()
{
method("非静态的method2要领");
}
public static synchronized void method3()
{
method("静态的method3要领");
}
public static synchronized void method4()
{
method("静态的method4要领");
}
public void run()
{
try
{
getClass().getMethod(methodName).invoke(this);
}
catch (Exception e)
{
}
}
public static void main(String[] args) throws Exception
{
MyThread1 myThread1 = new MyThread1();
for (int i = 1; i <= 4; i++)
{
myThread1.methodName = "method" + String.valueOf(i);
new Thread(myThread1).start();
sleep(100);
}
}
}
运行功效如下:
非静态的method1要领
静态的method3要领
从上面的运行功效可以看出,method2和method4在method1和method3未竣事之前不能运行。因此,我们可以得出一个结论,假如在类中利用synchronized要害字来界说非静态要领,那将影响这其中的所有利用synchronized要害字界说的非静态要领。假如界说的是静态要领,那么将影响类中所有利用synchronized要害字界说的静态要领。这有点象数据表中的表锁,当修改一笔记录时,系统就将整个表都锁住了,因此,大量利用这种同步方法会使措施的机能大幅度下降。