Thread类相关问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cugwuhan2014/article/details/78630881

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()可以很方便的控制线程执行顺序,保证多线程时序。
这里写图片描述

猜你喜欢

转载自blog.csdn.net/cugwuhan2014/article/details/78630881