线程的创建方式
常见的Java线程创建方式有四种
继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Thread1 { public static void main(String[] args) { Thread.currentThread().setName("主线程"); System.out.println(Thread.currentThread().getName()+":"+"输出的结果"); //创建一个新线程 ThreadDemo1 thread1 = new ThreadDemo1(); //为线程设置名称 thread1.setName("线程一"); //开启线程 thread1.start(); } } class ThreadDemo1 extends Thread{ @Override public void run() { System.out.println(Thread.currentThread().getName()+":"+"输出的结果"); } }
|
实现Runnable接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Thread2 { public static void main(String[] args) { Thread.currentThread().setName("主线程"); System.out.println(Thread.currentThread().getName()+":"+"输出的结果"); //创建一个新线程 Thread thread2 = new Thread(new ThreadDemo2()); //为线程设置名称 thread2.setName("线程二"); //开启线程 thread2.start(); } } class ThreadDemo2 implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()+":"+"输出的结果"); } }
|
实现Callable接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class Thread3 { public static void main(String[] args) throws Exception { Thread.currentThread().setName("主线程"); System.out.println(Thread.currentThread().getName()+":"+"输出的结果"); //创建FutureTask的对象 FutureTask<String> task = new FutureTask<String>(new ThreadDemo3()); //创建Thread类的对象 Thread thread3 = new Thread(task); thread3.setName("线程三"); //开启线程 thread3.start(); //获取call()方法的返回值,即线程运行结束后的返回值 String result = task.get(); System.out.println(result); } } class ThreadDemo3 implements Callable<String> { @Override public String call() throws Exception { System.out.println(Thread.currentThread().getName()+":"+"输出的结果"); return Thread.currentThread().getName()+":"+"返回的结果"; } }
|
使用线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| public class Thread4 { public static void main(String[] args) throws Exception { Thread.currentThread().setName("主线程"); System.out.println(Thread.currentThread().getName()+":"+"输出的结果"); //通过线程池工厂创建线程数量为2的线程池 ExecutorService service = Executors.newFixedThreadPool(2); //执行线程,execute()适用于实现Runnable接口创建的线程 service.execute(new ThreadDemo4()); service.execute(new ThreadDemo6()); service.execute(new ThreadDemo7()); //submit()适用于实现Callable接口创建的线程 Future<String> task = service.submit(new ThreadDemo5()); //获取call()方法的返回值 String result = task.get(); System.out.println(result); //关闭线程池 service.shutdown(); } } //实现Runnable接口 class ThreadDemo4 implements Runnable{ @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+"输出的结果"); } } //实现Callable接口 class ThreadDemo5 implements Callable<String>{ @Override public String call() throws Exception { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+"输出的结果"); return Thread.currentThread().getName()+":"+"返回的结果"; } } //实现Runnable接口 class ThreadDemo6 implements Runnable{ @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+"输出的结果"); } } //实现Runnable接口 class ThreadDemo7 implements Runnable{ @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+"输出的结果"); } }
|
线程的通知与等待
Object中的通知与等待系列函数
wait()函数
当一个线程调用一个共享变量 wait() 方法时, 该调用线程会被阻塞挂起,直到发生下面几件事情之一才返回
- 线程调用了该共享对象 notify()或者 notifyAll() 方法
- 其他线程调用了该线程 interrupt() 方法 该线程抛出InterruptedException 异常返回。
另外需要注意的是,如果调用 wait() 方法 线程没有事先获取该对象的监视器锁,则调用 wait() 方法时调用线程会抛出 IllegalMonitorStateException 异常。
wait(long timeout)函数
该方法相 wait()方法多了一个超时参数,它的不同之处在于,如果一个线程调用共享对象的该方法挂起后,没有在指定的 timeout ms 内被其它线程调用该共享变量的notify() 或者notifyAll() 方法唤醒,那么该函数还是会因为超时而返回。如果将 timeout置为0则和 wait 方法效果一样,因为在 wait 方法内部就是调用了 wait 需要注意的是,如果在调用该函数时,传递了一个负的 timeout 会抛出 IllegalArgumentException 异常
wait(long timeout, int nanos)函数
在其内部调用的是 wait(long timeout)函数,只有在 nanos>0 时才使参数timeout 递增1
notify()函数
一个线程调用共享对象的notify()方法后,会唤醒一个在该共享变量上调用wait系列方法后被挂起的线程。 一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的。
此外,被唤醒的线程不能马上从wait方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回,也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可继续执行。
类似wait系列方法,只有当前线程获取到了共享变量的监视器锁后,才可以调用共享变量的 notify()方法,否则会抛出 IllegalMonitorStateException异常。
notifyAll()函数
不同于在共享变量上调用 notify()函数会唤醒被阻塞到该共享变量上的一个线程,notifyAll()方法则会唤醒所有在该共享变量上由于调用 wait 系列方法而被挂起的线程。
Thread中的通知与等待系列函数
join()方法
等待线程执行终止.
1 2 3 4 5 6 7 8 9
| ...... //启动子线程 threadOne.start(); threadTwo.start(); System.out.println("wait all child thread over !”); //等待子线程执行完毕,返回 threadOne.join(); threadTwo.join(); ......
|
如上代码在主线程里面启动了两个子线程,然后分别调用了它 join()方法,那
么主线程首先会在调用 threadOne.join()方法后被阻塞threadOne执行完毕后返回threadOne执行完毕后threadOne.join()就会返回,然后主线程调用 threadTwo.join()方法后再次被阻塞,等待 threadTwo 执行完毕后返回。这里只是为了演示 join 方法的作用, 在这种情况下使用后面会讲到的CountDownLatch 是个不错的选择
另外,线程调用线程 join 方法后会被阻塞,当其它线程调用了线程A的interrupt()方法中断了线程A时,线程A会抛出InterruptedException 异常而返回。
sleep方法
让线程睡眠
当一个执行中的线程调用了Thread的sleep方法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与CPU的调度,但是该线程所拥有的监视器资源,比如锁还是持有不让出的。指定的睡眠时间到了后该函数会正常返回,线程就处于就绪状态,然后参与CPU的调度,获取到CPU资源后就可以继续运行了。如果在睡眠期间其线程调用了该线程的 intenrrupt()方法中断了该线程,则该
线程会在调用 sleep 方法的地方抛出 InterruptedException 异常而返回
yield方法
让出CPU执行权
当一个线程调用 yield 方法时,实际就是在暗示线程调度器当前线程请求让出自己CPU使用,但是线程调度器可以无条件忽略这个暗示。我们知道操作系统是为每个线程分配一个时间片来占有CPU的, 正常情况下当一个线程分配给自己的时间片使用完后,线程调度器才会进行下一轮的线程调度,而当一个线程调用了Thread类的静态方法yield时,是在告诉线程调度器自己占有的时间片中还没
有使用完的部分自己不想使用了,这暗示线程调度器现在就可以进行下一轮的线程调度。
当一个线程调用yield 方法时, 当前线程会让出CPU 使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出CPU的那个线程来获取CPU执行权。
线程中断
Java 中的线程中断是 种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。
void interrupt()方法: 中断线程 例如,当线程A运行时,线程B可以调用线程A的interrupt()方法来设置线程A的中断标志为true并立即返回。设置标志仅仅是设置标志,线程A实际并没有被中断,它会继续往下执行。如果线程A因为调用了wait系列函数、join方法或者sleep方法阻塞挂起,这时候若线程B调用线程A的interrupt()方法,线程A会在调用这些方法的地方抛出InterruptedException返回。
boolean isinterrupted()方法:检测当前线程是否被中断,如果是返回 true 否则返false
1 2 3 4
| public boolean isInterrupted() { //传递false ,说明不清除中断标志 return isinterrupted (false) }
|
- boolean interrupted() 方法: 检测当前线程是否被中断,如果是返回true否则返回false。与 isInterrupted不同的是,该方法如果发现当前线程被中断, 会清除中断标志,并且该方法是static方法,通过Thread 类直接调用。另外从下面的代码可以知道,在interrupted()内部是获取当前调用线程的中断标志,而不是调用interrupted()方法的实例对象对象的中断标志。
1 2 3 4
| public static boolean interrupted() { //清除 中断标志 return currentThread().isinterrupted(true); }
|
守护线程与用户线程
Java 中的线程分为两类,分别为daemon线程(守护线程)和user线程(用户线程)。在JVM启动时会调用main函数,main函数所在的线程就是一个用户线程,其实在JVM内部同时还启动了好多守护线程,比如垃圾回收线程。那么守线程和用户线程有什么区别呢?区别之一是当最后一个非守护线程结束时,JVM 正常退出,而不管当前是否有守护线程,也就是说守护线程是否结束并不影响JVM退出。言外之意,只要有一个用户线程还没结束,正常情况下JVM就不会退出。
ThreadLocal
多线程访问同一个共享变量时特别容易出现并发问题,特别是在多个线程需要对
共享变量进行写入时。为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步。
同步的措施一般是加锁,这就需要使用者对锁有一定的了解,这显然加重了使用者的负担。那么有没有一种方式可以做到,当创建一个变量后,每个线程对其进行访问的时候访问的是自己线程的变量呢?其实 ThreadLocal就可以做这件事情,虽然 ThreadLocal并不是为了解决这个问题而出现的。