# Java 线程

# 多线程有几种实现方式

参考答案

  • 继承Thread类
public class ThreadDemo extends Thread {

	@Override
	public void run() {
		while (true) {
            // 打印当前线程的名字
			System.out.println(Thread.currentThread().getName() + " is running ... "); 
			try {
				Thread.sleep(1000); // 休息1000ms
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) {
		ThreadDemo td = new ThreadDemo();
		td.start(); // 启动线程
		while (true) {
            // 打印当前线程的名字
			System.out.println(Thread.currentThread().getName() + " is running ... "); 
			try {
				Thread.sleep(1000); // 休息1000ms
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}
  • 实现Runnable接口
public class ThreadTarget implements Runnable {

	@Override
	public void run() {
		while(true) {
			System.out.println(Thread.currentThread().getName() + " is running .. ");
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}

  • 内部类的方式
public class DemoThread {
	
	public static void main(String[] args) {
		
		// 基于子类的实现
		new Thread() {
			public void run() {
				while (true) {
                    // 打印当前线程的名字
					System.out.println(Thread.currentThread().getName() + " is running ... "); 
					try {
                         // 休息1000ms
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			};
		}.start();
		
		// 基于接口的实现
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
                    // 打印当前线程的名字
					System.out.println(Thread.currentThread().getName() + " is running ... "); 
					try {
						Thread.sleep(1000); // 休息1000ms
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
		
		// 主线程的方法
		while (true) {
            // 打印当前线程的名字
			System.out.println(Thread.currentThread().getName() + " is running ... "); 
			try {
				Thread.sleep(1000); // 休息1000ms
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
	}

}
  • 实现Callable接口 现Callable接口,增加了异常和返回值,需要创建一个FutureTask,指定Callable对象,做为线程任务。
public class CallableTest {
	
	public static void main(String[] args) throws Exception {
		Callable<Integer> call = new Callable<Integer>() {

			@Override
			public Integer call() throws Exception {
				System.out.println("thread start .. ");
				Thread.sleep(2000);
				return 1;
			}
		};
		
		FutureTask<Integer> task = new FutureTask<>(call);
		Thread t =  new Thread(task);
		
		t.start();
		System.out.println("do other thing .. ");
		System.out.println("拿到线程的执行结果 : " + task.get());
	}

}

Callable中可以通过范型参数来指定线程的返回值类型。通过FutureTask的get方法拿到线程的返回值。

  • 线程池的方式
public class ThreadPoolDemo {
	
	public static void main(String[] args) {
		
		// 创建线程池
		ExecutorService threadPool = Executors.newFixedThreadPool(10);
		
		while(true) {
		// 提交多个线程任务,并执行
			threadPool.execute(new Runnable() { 
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName() + " is running ..");
					try {
						Thread.sleep(3000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
		}
	}

}

  • Spring方式:这种方式依赖于Spring3以上版本,我们可以通过Spring的@Async注解非常方便的实现多线程。

# 线程池中submit()和execute()的区别

参考答案

两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线 程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

# synchronized 和 ReentrantLock的区别

参考答案

Java在过去很长一段时间只能通过synchronized关键字来实现互斥,它有一些缺点。比如你不能扩展锁之外的方法或者块边界,尝试获取锁 时不能中途取消等。Java 5 通过Lock接口提供了更复杂的控制来解决这些问题。 ReentrantLock 类实现了 Lock,它拥有与 synchronized 相同的并发性和内存语义且它还具有可扩展性。

# 怎么检测一个线程是否拥有锁

参考答案

在java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁。

# Java中的同步集合与并发集合的区别

参考答案

  • HashTable(同步集合)比并发集合会慢得多,主要原因是锁,同步集合会对整个Map或List加锁。

  • ConcurrentHashMap(并发集合):把整个Map 划分成几个片段,只对相关的几个片段上锁,同时允许多线程访问其他未上锁的片段。

# 什么是ThreadLocal变量

参考答案

ThreadLocal是Java里一种特殊的变量。每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被 彻底消除了。它是为创建代价高昂的对象获取线程安全的好方法,比如你可以用ThreadLocal让SimpleDateFormat变成线程安全的,因 为那个类创建代价高昂且每次调用都需要创建不同的实例所以不值得在局部范围使用它,如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。首先,通 过复用减少了代价高昂的对象的创建个数。其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。线程局部变量的另一个不错的例子是 ThreadLocalRandom类,它在多线程环境中减少了创建代价高昂的Random对象的个数。

# 如何在两个线程间共享数据

参考答案

  • wait 和 notify方法。
  • 使用 volatile 关键字。
  • CountDownLatch 并发工具
  • CyclicBarrier 并发工具
  • 管道输入/输出流:PipedOutputStream、PipedInputStream、 PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。

# Java中Runnable和Callable有什么不同

参考答案

Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在 JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。