一.普通对象,线程对象,线程
- 普通对象:针对于线程对象而言的。到处可见普通对象,Person p = new Person();一个无头无脑的人就是一个普通对象。
- 线程对象:线程类的实例对象。我自己定义了一个线程类public class CheckThread extends Thread{...},然后CheckThread ct = new CheckThread(),ct就是线程对象。
- 线程:代码的一次执行过程。
- 从概念层面讲,普通对象和线程对象是实实在在的东西,线程是虚的东西。线程好比一条细线。把普通对象和线程对象上的方法串起来执行,得到想要的结果而已。
- 从内存层面讲,对象就是一块内存地址,里面有数据和运行方法。而线程就是这些方法的一次执行路径。
- 唯一觉得线程对象和线程有点关系的地方:线程对象定义了线程的一个入口(线程对象的run方法),就好像main方法开启了主线程的一个入口一样。
二.锁和同步
- 锁是针对对象或者类而言的。Java中每个对象都有一个内置锁。
- 同步的表现形式为synchronized方法或者synchronized代码块,作用为同一时刻,只能有一个线程调用此方法或代码块。当程序运行到synchronized同步方法或代码块时锁才起作用。
- 对象锁:
- 线程1调用synchronized getX()方法时,有幸获得对象锁,会把对象锁带在自己身上,此时线程2想进入synchronized getX()方法或者synchronized getY()方法等任何非静态同步方法时,看到synchronized就需要对象锁,由于对象锁被线程1占有者,所以线程2只能等待。
- 线 程1调用getX()的synchronized代码块时,有幸获得对象锁,会把对象锁带在自己身上,此时线程2可以进入getX()或者getY()方 法,但进入后什么时候发现synchronized代码块,就什么时候需要对象锁,由于对象锁被线程1占有者,所以线程2只能等待。
- 如果一个类有有两个实例A和B,线程1调用对象A的synchronized getX()方法时,用到对象A的锁,线程2调用对象B的synchronized getX()方法时,用到对象B的锁,两个锁不一样,所以两个线程可以同时进行。
public synchronized int getX() { return x++; } public synchronized int getY() { return y++; } public int getX() { synchronized (this) { return x; } } public int getY() { synchronized (this) { return y; } }
- 类锁:和对象锁差不多的
- 线程3调用synchronized static setName()方法时,有幸获得类锁,会把类锁带在自己身上,此时线程4想进入synchronized static setName()方法或者synchronized static setAge()方法等任何静态同步方法时,看到synchronized就需要类锁,由于类锁被线程3占有者,所以线程4只能等待。
- 线 程3调用static setName()的synchronized代码块时,有幸获得类锁,会把类锁带在自己身上,此时线程4可以进入setName()或者setAge()方法等任何静态同步方法时,但进入后什么时候发现synchronized代码块,就什么时候需要类锁,由于类锁被线程3占有者,所以线程4只能等待。
- 区别于对象的是,类的静态方法是有一份,多个线程都想调用此同步静态方法时,同一时刻肯定只能有一个线程能调用。
public static synchronized int setName(String name){ Xxx.name = name; } public static synchronized int setAge(String age){ Xxx.age= age; } public static int setName(String name){ synchronized(Xxx.class){ Xxx.name = name; } } public static int setAge(String age){ synchronized(Xxx.class){ Xxx.age= age; } }
- 线程1对静态同步方法的调用用到类锁,线程3对非静态同步方法的调用用到对象锁,它们之间不会产生阻塞。
- 一个对象可以同步方法和非同步方法同时存在,线程对同步方法的调用需要获得锁,对非同步方法的调用不需要。
- 总结线程一看到synchronized方法或代码块时三步曲,每个线程来的时候你都这么想一下,就理清头绪了。
- 线程要搞清楚要得到什么样的锁才能进入这个方法或代码块。
- 这把锁现在是什么状态。
- 线程拿到了这把锁后一起把锁带入方法或代码块里,直到方法和代码块区域执行完后释放锁。
三.测试用例
引用下greatwqs 的例子,1234线程组合式的运行其中两个,能证明上面说的每一点。
package com.zzy.Thread;
public class ThreadTest {
public static void main(String[] args) {
final MyObject obj = new MyObject();
final MyObject obj2 = new MyObject();
//线程1
Runnable r1 = new Runnable(){
public void run(){
obj.getName(1);
}
};
Thread t1 = new Thread(r1);
t1.start();
//线程2
Runnable r2 = new Runnable(){
public void run(){
obj.getAge(2);
}
};
Thread t2 = new Thread(r2);
t2.start();
//线程3
Runnable r3 = new Runnable(){
public void run(){
MyObject.getNameStatic(3);
}
};
Thread t3 = new Thread(r3);
t3.start();
//线程4
Runnable r4 = new Runnable(){
public void run(){
obj2.getName(4);
}
};
Thread t4 = new Thread(r4);
t4.start();
}
}
class MyObject {
public String getName(int i) {
synchronized (this) {
try {
System.out.println("线程" + i + ":getName sleep 10 secs start");
Thread.sleep(1000 * 10);
System.out.println("线程" + i + ":getName sleep 10 secs end");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
public String getAge(int i) {
synchronized (this) {
try {
System.out.println("线程" + i + ":getAge sleep 10 secs start");
Thread.sleep(1000 * 10);
System.out.println("线程" + i + ":getAge sleep 10 secs end");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
public static String getNameStatic(int i) {
synchronized (MyObject.class) {
try {
System.out.println("线程" + i + ":getNameStatic sleep 10 secs start");
Thread.sleep(1000 * 10);
System.out.println("线程" + i + ":getNameStatic sleep 10 secs end");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}