java程序员面试笔试宝典-4.10多线程

4.10.1 什么是线程?它和进程有什么区别?为什么要 使用多线程?

线程是指程序在执行过程中,能够执行程序代码的一个执行单元。
在java语言中,线程有四种状态:运行,就绪,挂起和结束。
进程是指一段正在执行的程序。线程有时也被称为轻量级进程,它是程序执行的最小单元,一个进程可以拥有多个线程,各个线程之间共享程序的内存空间(代码段,数据段和堆空间)及一些进程级的资源(例如打开的文件)
但是各个线程拥有自己的栈空间,进程与线程的对比关系如下:
在这里插入图片描述

在操作系统级别上,程序的执行都是以进程为单位的,而每个进程通常都会有多个线程互不影响并发执行,那么为什么要使用多线程呢?

1)可以减少程序的响应时间

单线程:程序执行过程中只有一个有效操作的序列,不同操作之间都有明确的执行先后顺序。
在单线程的情况下,如果一个操作很耗时,此时程序不会响应鼠标和键盘等操作。
使用多线程后,可以把这个耗时的线程分配到一个单独的线程去执行,从而使程序具备了更好的交互性。

2)与进程相比,线程的创建和开销更小。

3)多CPU或多核计算机本身就具有执行多线程的能力。

4)使用多线程能够简化程序的结构,使程序便于理解和维护。

4.10.2 同步和异步有什么区别?

在多线程的环境中,经常会碰到数据的共享问题,即多个线程需要访问同一个资源,他们需要以某种顺序来确保该资源在某一时刻是能被一个线程使用,否则程序的运行结果将会是不可预料的。
这种情况下就必须对数据进行同步。
例如多个线程同时对同一数据进行写操作,即当线程A需要某个资源时,如果这个资源正在被线程B使用,同步机制会让A一直等待下去,直到线程B结束对该资源的使用后,线程A才能使用这个资源。
可见同步机制能够保证资源的安全。
要想实现同步操作,必须要获得每一个线程对象的锁。获得他可以保证在同一时刻只有一个线程能够进入临界区(访问互斥资源的代码块),并且在这个锁被释放之前,其他线程就不能再进入这个临界区。
如果还有其他线程想要获得该对象的锁,只能进入等待队列等待。
只有当拥有该对象锁的线程退出临界区时,锁才会被释放,等待队列中优先级最高的先成功才能获得该锁,从而进入共享代码区。
可以使用synchronized关键字来实现同步,但是系统开销很大,可能造成死锁。

实现同步的方式有两种:

1)利用同步代码块来实现同步
2)利用同步方法来实现同步
异步与非阻塞相似,由于每个线程都包含了运行时所需要的数据和方法,因此,在进行输入输出处理时,不必关心其他线程的状态或行为,也不必等到输入输出处理完毕才返回。
当应用程序调用了一个很耗时的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,异步能够提高程序的效率。
例子:
同步就是你喊我吃饭,如果我听到了,我就去和你吃饭,如果我没有听到,你就不停喊,直到我告诉你我听到了,我们才一起去吃饭。
异步就是你喊我,然后自己去吃饭,我得到消息后可能立即走,可能等到下班才去吃饭。

4.10.3 如果实现java多线程?

java中多线程的实现一般有三种方法,前两种常用。

1)继承Thread类,重写run方法

Thread本质也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start方法。
start方法是一个navite(本地)方法,它将启动一个新线程,并执行run方法。(Thread中提供的run方法是一个空方法。)
注意:
调用start方法后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行多线程代码是由操作系统决定的。

例1:

package cn.itcast.demo3;
//创建线程类
public class MyThread extends Thread {

	public void run(){
		System.out.println("Thread body");//线程的函数体
	}

}

package cn.itcast.demo3;

public class Test {

	public static void main(String[] args) {
		MyThread thread = new MyThread();
		thread.start();//开启线程
	}

}

在这里插入图片描述

2)实现Runnable接口,并实现该接口的run方法

