一、前言
javase还是在大一时候学的了,后来学习android、j2ee等等。现在回想起来发现自己基础还是不是太牢,因此准备花一些时间来回顾一些java基础知识。就准备从多线程出发。
二 多线程并发
多线程在我们实际项目中应用中还是比较多的,比如在Android中我们不可以在主线程刷新UI,因此必须创建子线程,而创建线程又是一个耗费资源的操作,因此我们需要加以控制,即线程池。不过本篇博客不介绍线程池方面的知识,接下来的博客会介绍。回到多线程,虽然多线程能有很多好处,但是同时带来了一些问题,最严重最常见的应该算是线程并发了吧。不过java既是老牌语言了,自然提供了解决方法。
三、解决多线程并发
java提供了琐的概念来解决多线程的问题,这也是本篇博客的核心。
synchronized
synchronized中文翻译同步的意思。synchronized一般用在两种地方:
- 修饰方法:代表这个方法是同步方法
- 修饰中括号:同步代码块
两种用法如下:
同步方法
public static synchronized void test2() {
// code
}
同步代码块
synchronized (ObjectLockTest.class) {
// code
}
举个栗子
public class ObjectLockTest {
public void test1() {
// 同步代码块,使用的琐是ObjectLockTest
synchronized (ObjectLockTest.class) {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (Exception ie) {
}
}
}
}
// 静态方法上的synchronized使用的琐是:该类
public synchronized static void test2() {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (Exception ie) {
}
}
}
public static void main(String[] args) {
// 两个线程用的是同一个对象琐,因此必须在第一个运行的线程执行完毕释放完琐之后才能执行第二个线程
final ObjectLockTest olt = new ObjectLockTest();
Thread test1 = new Thread(new Runnable() {
public void run() {
olt.test1();
}
}, "test1");
Thread test2 = new Thread(new Runnable() {
public void run() {
// ObjectLockTest.test2();
olt.test2();
}
}, "test2");
test1.start();
test2.start();
}
}
分析:test1方法使用的是同步代码块,使用的琐就是这个类ObjectLockTest,test2使用的是同步方法修饰静态方法。然后在主线程依次从两个线程调用test1和test2方法。输出如下:
test1 : 4
test1 : 3
test1 : 2
test1 : 1
test1 : 0
test2 : 4
test2 : 3
test2 : 2
test2 : 1
test2 : 0
从输出我们也可以证明:test2只有等到test1执行完之后才执行,synchronized修饰的静态方法使用的琐就是该类。那么类琐和对象琐是否是同一个琐呢?我将静态方法改成非静态,结果输出如下:
test2 : 4
test1 : 4
test2 : 3
test1 : 3
test2 : 2
test1 : 2
test2 : 1
test1 : 1
test2 : 0
test1 : 0
从输出结果来看,两个线程相互争夺资源,因此证明对象琐和类琐不是一个对象。
关于synchronized还对应两个概念:
- 对象琐:当一个对象中有synchronized method或synchronized block的时候调用此对象的同步方法或进入其同步区域时,就必须先获得对象锁。如果此对象的对象锁已被其他调用者占用,则需要等待此锁被释放
- 类琐:一个静态的方法被申明为synchronized。此类所有的实例化对象在调用此方法,共用同一把锁。主要由同步静态方法引申出一个概念,其实系统中并不存在什么类锁。当一个同步静态方法被调用时,系统获取的其实就是代表该类的类对象的对象锁。
简单来说:类琐就只有一个,一旦被A实例获取到了B实例来访问时必须等到A实例释放掉才可访问;而对象琐则是每个对象都有一个。
下面通过代码分别演示类琐和对象琐
类琐
package com.lw.thread.lock;
public class ThreadTest2 extends Thread {
private int threadNo;
private String lock;
public ThreadTest2(int threadNo, String lock) {
this.threadNo = threadNo;
this.lock = lock;
}
public static void main(String[] args) throws Exception {
String lock = new String("lock");
for (int i = 1; i < 3; i++) {
new ThreadTest2(i, lock).start();
}
}
public void run() {
synchronized (lock) {
for (int i = 1; i < 5; i++) {
System.out.println("No." + threadNo + ":" + i);
}
}
}
}
这是类琐的demo,该例子比较简单,在main里面首先创建一个String对象,然后for循环2次,创建线程并且开启线程。输出如下:
No.1:1
No.1:2
No.1:3
No.1:4
No.2:1
No.2:2
No.2:3
No.2:4
由上面输出可知,No.2必须在No.1释放锁之后才能执行。
对象琐
public class ThreadTest2 extends Thread {
private int threadNo;
private String lock;
public ThreadTest2(int threadNo, String lock) {
this.threadNo = threadNo;
this.lock = lock;
}
public static void main(String[] args) throws Exception {
for (int i = 1; i < 3; i++) {
String lock = new String("lock");
new ThreadTest2(i, lock).start();
}
}
public void run() {
synchronized (lock) {
for (int i = 1; i < 5; i++) {
System.out.println("No." + threadNo + ":" + i);
}
}
}
}
可能你觉得这个上面代码和类琐的代码好像没区别,但实际上确实有点像,我只是将lock对象的创建放在了for循环里面而已。然而输出已经很不一样了。
No.1:1
No.2:1
No.1:2
No.2:2
No.1:3
No.2:3
No.1:4
No.2:4
因为在for循环里面每次都会创建一个String对象,也就是每次的lock对象都是不一样的,它们互不干扰,因此输出就看CPU的分配了。
四、总结
这篇主要回顾了synchronized的一些用法,理解synchronized在不同位置的作用。