1.sleep函数
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)2.join()函数
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。1. package com.visenergy.thread;
2.
3. /**
4. * Created by zhonghuan on 2018/3/30.
5. * author zhonghuan
6. */
7. public class JoinThreads extends Thread{
8. private String threadName;
9. public JoinThreads(String threadName){
10. super(threadName);
11. this.threadName=threadName;
12. }
13.
14. public void run(){
15. System.out.println(Thread.currentThread().getName()+"线程开始运行!");
16. for (int i = 0; i <5 ; i++) {
17. System.out.println("子线程"+threadName+"运行:"+i);
18. try {
19. sleep(100);
20. }catch (InterruptedException e){
21. e.printStackTrace();
22. }
23. }
24. System.out.println(Thread.currentThread().getName()+"主线程运行结束!");
25. }
26.
27. public static void main(String[] args) {
28. System.out.println("main主线程运行开始");
29. Thread thread=new JoinThreads("A");
30. Thread thread1=new JoinThreads("B");
31. thread.start();
32. thread1.start();
33. System.out.println("main线程运行结束");
34. }
35. }
输出结果为: main主线程运行开始 main线程运行结束 A线程开始运行! B线程开始运行! 子线程A运行:0 子线程B运行:0 子线程B运行:1 子线程A运行:1 子线程B运行:2 子线程A运行:2 子线程B运行:3 子线程A运行:3 子线程B运行:4 子线程A运行:4 A主线程运行结束! B主线程运行结束! 发现主线程比子线程结束的早很多,此时我们使用join()函数,main方法调用修改为如下代码:
1. public static void main(String[] args) {
2. System.out.println("main主线程运行开始");
3. Thread thread=new JoinThreads("A");
4. Thread thread1=new JoinThreads("B");
5. thread.start();
6. try {
7. thread.join();
8. } catch (InterruptedException e) {
9. e.printStackTrace();
10. }
11. thread1.start();
12. try {
13. thread1.join();
14. } catch (InterruptedException e) {
15. e.printStackTrace();
16. }
17. System.out.println("main线程运行结束");
18. }
运行结果: main主线程运行开始 A线程开始运行! 子线程A运行:0 子线程A运行:1 子线程A运行:2 子线程A运行:3 子线程A运行:4 A主线程运行结束! B线程开始运行! 子线程B运行:0 子线程B运行:1 子线程B运行:2 子线程B运行:3 子线程B运行:4 B主线程运行结束! main线程运行结束 main线程等待子线程执行完毕才执行。
3.yield()函数
Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。 结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。4.setPriority()函数
MIN_PRIORITY = 1 NORM_PRIORITY = 5 MAX_PRIORITY = 10 用法: Thread4 t1 = new Thread4(“t1”); Thread4 t2 = new Thread4(“t2”); t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORITY);5.interrupt()函数
不要以为它是中断某个线程!它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!6.wait(), notify(),notifyAll()函数
• notify()唤醒在此对象监视器上等待的单个线程• notifyAll()唤醒在此对象监视器上等待的所有线程
• void wait( ) 导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法
代码示例:通过调用notify()以及wait()方法,控制三个线程有序执行
1. package com.visenergy.thread;
2.
3. /**
4. * Created by zhonghuan on 2018/3/30.
5. */
6. public class NotifyThread implements Runnable{
7. private String name;
8. private Object pre;
9. private Object self;
10. public NotifyThread(String name,Object pre,Object self){
11. this.name=name;
12. this.pre=pre;
13. this.self=self;
14. }
15. @Override
16. public void run(){
17. int count=5;
18. while(count>0){
19. synchronized (pre) {
20. synchronized (self) {
21. count--;
22. System.out.println("线程" + name + "执行:" + count);
23. self.notify();
24. }
25. try {
26. pre.wait();
27. } catch (InterruptedException e) {
28. e.printStackTrace();
29. }
30. }
31. }
32. }
33.
34. public static void main(String[] args) throws InterruptedException {
35. Object a=new Object();
36. Object b=new Object();
37. Object c=new Object();
38. NotifyThread threadA=new NotifyThread("A",c,a);
39. NotifyThread threadB=new NotifyThread("B",a,b);
40. NotifyThread threadC=new NotifyThread("C",b,c);
41. new Thread(threadA).start();
42. Thread.sleep(100);
43. new Thread(threadB).start();
44. Thread.sleep(100);
45. new Thread(threadC).start();
46. Thread.sleep(100);
47. }
48. }
输出结果: 线程A执行:4 线程B执行:4 线程C执行:4 线程A执行:3 线程B执行:3 线程C执行:3 线程A执行:2 线程B执行:2 线程C执行:2 线程A执行:1 线程B执行:1 线程C执行:1 线程A执行:0 线程B执行:0 线程C执行:0
• 当需要调用以上的方法的时候,一定要对竞争资源进行加锁,如果不加锁的话,则会报 IllegalMonitorStateException 异常
• 当想要调用wait( )进行线程等待时,必须要取得这个锁对象的控制权(对象监视器),一般是放到synchronized(obj)代码中。
7.final关键字
• final在Java中是一个保留的关键字,可以声明成员变量、方法、类以及本地变量。一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如果你试图将变量再次初始化的话,编译器会报编译错误。1. public static final String LOAN = "loan";
2. LOAN = new String("loan") //invalid compilation error
• final变量是只读的。
• final也可以声明方法。方法前面加上final关键字,代表这个方法不可以被子类的方法重写。如果你认为一个方法的功能已经足够完整了,子类中不需要改变的话,你可以声明此方法为final。final方法比非final方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。
• 使用final来修饰的类叫作final类。final类通常功能是完整的,它们不能被继承。Java中有许多类是final的,譬如String, Interger以及其他包装类
关于final的重要知识点
1. final关键字可以用于成员变量、本地变量、方法以及类。
2. final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
3. 你不能够对final变量再次赋值。
4. 本地变量必须在声明时赋值。
5. 在匿名类中所有变量都必须是final变量。
6. final方法不能被重写。
7. final类不能被继承。
8. final关键字不同于finally关键字,后者用于异常处理。
9. final关键字容易与finalize()方法搞混,后者是在Object类中定义的方法,是在垃圾回收之前被JVM调用的方法。
10. 接口中声明的所有变量本身是final的。
11. final和abstract这两个关键字是反相关的,final类就不可能是abstract的。
12. final方法在编译阶段绑定,称为静态绑定(static binding)。
13. 没有在声明时初始化final变量的称为空白final变量(blank final variable),它们必须在构造器中初始化,或者调用this()初始化。不这么做的话,编译器会报错“final变量(变量名)需要进行初始化”。
14. 将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
15. 按照Java代码惯例,final变量就是常量,而且通常常量名要大写: private final int COUNT = 10;
16. 对于集合对象声明为final指的是引用不能被更改,但是你可以向其中增加,删除或者改变内容。譬如:
1. private final List Loans = new ArrayList();
2. list.add(“home loan”); //valid
3. list.add("personal loan"); //valid
4. loans = new Vector(); //not valid
我们已经知道final变量、final方法以及final类是什么了。必要的时候使用final,能写出更快、更好的代码的。
8.volatile关键字
被volatile关键字修饰的共享变量,当发生改变的时候会被强制立即写入内存,从而导致别的线程在缓存中读的这个变量会失效,从而重新从内存中读取,进而实现了volatile的可见性。 在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。 请分析以下哪些操作是原子性操作:1. x = 10; //语句1
2. y = x; //语句2
3. x++; //语句3
4. x = x + 1; //语句4
咋一看,有些朋友可能会说上面的4个语句中的操作都是原子性操作。其实只有语句1是原子性操作,其他三个语句都不是原子性操作。
语句1是直接将数值10赋值给x,也就是说线程执行这个语句的会直接将数值10写入到工作内存中。
语句2实际上包含2个操作,它先要去读取x的值,再将x的值写入工作内存,虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作,但是合起来就不是原子性操作了。
同样的,x++和 x = x+1包括3个操作:读取x的值,进行加1操作,写入新的值。
所以上面4个语句只有语句1的操作具备原子性。
也就是说,只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。
不过这里有一点需要注意:在32位平台下,对64位数据的读取和赋值是需要通过两个操作来完成的,不能保证其原子性。但是好像在最新的JDK中,JVM已经保证对64位数据的读取和赋值也是原子性操作了。
从上面可以看出,Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。 那么volatile也是不能保证更大的原子性,它只能实现java内存的原子性操作,但是使用的时候可以保证有序性,在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。使用volatile可以保证这种顺序不会被重排。
volatile保证原子性吗?
1. public class Test {
2. public volatile int inc = 0;
3.
4. public void increase() {
5. inc++;
6. }
7.
8. public static void main(String[] args) {
9. final Test test = new Test();
10. for(int i=0;i<10;i++){
11. new Thread(){
12. public void run() {
13. for(int j=0;j<1000;j++)
14. test.increase();
15. };
16. }.start();
17. }
18.
19. while(Thread.activeCount()>1) //保证前面的线程都执行完
20. Thread.yield();
21. System.out.println(test.inc);
22. }
23. }
一般人会认为执行的结果应该为:上面是对变量inc进行自增操作,由于volatile保证了可见性,那么在每个线程中对inc自增完之后,在其他线程中都能看到修改后的值啊,所以有10个线程分别进行了1000次操作,那么最终inc的值应该是1000*10=10000。 实际上:每次运行的结果都小于10000 原因:volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。即自增操作被分为三个基本的原子性操作:读inc的值,对inc值加1,写入内存。 假如此刻inc=10; 线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了; 然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。 然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。 那么两个线程分别进行了一次自增操作后,inc只增加了1。