以下是主要步骤:
自定义类并实现Runnable接口,实现run方法
创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象
调用Thread的start方法

package cn.itcast.demo4;
//创建线程类
public class MyThread implements Runnable {

	public void run(){
		System.out.println("Thread body");//线程的函数体
	}

}

package cn.itcast.demo4;
/*
 * 自定义类并实现Runnable接口,实现run方法
      创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象
      调用Thread的start方法
 */
public class Test {

	public static void main(String[] args) {
		MyThread thread = new MyThread();
		//Thread(Runnable target) 
		//分配新的 Thread 对象
		Thread t = new Thread(thread);
        t.start();//开启线程
	}

}

其实不管是通过继承Thread类还是通过使用Runnable接口来实现多线程的方法,最终都是通过Thread的对象的API来控制线程的。

在这里插入图片描述

3)实现Callable接口,重写call方法

Callable接口实际是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能,主要表现为以下三点:
1)Callable可以在任务结束后提供一个返回值,Runnable无法提供这个功能。
2)Callable中的call方法可以抛出异常,而Runnable的run方法不能抛出异常。
3)运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供了检查计算是否完成的方法。
由于线程属于异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下,就可以使用Future来监视目标线程调用call方法的情况。
当调用Future的get方法以获取结果时,当前线程就会被阻塞,直到call方法结束返回结果。

package cn.itcast.demo5;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableAndFuture {
	//创建线程类
	public static class CallableTest implements Callable<String>{
		public String call() throws Exception{
			return "Hello World!";
		}
		
		public static void main(String[] args) {
			ExecutorService threadPool = Executors.newSingleThreadExecutor();
			//启动线程
			Future<String> future = threadPool.submit(new CallableTest());
			try {
				System.out.println("waiting thread to finish");
				//当调用Future的get()方法以获取结果时,当前线程就会阻塞
				System.out.println(future.get());//等待线程结束,并获取返回结果
			} catch (Exception ex) {
				System.out.println(ex.getMessage());
			}
		}
		
	}
}

在这里插入图片描述
在以上三种方式中,前两种方式线程执行完后都没返回值,只有最后一种是带返回值的。

当需要实现多线程时,一般推荐实现Runnable接口的方式,原因如下:

1)Thread类定义了多种方法可以被派生类使用或重写,但是只有run方法是必须被重写的,在run方法中实现这个线程的主要功能。这当然是实现Runnable接口所需的方法。
2)很多java开发人员认为,一个类仅在他们需要被加强或修改时才会被继承,因此,如果没有必要重写Thread类中的其他方法,那么通过继承Thread的实现方式与实现Runnable接口的效果相同,在这种情况下最好通过实现Runnable接口的方式来创建线程

引申:一个类是否可以同时继承Thread类和实现Runnable接口?

可以,如下所示:

package cn.itcast.demo5;

public class Test1 extends Thread implements Runnable{

	@Override
    public void run() {
        System.out.println("this is run()");
    }
	
	public static void main(String[] args) {
		Thread t = new Thread(new Test1());
        t.start();
	}

}

在这里插入图片描述

4.10.4 run()方法与start()方法有什么区别?

通常,系统通过调用线程类的start()方法来启动一个线程,此时线程处于就绪状态,而非运行状态,意味着这个线程可以被JVM来调度执行。
在调度过程中,JVM通过调用线程类的run()方法来完成实际的操作,当run()方法结束后,此线程就会中止。
如果直接调用线程类的run()方法,这会被当做一个普通的函数调用,程序中仍然只有主线程这一个线程,即start()方法能够异步调用run方法,但是直接调用run方法确实同步的,因此也就无法达到多线程的目的。

因此,只有通过调用线程类的start()方法才能真正达到多线程的目的

package cn.itcast.demo6;

public class ThreadDemo extends Thread{
	public void run(){
		System.out.println("ThreadDemo:begin");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			
			e.printStackTrace();
		}
		System.out.println("ThreadDemo:end");
	}
}

