java多线程

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。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值