接上篇:Java多线程核心技术二-synchronzied同步方法
1 概述
用synchronzied关键字声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个长时间的任务,那么B现成就要等待比较长的时间,此时可以使用synchronzied同步语句块来解决,已增加运行效率。
synchronzied方法是将当前对象作为锁,而synchronzied代码块是将任意对象作为锁。锁可以认为是一个标识,持有这个标识的线程就可以执行被同步的代码。
2 synchronzied方法的弊端
下面证明用synchronzied关键字声明方法时是有弊端的。
public class Task {
private String getData1;
private String getData2;
public synchronized void doLongTimeTask(){
try {
System.out.println("任务开始");
Thread.sleep(3000);
getData1 = "长时间处理任务后,从远程返回的值1线程名=" + Thread.currentThread().getName();
getData2 = "长时间处理任务后,从远程返回的值2线程名=" + Thread.currentThread().getName();
System.out.println(getData1);
System.out.println(getData2);
System.out.println("任务结束");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class CommonUtils {
public static long beginTime1;
public static long endTime1;
public static long beginTime2;
public static long endTime2;
}
public class MyThread1 extends Thread{
private Task task;
public MyThread1(Task task) {
this.task = task;
}
@Override
public void run(){
CommonUtils.beginTime1 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime1 = System.currentTimeMillis();
}
}
public class MyThread2 extends Thread{
private Task task;
public MyThread2(Task task) {
this.task = task;
}
@Override
public void run(){
CommonUtils.beginTime2 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime2 = System.currentTimeMillis();
}
}
public class Run1 {
public static void main(String[] args) {
Task task = new Task();
MyThread1 t1 = new MyThread1(task);
t1.start();
MyThread2 t2 = new MyThread2(task);
t2.start();
try {
Thread.sleep(10000);
}catch (InterruptedException e){
e.printStackTrace();
}
long beginTime = CommonUtils.beginTime1;
if(CommonUtils.beginTime2 < CommonUtils.beginTime1){
beginTime = CommonUtils.beginTime2;
}
long endTime = CommonUtils.endTime1;
if(CommonUtils.endTime2 > CommonUtils.endTime1){
endTime = CommonUtils.endTime2;
}
System.out.println("耗时:"+((endTime - beginTime) /1000) + "秒");
}
}
通过使用synchronzied关键字来声明方法,从运行的时间上来看,弊端很明显,可以使用synchronzied同步代码块来解决。
3 synchronzied同步代码块的使用
先来了解一下synchronzied同步代码块的使用方法。当两个并发线程访问同一个对象object中的synchronzied(this)同步代码块时,一个时间内只能执行一个线程,另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
public class ObjectService {
public void serviceMethod(){
try {
synchronized (this){
System.out.println("线程名 = " + Thread.currentThread().getName() + "开始时间 = " + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("线程名 = " + Thread.currentThread().getName() +"结束时间 = " + System.currentTimeMillis());
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class ThreadA extends Thread{
private ObjectService objectService;
public ThreadA(ObjectService objectService) {
this.objectService = objectService;
}
@Override
public void run(){
objectService.serviceMethod();
}
}
public class ThreadB extends Thread{
private ObjectService service;
public ThreadB(ObjectService service) {
this.service = service;
}
@Override
public void run(){
service.serviceMethod();
}
}
public class Run1 {
public static void main(String[] args) {
ObjectService service = new ObjectService();
ThreadA a = new ThreadA(service);
a.setName("a");
a.start();
ThreadB b = new ThreadB(service);
b.setName("b");
b.start();
}
}
上面的例子虽然使用了synchronzied同步代码块,但执行的效率没有提高,还是同步运行。如何用synchronzied同步代码块解决程序执行效率慢的问题呢?
4 用同步代码块解决同步方法的弊端
【示例】
public class Task {
private String getData1;
private String getData2;
public void doLongTimeTask(){
try {
System.out.println("开始任务:");
Thread.sleep(3000);
String privateGetData1 = "长时间处理任务后,从远程返回的值1 线程名 = " + Thread.currentThread().getName();
String privateGetData2 = "长时间处理任务后,从远程返回的值2 线程名 = " + Thread.currentThread().getName();
synchronized (this){
getData1 = privateGetData1;
getData2 = privateGetData2;
System.out.println(getData1);
System.out.println(getData2);
System.out.println("任务结束。");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class MyThread1 extends Thread{
private Task task;
public MyThread1(Task task) {
this.task = task;
}
@Override
public void run(){
CommonUtils.beginTime1 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime1 = System.currentTimeMillis();
}
}
public class MyThread2 extends Thread{
private Task task;
public MyThread2(Task task) {
this.task = task;
}
@Override
public void run(){
CommonUtils.beginTime2 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime2 = System.currentTimeMillis();
}
}
public class Run1 {
public static void main(String[] args) {
Task task = new Task();
MyThread1 t1 = new MyThread1(task);
t1.start();
MyThread2 t2 = new MyThread2(task);
t2.start();
try {
Thread.sleep(10000);
}catch (InterruptedException e){
e.printStackTrace();
}
long beginTime = CommonUtils.beginTime1;
if(CommonUtils.beginTime2 < CommonUtils.beginTime1){
beginTime = CommonUtils.beginTime2;
}
long endTime = CommonUtils.endTime1;
if(CommonUtils.endTime2 > CommonUtils.endTime1){
endTime = CommonUtils.endTime2;
}
System.out.println("耗时:"+((endTime - beginTime) /1000) + "秒");
}
}
通过上面的示例可以看出,当一个线程访问object对象的一个synchronzied同步代码块时,另一个线程仍然可以访问该对象中的非synchronzied同步代码块。在这个示例中,虽然时间缩短了,加快了运行效率,但同步synchronzied代码块真的是同步的吗?它真的持有当前调用对象的锁吗?是的,但必须通过下个例子来验证。
5 一半异步,一半同步
本节示例用于说明不在synchronzied代码块中就是异步执行,在synchronzied代码块中就是同步执行。
public class Task {
public void doLongTimeTask(){
for (int i = 0; i < 100; i++) {
System.out.println("没有执行同步方法的线程名 = " + Thread.currentThread().getName() + "i=" + (i+1));
}
System.out.println("");
synchronized (this){
for (int i = 0; i < 100; i++) {
System.out.println("执行同步方法的线程名 = " + Thread.currentThread().getName() + "i=" + (i+1));
}
}
}
}
public class MyThread1 extends Thread{
private Task task;
public MyThread1(Task task) {
this.task = task;
}
@Override
public void run(){
task.doLongTimeTask();
}
}
public class MyThread2 extends Thread{
private Task task;
public MyThread2(Task task) {
this.task = task;
}
@Override
public void run(){
task.doLongTimeTask();
}
}
public class Run1 {
public static void main(String[] args) {
Task task = new Task();
MyThread1 a = new MyThread1(task);
a.start();
MyThread2 b = new MyThread2(task);
b.start();
}
}
根据运行结果可知,在执行没有同步代码块的时候,两个线程之间互相竞争资源。进入同步代码块后,两个线程被排队执行。
6 synchronzied代码块间的同步性
在使用synchronzied同步代码块时需要注意,当一个线程访问object的一个synchronzied同步代码块时,其他线程对同一个object中的所有其他synchronzied同步代码块的访问都被阻塞,说明synchronzied使用的对象监视器是一个,即使用的“锁”是一个。通过示例验证。
public class ObjectService {
public void serviceMethodA(){
try {
synchronized (this){
System.out.println("方法A开始执行时间" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("方法A结束时间" + System.currentTimeMillis());
}
}catch (InterruptedException e){
}
}
public void serviceMethodB(){
synchronized (this){
System.out.println("方法B开始执行时间 = " + System.currentTimeMillis());
System.out.println("方法B结束时间 = " + System.currentTimeMillis());
}
}
}
public class ThreadA extends Thread{
private ObjectService objectService;
public ThreadA(ObjectService objectService) {
this.objectService = objectService;
}
@Override
public void run(){
objectService.serviceMethodA();
}
}
public class ThreadB extends Thread{
private ObjectService objectService;
public ThreadB(ObjectService objectService) {
this.objectService = objectService;
}
@Override
public void run(){
objectService.serviceMethodB();
}
}
public class Run1 {
public static void main(String[] args) {
ObjectService service = new ObjectService();
ThreadA a = new ThreadA(service);
a.setName("a");
a.start();
ThreadB b = new ThreadB(service);
b.setName("b");
b.start();
}
}
7 将任意对象作为锁
多个线程调用同一个对象中的不同名称的synchronzied同步方法或同步代码块,是按顺序执行,也就是同步的。
如果在一个类中同时存在synchronzied同步方法和同步代码块,对其他synchronzied同步方法或同步代码块调用会呈同步的效果,执行特性如下:
1、同一个时间只有一个线程可以执行synchronzied同步方法中的代码。
2、同一时间只有一个线程可以执行synchronzied同步代码块中的代码。
其中Java还支持将任意对象作为锁,来实现同步的功能,这个任意对象大多数是实例变量及方法的参数,格式为synchronzied(非this对象)。
synchronzied(非this对象)同步代码块的执行特性是:在多个线程争抢相同的非this对象的锁时,同一时间只有一个线程可以执行synchronzied同步代码块中的代码。
public class Service {
private String usernameParam;
private String passwordParam;
private String anyString = new String();
public void setUsernamePassword(String username,String password){
try {
synchronized (anyString){
System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "进入同步代码块");
usernameParam = username;
Thread.sleep(3000);
passwordParam = password;
System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "离开同步代码块");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class ThreadA extends Thread{
private Service service;
public ThreadA(Service service) {
this.service = service;
}
@Override
public void run(){
service.setUsernamePassword("a","aa");
}
}
public class ThreadB extends Thread{
private Service service;
public ThreadB(Service service) {
this.service = service;
}
@Override
public void run(){
service.setUsernamePassword("b","bb");
}
}
public class Run1 {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
}
}
总结:锁非this对象具有一定的优点,就是如果一个类中有很多synchronzied方法,这时虽然能是实现同步,但影响运行效率,如果使用同步代码块锁非this对象,则synchronzied代码块中的程序与同步方法是异步的,因为是两把锁,不与其他锁this同步方法争抢this锁,可以提高运行效率。
8 验证方法被调用是随机的
同步代码块放在非同步synchronzied方法中进行声明,并不能保证调用方法的线程的执行顺序,即线程调用方法是无序的,下面来验证多个线程调用同一个方法是随机的。
public class MyList {
private List list = new ArrayList<>();
synchronized public void add(String username){
System.out.println("线程 " + Thread.currentThread().getName() + "执行了 add 方法");
list.add(username);
System.out.println("线程 " + Thread.currentThread().getName() + "退出了 add 方法");
}
}
public class ThreadA extends Thread{
private MyList myList;
public ThreadA(MyList myList) {
this.myList = myList;
}
@Override
public void run(){
for (int i = 0; i < 50000; i++) {
myList.add("thread_a " + (i+1));
}
}
}
public class ThreadB extends Thread{
private MyList myList;
public ThreadB(MyList myList) {
this.myList = myList;
}
@Override
public void run(){
for (int i = 0; i < 50000; i++) {
myList.add("thread_b"+ (i+1));
}
}
}
public class Run1 {
public static void main(String[] args) {
MyList myList = new MyList();
ThreadA a = new ThreadA(myList);
a.setName("a");
a.start();
ThreadB b = new ThreadB(myList);
b.setName("b");
b.start();
}
}
从运行结果来看,同步方法中的代码是同步输出的,所以线程的“执行”与“退出”是成对出现的,但是方法被调用是随机的,也就是线程A和线程B的执行是异步的。
9 静态同步:synchronzied方法与synchronzied(class)代码块
synchronzied关键字还可以应用在静态方法上,如果这些写,那是对当前的*.java文件对应的Class类的对象进行持锁,Class类的对象是单例的,更具体的说,在静态方法上使用synchronzied关键字声明同步方法时,是使用当前静态方法所在类对应Class类的单例对象作为锁的。
public class Service {
synchronized public static void printA(){
try {
System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +
System.currentTimeMillis() + "进入printA方法");
Thread.sleep(3000);
System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +
System.currentTimeMillis() + "退出printA方法");
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized public static void printB(){
System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +
System.currentTimeMillis() + "进入printA方法");
System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +
System.currentTimeMillis() + "退出printA方法");
}
}
public class ThreadA extends Thread{
@Override
public void run(){
Service.printA();
}
}
public class ThreadB extends Thread{
@Override
public void run(){
Service.printB();
}
}
public class Run1 {
public static void main(String[] args) {
ThreadA a = new ThreadA();
a.setName("a");
a.start();
ThreadB b = new ThreadB();
b.setName("b");
b.start();
}
}
虽然运行结果与将synchronzied关键字加到非static静态方法上的效果一样,都是同步的效果,但还是有本质上的不同。synchronzied关键字加到static静态方法上是将Class类的对象作为锁,而synchronzied关键字加到非static静态方法上是将方法所在类的对象作为锁。
10 同步synchronzied方法可以对类的所有对象实例起作用
Class锁可以对同一个类的所有对象实例起作用,实现同步效果。
public class Service {
synchronized public static void printA(){
try {
System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +
System.currentTimeMillis() + "进入printA方法");
Thread.sleep(3000);
System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +
System.currentTimeMillis() + "退出printA方法");
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized public static void printB(){
System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +
System.currentTimeMillis() + "进入printB方法");
System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +
System.currentTimeMillis() + "退出printB方法");
}
}
public class ThreadA extends Thread{
private Service service;
public ThreadA(Service service) {
this.service = service;
}
@Override
public void run(){
service.printA();
}
}
public class ThreadB extends Thread{
private Service service;
public ThreadB(Service service) {
this.service = service;
}
@Override
public void run(){
service.printB();
}
}
public class Run1 {
public static void main(String[] args) {
Service s1 = new Service();
ThreadA a = new ThreadA(s1);
a.setName("A");
a.start();
Service s2 = new Service();
ThreadB b = new ThreadB(s2);
b.setName("B");
b.start();
}
}
虽然是不同的对象,但静态的同步方法还是同步运行了。
11 同步synchronzied(class)代码块可以对类的所有对象实例起作用
同步代码块的作用其实和同步静态方法的作用一样。
public class Service {
public void printA(){
synchronized (Service.class){
try {
System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +
System.currentTimeMillis() + "进入printA方法");
Thread.sleep(3000);
System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +
System.currentTimeMillis() + "退出printA方法");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public void printB(){
synchronized (Service.class){
System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +
System.currentTimeMillis() + "进入printB方法");
System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +
System.currentTimeMillis() + "退出printB方法");
}
}
}
public class ThreadA extends Thread{
private Service service;
public ThreadA(Service service) {
this.service = service;
}
@Override
public void run(){
service.printA();
}
}
public class ThreadB extends Thread{
private Service service;
public ThreadB(Service service) {
this.service = service;
}
@Override
public void run(){
service.printB();
}
}
public class Run1 {
public static void main(String[] args) {
Service s1 = new Service();
Service s2 = new Service();
ThreadA a = new ThreadA(s1);
a.setName("A");
a.start();
ThreadB b = new ThreadB(s2);
b.setName("B");
b.start();
}
}