package cn.itcast.demo6;

public class Test {
	public static void test1(){
		System.out.println("test1:begin");
		Thread t1 = new ThreadDemo();
		t1.start();
		System.out.println("test1:end");
	}
	
	public static void test2(){
		System.out.println("test2:begin");
		Thread t1 = new ThreadDemo();
		t1.run();
		System.out.println("test2:end");
	}
	public static void main(String[] args) {
		test1();//main线程与t1线程是异步执行的
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			
			e.printStackTrace();
		}
		System.out.println();
		test2();//直接调用run()方法是同步的
	}
}

结果:

test1:begin
test1:end
ThreadDemo:begin
ThreadDemo:end

test2:begin
ThreadDemo:begin
ThreadDemo:end
test2:end

说明:

线程t1是在test1方法结束后才执行的System.out.println(“test1:end”);语句不需要等待t1.start()运行结束就可以执行。因此,test1中调用start()方法是异步的,所以main线程与t1线程是异步执行的。

从test2的运行结果可以看出,调用t1.run()方法是同步的调用方法,因为System.out.println(“test2:end”);只有等t1.run()调用结束后才能执行。

4.10.5 多线程实现的方法有哪些?

当使用多线程访问同一个资源时,非常容易出现线程安全的问题,因此需要采用同步机制来解决问题。

Java提供了3种实现同步机制的方法。

1)synchronized关键字

在java语言中,每个对象都有一个对象锁与之相关联,该锁表明对象在任何时候只允许被一个线程所拥有,
当一个线程调用对象的一段synchronized代码时,需要先获取这个锁,然后去执行相应的代码,执行结束后,释放锁。
synchronized关键字主要有两种用法:synchronized方法和synchronized代码块,此外该关键字还可以作用于静态方法,类或某个实例,但是对程序影响很大。

a)synchronized方法

public synchronized void mutiThreadAccess();

只要把多个线程对类需要被同步的资源的操作放到mutiThreadAccess方法中,就能保证这个方法在同一时刻只能被一个线程访问,从而保证了多线程访问的安全性。
然而当一个方法的方法体规模非常大时,把该方法申明为synchronized 会很影响程序的执行效率,为了提高程序的执行效率,Java提供了synchronized 代码块。

b)synchronized 代码块

既可以把任意的代码申明为synchronized ,也可以指定上锁的对象

synchronized (syncObject){
    //访问syncObject的代码
}

2)wait()方法与notify()方法

当使用synchronized 来修饰某个共享资源时,如果线程A1在执行synchronized 代码,另外一个线程A2也要同时执行同一对象的synchronized 代码时,
线程A2将要等到线程A1执行完后,才能继续执行,这种情况下使用wait方法和notify方法。
在synchronized 代码被执行期间,线程可以调用对象的wait方法,释放对象锁,进入等待状态,并且可以调用notify方法或者notifyAll方法通知正在等待的其他线程。
notify方法仅唤醒一个线程,并允许它去获得锁
notifyAll方法唤醒所有等待这个对象的线程,并让他们去竞争。

3)Lock

jdk1.5新增了Lock接口以及它的一个实现类ReentrantLock(重入锁),Lock也可以用来实现多线程的同步。

a)lock()

以阻塞的方式获取锁,即如果获得了锁,立即返回;如果别的线程持有锁,当前线程等待,直到获得锁后返回。

b)tryLock()

以非阻塞的方式获得锁,只是常识性去获取一下锁,如果获得锁,立即返回true,否则,立即返回false。

c)tryLock(long timeout,TimeUnit unit)

如果获得了锁,立即返回true,否则会等待参数给定的时间单元,在等待的过程中,如果获得了锁,就返回true,如果等待超时,返回false。

d)lockInterruptibly()

如果获取了锁,立即返回;如果没有获取锁,当前线程处于休眠状态,直到获得锁。
或者当前线程被别的线程中断(会收到InterruptedException异常)
它与lock方法最大的区别是如果lock方法获取不到锁,就会一直处于阻塞状态,且会忽略interrupt方法

