1、start()方法和run()方法
调用start()方法会启动新线程,新线程会自动执行run()方法,run()方法执行完毕,线程生命周期结束。start()方法不能被重复调用,否则抛异常程序终止。
run()方法是线程的执行体,可以与普通方法一样被主动重复调用,单独调用run()方法的话,会在当前线程中执行run(),而不会启动新线程,启动新线程需要start()方法。
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
public void run() {
System.out.println("currentThreadName=" + Thread.currentThread());
}
});
thread.run();//将run()方法当普通方法调用,不会创建新线程
thread.run();//将run()方法当普通方法调用,可以重复调用
thread.start();//创建新线程,并自动调用run()方法
//thread.start();//多次调用start()抛异常
}
执行结果:
Thread的start()方法源码
思考:为何start()方法要加synchronized关键字修饰,不加会有什么问题?
2、synchronized关键字
在Java中,每个对象有且仅有一个同步锁,意味着同步锁依赖对象而存在。当线程访问某对象的synchronized区域(synchronized方法或代码块)时,其他线程对该对象的所有synchronized区域的访问将被阻塞(因为方法或代码块所属的对象被锁定)。而对该对象的非同步区域可以正常访问。
synchronized关键字被编译后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象。如果程序中synchronized明确指定了对象参数(synchronized修饰代码块),那就是这个对象的reference;如果没有明确指定(synchronized修饰的是方法),那就根据synchronized修饰的是实例方法还是类方法,去取对应的对象实例或者类对象作为锁对象。
synchronized实例1
线程锁定相同对象,其他线程对该synchronized区域不能访问
class MyRunable implements Runnable {
public void run() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread() + " loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
}
public class ThreadSynchronized {
public static void main(String[] args) {
Runnable runnable = new MyRunable(); // 新建“Runnable对象”
Thread t1 = new Thread(runnable, "t1"); // 新建“线程t1”, t1是基于demo这个Runnable对象
Thread t2 = new Thread(runnable, "t2"); // 新建“线程t2”, t2是基于demo这个Runnable对象
t1.start(); // 启动“线程t1”
t2.start(); // 启动“线程t2”
}
}
执行结果
先将t1线程执行完,再执行t2线程。
因为synchronized(this)锁定的是MyRunable类的对象实例runnable,而t1与t2两个线程的执行体是同一个runnable对象,因此当t1锁定时,t2并不能访问,直到t1执行完毕释放锁,t2才能获取锁继续执行。
synchronized示例2
线程锁定相同对象,其他线程对该对象的其他synchronized区域也不能访问
class Count {
// 含有synchronized同步块的方法
public void synMethod() {
synchronized(this) {//锁定Count对象实例
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
// 也包含synchronized同步块的方法
public void synMethod2() {
synchronized(this) {//锁定Count对象实例
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " synMethod2 loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
}
public class ThreadSynchronizedTwo {
public static void main(String[] args) {
final Count count = new Count();
// 新建t1, t1会调用“count对象”的synMethod()方法
Thread t1 = new Thread(
new Runnable() {
public void run() {
count.synMethod();
}
}, "t1");
// 新建t2, t2会调用“count对象”的nonSynMethod()方法
Thread t2 = new Thread(
new Runnable() {
public void run() {
count.synMethod2();
}
}, "t2");
t1.start(); // 启动t1
t2.start(); // 启动t2
}
}
执行结果
虽然两个线程访问的代码块不同,但仍是先将t1线程执行完,再执行t2线程。
因为两个线程锁定的是同一个对象count,即使两线程访问不同的synchronized同步代码块,t2仍需要等待t1执行完毕,释放count对象锁之后才能执行。
synchronized示例3
线程锁定相同对象,其他线程对该对象的非synchronized区域可以访问
class MyCount {
// 含有synchronized同步块的方法
public void synMethod() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
// 非同步的方法
public void nonSynMethod() {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
public class ThreadSynchronizedThree {
public static void main(String[] args) {
final MyCount count = new MyCount();
// 新建t1, t1会调用“count对象”的synMethod()方法
Thread t1 = new Thread(
new Runnable() {
public void run() {
count.synMethod();
}
}, "t1");
// 新建t2, t2会调用“count对象”的nonSynMethod()方法
Thread t2 = new Thread(
new Runnable() {
public void run() {
count.nonSynMethod();
}
}, "t2");
t1.start(); // 启动t1
t2.start(); // 启动t2
}
}
执行结果
t1与t2交替执行。
虽然t1保留了count对象锁,但t2执行的是非synchronized同步区域,不需要等待对象锁,所以t1获取对象锁,不影响t2的运行。
synchronized示例4
线程锁定不同对象,对不同对象的synchronized区域可以访问
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread().getName() + " loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
}
public class ThreadSynchronizedFour {
public static void main(String[] args) {
Thread t1 = new MyThread("t1"); // 新建“线程t1”
Thread t2 = new MyThread("t2"); // 新建“线程t2”
t1.start(); // 启动“线程t1”
t2.start(); // 启动“线程t2”
}
}
执行结果
t1与t2交替执行。
因为t1与t2锁定的是不同的MyThread对象实例,因此相互不影响。
3、实例锁与全局锁
实例锁:锁在某个实例对象上,实例锁对应的是synchronized关键字。
全局锁:锁针对的是类,无论该类有多少对象,线程都共享该锁,全局锁对应的是static synchronized关键字(或者是锁在该类的class或者classloader对象上)。
假设代码如下:
pulbic class Something {
public synchronized void syncA(){}
public synchronized void syncB(){}
public static synchronized void staticSyncA(){}
public static synchronized void staticSyncB(){}
}
Something x = new Something();
Something y = new Something();
分析下面4组情况能否同时访问:
(1)x.syncA与x.syncB
(2)x.syncA与y.syncA
(3)x.staticSyncA与y.staticSyncB
(4)x.syncA与Something.staticSyncB
抓住synchronized同步锁依赖对象而存在,只要分析出锁定的对象是否相同,不难分析出:
(1) 不能同时访问,因为它们锁定的对象相同,都是x对象实例;
(2) 可以同时访问,因为它们锁定的对象不同,分别是x对象实例和y对象实例;
(3) 不能同时访问,因为它们锁定的对象相同,都是Something类实例;
(4) 不能同时访问,因为它们锁定的对象不同,分别是x对象实例和Something类实例。
4、线程等待wait()与线程唤醒notify()
在Object.java中,定义了wait(), notify()和notifyAll()等接口。
wait()的作用是让当前线程(CPU正在运行的线程)进入等待状态,同时,wait()也会让当前线程释放它所持有的锁(而阻塞在sleep()方法时不会释放锁)。
而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程(如有多个线程等待,随机唤醒其中一个线程),而notifyAll()是唤醒所有的线程。
//Wait()使当前线程等待
class TestThread extends Thread{
public TestThread(String name) {
super(name);
}
public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName()+" call notify()");
// 唤醒当前的wait线程
notify();
}
}
}
public class WhichThreadWait {
public static void main(String[] args) {
TestThread t1 = new TestThread("t1");
synchronized(t1) {
try {
// 启动“线程t1”
System.out.println(Thread.currentThread().getName()+" start t1");
t1.start();
// 主线程等待t1通过notify()唤醒。
System.out.println(Thread.currentThread().getName()+" wait()");
t1.wait();//为何这里是主线程等待,而不是t1线程等待?
System.out.println(Thread.currentThread().getName()+" continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果:
t1.wait()为何这里是主线程等待,而不是t1线程等待?
JDK中的解释 “ Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. ”, wait()的作用是让“当前线程”等待,即正在CPU上运行的线程。虽然t1.wait()是通过“线程对象t1”调用的wait()方法,但是执行t1.wait()的地方是在主线程,即主线程是当前线程,需要处于等待状态。
唤醒所有在此监视器上等待的线程
public class WaitAndNotify {
private static Object obj = new Object();
public static void main(String[] args) {
MyThread t1 = new MyThread("t1");
MyThread t2 = new MyThread("t2");
MyThread t3 = new MyThread("t3");
t1.start();
t2.start();
t3.start();
try {
System.out.println(Thread.currentThread().getName()+" sleep(3000)");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(obj) {
System.out.println(Thread.currentThread().getName()+" notifyAll()");
obj.notifyAll();//唤醒所有wait()线程
//obj.notify();//唤醒单个wait()线程
}
}
static class MyThread extends Thread{
public MyThread(String name){
super(name);
}
public void run() {
synchronized (obj) {
try {
// 打印输出结果
System.out.println(Thread.currentThread().getName() + " wait");
//当前线程进入等待状态,释放锁
obj.wait();
// 打印输出结果
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
执行结果:
主线程通过notifyAll()唤醒所有等待的子线程。
思考: notify()是依据什么唤醒等待线程的,即wait()等待线程与notify()之间通过什么关联起来的?为何notify(),wait()等方法是定义在Object中,而不是Thread中?
wait()与notify()方法的联系依据是对象同步锁。使用wait()与notify()方法必须要有对象锁,否则运行时抛非法监视器的异常。唤醒线程(负责唤醒等待线程的那个线程),只有在获取该对象的同步锁(必须与等待线程是同一个锁),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然等待线程被唤醒,但是它不能立即执行,需要等待唤醒线程释放对象的同步锁之后,等待线程才能获取对象同步锁进而继续执行。
既然notify()与wait()依赖于同步锁(不是依赖线程),而同步锁是对象所持有,并且每个对象有且仅有一个。因此notify()与wait()等方法需要定义在Object()类,而不是Thread类中。
##5、线程同步之join()##
让当前执行join()方法的线程等待,直到等待超时或拥有join()方法的线程执行完毕。
public class JoinTest{
public static void main(String[] args){
try {
ThreadA t1 = new ThreadA("t1"); // 新建“线程t1”
t1.start(); // 启动“线程t1”
t1.join(); //“主线程main()会等待"线程t1"完成”
System.out.printf("%s finish\n", Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class ThreadA extends Thread{
public ThreadA(String name){
super(name);
}
public void run(){
System.out.printf("%s start\n", this.getName());
// 延时操作
try {
sleep(3000);
} catch (Exception e) {
// TODO: handle exception
}
System.out.printf("%s finish\n", this.getName());
}
}
}
执行结果:
##6、Thread与Runnable的关系##
Thread是线程类,Runnable是接口。如果只使用Thread创建新线程,需要自定义线程类继承Thread,并重写run()方法,因为线程的执行体是run(),如果不自定义线程类,则无法重写run()方法,即不能执行我们需要新线程运行的内容。Runnable接口解决了必须自定义线程类,才能创建满足需求线程的弊端,简化了线程创建流程,并且可以多线程共享同一个target对象(线程执行体,即run()方法体)。
如果既自定义了线程类继承Thread,又使用了Runnable接口,必须在自定义线程类中重写run()方法,并且调用super.run(),否则Runnable接口实例无法执行。
class MyThreadTest extends Thread{
public MyThreadTest(String namString){
super(namString);
}
public MyThreadTest(Runnable runnable, String namString){
super(runnable, namString);
}
@Override
public void run() {
super.run();//需要调用super.run()方法,否则传入的Runnable实例无法执行
System.out.println(Thread.currentThread() + " MyThread run method");
}
}
public class ThreadAndRunnable {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
public void run() {
System.out.println(Thread.currentThread() + " Runnable run method");
}
};
//直接创建Thread实例,Runnable创建线程执行体
System.out.println("直接创建Thread实例t1,Runnable创建线程执行体");
Thread thread1 = new Thread(runnable, "t1");
thread1.start();
//自定义类继承Thread,创建线程实例
System.out.println("自定义类继承Thread,创建线程实例t2");
MyThreadTest thread2 = new MyThreadTest("t2");
thread2.start();
//既自定义类继承Thread,又使用了Runnable接口
System.out.println("既自定义类继承Thread,又使用了Runnable接口,创建线程t3");
MyThreadTest thread3 = new MyThreadTest(runnable, "t3");
thread3.start();
}
}
执行结果:
##7、总结##
线程创建后需要调用start()方法才能启动,且只能调用一次,否则抛异常;run()方法是线程的执行体,线程启动后自动调用该方法;run()可以主动调用,但主动调用是在当前线程执行,而不是在新建线程执行。
synchronized是同步锁关键字,每个对象有且仅有一个同步锁,同步锁要依赖对象而存在。Synchronized关键字被编译后,会在同步块的前后分别生成monitorenter和monitorexit这两个字节码指令,这两个字节码需要一个类型参数来指明锁定和解锁的对象。如果程序中明确指定了synchronized对象参数,那么会以对象实例作为锁对象;如果没有明确指定synchronized对象参数,那么会将类对象作为锁对象。
如果锁定的对象相同,则所有synchronized区域均不能多线程访问(非synchronized区域仍可以多线程访问);如果锁定的对象不同,则synchronized区域可以多线程同时访问互不干扰。
wait()、notify()和notifyAll()等接口是在Object类中定义的,wait()的作用是让当前线程(CPU正在运行的线程)进入等待状态,同时,wait()也会让当前线程释放它所持有的锁;notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程。wait()与notify()或notifyAll()配合使用,可以很方便的控制线程同步。wait()与notify()方法是依赖对象同步锁联系起来的,而同步锁是对象所持有,且每个对象有且仅有一个同步锁,因此notify()与wait()等方法需要定义在Object()类,而不是Thread类中。
join()方法是使当前线程处于阻塞状态,等待拥有该join()方法的线程执行完毕或等待超时。join()可以很方便的控制线程执行顺序,保证多线程时序。