package cn.itcast.demo6;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test1 {

	public static void main(String[] args) throws InterruptedException {
		final Lock lock = new ReentrantLock();
		lock.lock();
		//Thread(Runnable target) 分配新的 Thread 对象
		Thread t1 = new Thread(new Runnable() {
			public void run() {
				try {
					lock.lockInterruptibly();
					//lock.lock();
				} catch (Exception e) {
					System.out.println("interrupt.");
				}
			}
		});
		t1.start();
		t1.interrupt();
		Thread.sleep(1);
		
		
	}

}

在这里插入图片描述
如果把lock.lockInterruptibly();替换lock.lock(),编译器将会提示catch块代码无效。
这是因为lock.lock()不会抛出异常,
由此可见lock()方法会忽略interrupt()引发的异常。
在这里插入图片描述

4.10.6 sleep()方法与wait()方法有什么区别?

sleep()方法是使线程暂停执行一段时间的方法
wait()方法也是一种使线程暂停执行的方法。

例如,当线程交互时,如果线程对一个同步对象x发出一个wait调用请求,那么该线程会暂停执行,被调用对象进入等待状态,直到被唤醒或等待时间超时。

区别如下:

1)原理不同:

sleep是Thread类的静态方法,是线程用来控制自身的,时间一到,线程就会自动苏醒。
wait是Object类的方法,用于线程间的通信。使当前拥有该对象锁的进程等待,直到其他线程调用notify()方法才醒来,开发人员也可以指定醒来时间。
与wait方法配套的方法还有notify()和notifyAll()

2) 对锁的处理机制不同

sleep不会释放锁
wait会释放锁

3)使用区域不同

sleep任何地方可以使用,必须捕获异常,有可能被其他对象调用它的interrupt方法,产生InterruptedException异常。
wait必须放在同步控制方法或同步语句块中使用,而wait,notify,notifyAll不需要捕获异常。
sleep不会释放锁,容易死锁,推荐wait方法。

引申:sleep方法和yield方法有什么区别?

1)sleep方法给其他线程运行机会时不考虑线程的优先级,因此会给优先级低的线程运行的机会
yield方法只会给相同优先级或更高优先级的线程以运行的机会。
2)线程执行sleep方法后会转入阻塞状态,所以执行sleep方法的线程在指定的时间内肯定不会被执行
yield方法只是使当前线程重新回到可执行状态,所以执行yield方法的线程有可能在进入到可执行状态后马上被执行。
3)sleep方法申明抛出InterruptedException ,而yield方法没有申明任何异常。
4)sleep方法比yield方法(跟操作系统相关)具有更好的移植性。

笔1:

利用Thread.wait()同步线程,可以设置超时时间吗?
可以
函数原型为wait(long timeout)和wait(long timeout,int nanos)
timeout代表最长的等待时间,ms,nanos代表额外的等待时间,单位为ns

笔2:

在一个线程中sleep(1000)方法,将使该线程在多长时间后获得对CPU的控制(假设睡眠过程中不会有其他事件唤醒该线程)C
A:正好1000ms
B:少于1000ms
c:大于等于1000ms
D:不一定
说明:sleep方法指定的时间为线程不会运行的最短时间
当睡眠时间结束后,线程会返回到可运行状态,不是运行状态,还需要等待CPU调度执行
因此***sleep()方法并不能保证该线程睡眠到期后就开始执行***。

4.10.7 中止线程的方法有哪些?

在java语言中,可以使用stop方法与suspend方法来终止线程的执行。
当用Thread.stop()来终止线程时,可能会导致程序执行的不确定性。
当调用suspend()方法容易发生死锁,因为不会释放锁。
死锁指的是两个或两个以上的进程在执行过程中,因争夺资源而造成互相等待的现象,如果无外力作用,他们都无法推进。
建议采用让线程自动结束进入Dead状态,一个线程进入Dead状态,即执行完run方法
即想要停止一个线程的执行,就要提供某种方式让线程能够自动结束run方法的执行。在实现时,可以通过设置一个flag标志来控制循环是否执行,通过这种方法来让线程离开run()方法从而中止线程

package cn.itcast.demo6;

public class MyThread implements Runnable{
	private volatile Boolean flag;//有默认类型,是false
	public void stop(){
		flag = false;
	}
	@Override
	public void run() {
		while(flag){
			;//do something
		}
	}
}

上例中,通过调用MyThread的stop方法虽然能够中止线程,但是同样存在问题
当线程处于非运行状态时,(当sleep方法被调用,wait方法被调用,当被I/O阻塞),上面的方法就不可用了。
此时可以使用interrupt()方法来打破阻塞的情况,当interrupt方法被调用时候,抛出InterruptedException 异常,可以通过在run方法中捕获这个异常来让线程安全退出

package cn.itcast.demo7;
/*
 * 当sleep()方法被调用或当wait()方法被调用-线程处于非运行状态
 * 此时可以使用interrupt()方法来打破阻塞的情况
 * 当interrupt()方法被调用时,会抛出InterruptedException异常
 */
public class MyThread {

	public static void main(String[] args) {
		Thread thread = new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println("Thread go to sleep");
				try {
					//用休眠来模拟线程被阻塞
					Thread.sleep(5000);
					System.out.println("Thread finish");
				} catch (InterruptedException e) {
					// TODO: handle exception
					System.out.println("thread is interrupted!");
				}
			}
		});
		thread.start();
		
		thread.interrupt();
	}

}

在这里插入图片描述
说明:如果程序因为I/O而停滞,进入非运行状态,基本上要等I/O完成才能离开这个状态。
在这种情况下,无法使用interrupt()方法来使程序离开run()方法

需要使用一个替代的方法,基本思路也是触发一个异常,而这个异常与所使用的I/O相关
例如,如果使用readLine()方法在等待网络上的一个信息,此时线程处于阻塞状态。
让程序离开run()就是使用close()关闭流,在这种情况下会引发IOException异常,run()方法可以通过捕获这个异常来安全地结束线程。

4.10.8 synchronized与Lock有什么异同?

Java语言提供了两种锁机制来实现对某个共享资源的同步:

1)synchronized

使用Object对象本身的notify,wait,notifyAll调度机制

2)Lock

使用Condition进行线程之间的调度,完成synchronized实现的所有功能。

区别:

1)用法不一样

在需要同步的对象中加入synchronized控制,synchronized既可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。synchronized是托管给JVM执行
Lock需要显示地指定起始位置了中止位置。Lock的锁定需要通过代码来实现。它有比synchronized更精确的线程定义。

2)性能不一样

在jdk1.5中增加了一个Lock接口的实现类ReentrantLock。
它不仅拥有和synchronized相同的并发性和内存语义,还多了锁投票,定时锁,等候和中断锁。
他们的性能在不同情况下会不同
在竞争不激烈的情况下,synchronized性能好
竞争那个激烈的情况下,synchronized性能下降很快,ReentrantLock性能基本不变。

3)锁机制不一样

synchronized获得锁和释放锁都是在块结构中,当获得多个锁时候,必须以相反的顺序释放,并且是自动解锁,不会因为出现了异常而导致锁没有被释放从而引发死锁。
Lock需要开发人员手动去释放,并且必须在finally块中释放,否则会引起死锁的问题
此外Lock还提供了更强大的功能,它的tryLock()方法可以采用非阻塞的方式去获取锁。

注意:

最好不要同时使用这两种同步机制,因为synchronized和ReentrantLock所使用的机制不同,运行是独立的,相当于两种类型的锁,在使用时互不影响。

package cn.itcast.demo7;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SyncTest {
	private int value = 0;
	Lock lock = new ReentrantLock();
	public synchronized void addValueSync(){
		this.value++;
		//static Thread currentThread()  
		//String getName()  返回该线程的名称
		System.out.println(Thread.currentThread().getName() + ":" + value);
	}
	public void  addValueLock(){
		try {
			lock.lock();
			value++;
			System.out.println(Thread.currentThread().getName() + ":" + value);
		} finally {
			lock.unlock();//Lock需要开发人员手动释放,必须在finally块中释放
		}
	}
}

package cn.itcast.demo7;

public class Test {

	public static void main(String[] args) {
		final  SyncTest st = new SyncTest();//测试synchronized
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 0; i < 5; i++) {
					st.addValueSync();
					try {
						Thread.sleep(20);
					} catch (InterruptedException e) {
						// TODO: handle exception
						System.out.println(e.getMessage());
					}
				}
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 0; i < 5; i++) {
					st.addValueLock();
					try {
						Thread.sleep(20);
					} catch (InterruptedException e) {
						// TODO: handle exception
						System.out.println(e.getMessage());
					}
				}
			}
		});
		t1.start();
		t2.start();
	}

}

结果:

Thread-0:2
Thread-1:2
Thread-1:3
Thread-0:4
Thread-0:6
Thread-1:6
Thread-1:7
Thread-0:8
Thread-0:10
Thread-1:10

说明:

上例中,并不是每次运行结果都是相同的输出结果的value值也不是连续的,这就是因为两种上锁方式采用了不同的机制造成的
因此实际使用时,最好不要同时使用两种上锁机制。
当采用同一个上锁机制:
在这里插入图片描述
结果:

Thread-0:1
Thread-1:2
Thread-0:3
Thread-1:4
Thread-0:5
Thread-1:6
Thread-0:7
Thread-1:8
Thread-0:9
Thread-1:10

在这里插入图片描述
结果:

Thread-0:1
Thread-1:2
Thread-1:3
Thread-0:4
Thread-0:5
Thread-1:6
Thread-1:7
Thread-0:8
Thread-1:9
Thread-0:10

笔1:当一个线程进入一个对象的synchronized方法后,其他线程是否可以进入此对象的其他方法?

当一个线程进入一个对象的synchronized方法后,其他线程能否进入此对象的其他方法取决与方法本身,如果该方法是非synchronized方法,那么是可以访问的
示例如下:

package cn.itcast.demo8;


public class Test {
	public synchronized void synchronizedMethod(){
		System.out.println("begin calling synchronizedMethod");
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			System.out.println(e.getMessage());
		}
		System.out.println("finish calling synchronizedMethod");
	}
	//public synchronized static void generalMethod()
	public void generalMethod(){
		System.out.println("calling generalMethod...");
	}
	
}

package cn.itcast.demo8;

public class MutiThread {

	static final Test t = new Test();
	public static void main(String[] args) {
		Thread t1 = new Thread(){
			public void run(){
				t.synchronizedMethod();
			}
		};
		Thread t2 = new Thread(){
			public void run(){
				t.generalMethod();
			}
		};
		t1.start();
		t2.start();
	}

}

在这里插入图片描述

说明:

从上例可以看出,线程t1在调用sychronized方法的过程中,线程t2仍然可以访问同一对象的非sychronized方法

例2:

如果其他方法是静态方法,它用的同步锁是当前类的字节码,与非静态的方法不能同步,(因为非静态的方法用的是this)
因此,静态方法可以被调用,实例如下:

package cn.itcast.demo8;


public class Test {
	public synchronized void synchronizedMethod(){
		System.out.println("begin calling synchronizedMethod");
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			System.out.println(e.getMessage());
		}
		System.out.println("finish calling synchronizedMethod");
	}
	
	public synchronized static void generalMethod(){
		System.out.println("calling generalMethod...");
	}
	
}

package cn.itcast.demo8;

public class MutiThread {

	static final Test t = new Test();
	public static void main(String[] args) {
		Thread t1 = new Thread(){
			public void run(){
				t.synchronizedMethod();
			}
		};
		Thread t2 = new Thread(){
			public void run(){
				t.generalMethod();
			}
		};
		t1.start();
		t2.start();
	}

}

从上例可以看出,当线程t1在调用对象t1的sychronized方法时,线程t2仍然可以调用这个对象的静态sychronized()方法。
在这里插入图片描述
如果这个对象内部调用了wait方法,那么其他线程可以访问同一对象的其他sychronized方法
如果这个方法内部没有调用wait方法,并且其他方法都为sychronized方法,那么其他线程将无法访问这个对象的其他方法。

4.10.9 什么是守护线程?

java提供了两种线程:守护线程和用户线程。
守护线程又被称为"服务进程",“精灵线程"或"后台线程”,是指程序运行时在后台提供的一种通用服务的线程这种线程并不属于程序中不可或缺的部分
任何一个守护线程都是整个JVM中所有非守护线程的保姆
用户线程和守护线程几乎一样,唯一不同的地方就是如果用户线程已经全部退出运行,只剩下守护线程存在了,JVM也就退出了
因为当所有非守护线程结束时,没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了,程序也就中止了,同时会杀死所有守护线程。
只要有任何非守护线程还在运行,程序就不会终止

在java语言中,守护线程一般具有较低的优先级,它并非只由JVM内部提供,用户在编写程序时也可以自己设置守护线程
例如将一个用户线程设置成守护线程的方法就是在调用start()方法启动线程之前调用对象的setDaemon(true)方法若将以上参数设置为false,则表示的是用户进程模式
当在一个守护线程中产生了其他线程,那么这些新产生大的线程默认还是守护线程,用户线程也是如此。

package cn.itcast.demo8;

public class ThreadDemo extends Thread{
	public void run(){
		System.out.println(Thread.currentThread().getName() + ":begin");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			System.out.println(e.getMessage());
		}
		System.out.println(Thread.currentThread().getName() + ":end");
	}
}

package cn.itcast.demo8;

public class Test1 {

	public static void main(String[] args) {
		System.out.println("Test1:begin:");
		Thread t1 = new ThreadDemo();
		t1.setDaemon(true);
		t1.start();
		System.out.println("Test1:end:");
		
	}

}

从运行结果发现,没有输出Thread - 0:end.
之所以这样是在启动线程前将其设置为守护线程了,当程序只有守护线程存在时,JVM是可以退出的。
当JVM中只有守护线程运行时,JVM会自动关闭。
因此当Test1方法调用结束后,main线程将退出,此时线程t1还处于休眠状态没有运行结束,但是由于此时只有这个守护线程在运行,JVM将关闭,因此不会输出Thread - 0:end.

守护线程的一个典型例子就是垃圾回收器只要JVM启动,它始终在运行,实时监控和管理系统中可以被回收的资源。
在这里插入图片描述

4.10.10 join()方法的作用是什么?

在java语言中,join()方法的作用是让调用该方法的线程在执行完run()方法后,再执行join之后的代码。
简单点说:就是将两个线程合并,用于实现同步功能。
具体而言,可以通过线程A的join()方法来等待线程A的结束,或者使用线程A的join(2000)方法来等待线程A的结束,但是最多只等待2s.

package cn.itcast.demo8;

public class ThreadImp implements Runnable {

	@Override
	public void run() {
		try {
			System.out.println("Begin ThreadImp");
			Thread.sleep(5000);
			System.out.println("End ThreadImp");
		} catch (InterruptedException e) {
			System.out.println(e.getMessage());
		}
	}

}

package cn.itcast.demo8;


public class JoinTest {

	public static void main(String[] args) {
		Thread t = new Thread(new ThreadImp());
		t.start();
		try {
			t.join(1000);//让主程序等待线程t结束,只等1s
			if(t.isAlive()){
				System.out.println("t has not finished");
			}else{//t已经结束
				System.out.println("t has finished");
			}
			System.out.println("joinFinish");
		} catch (InterruptedException e) {
			System.out.println(e.getMessage());
		}
	}

}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_40807247/article/details/86726580