Java并发基础<1>

java内存模型基本概念


  • 原子性:32位机器中long型的读写是不具有原子性的。

  • 可见性:在并行关系中,当一个线程修改了某一共享变量的值时,其他的线程是否能立即知道这个修改。就比如CPU1 存在共享变量t的cache值,而CPU2修改了t的值,而CPU1还是直接从cache处取值,数据不一致。,产生可见性的问题。(指令重拍以及编译器优化都可能产生这个问题.)

  • 有序性: 就是执行时候,代码排在前的后执行,原因就是在执行的时候,可能会发生指令重排,重排后的顺序未必一致,线程A的指令执行顺序在线程B看来是没有保证的。

caution:指令重排可以保证串行语义一致,但是没有义务保证多线程间的语义也一致。


哪些指令不能重排 Happen-Before原则


程序顺序原则:一个线程内保证语义的串行性

volatile规则:volatile变量的写先发生于读,保证了可见性。

锁规则:解锁(unlock)必然发生在随后的加锁(lock)前。

传递性:A先于B,B先于C 则A必然先于C;

线程的start()方法,先于它的每一个动作。

线程的所有操作,先于线程的终结(Thread.join())

线程的中断(interrupt())先于被中断的代码。

对象的构造函数执行,结束,先于finalize();


线程的5种状态


NEW—>RUNNABLE—->BLOCKED–>WAITING—>TIMED_WAITING—>TERMINATED


线程的基本操作


新建线程


Thread t1 = new Thread();
t1.start();

注意:
执行t1.run()也可以通过编译,也能正常执行,只不过,却不能新建一个线程,而是作为一个普通的方法调用。


终止线程


Thread.stop()

这个方法已经被标注为废弃的方法了,原因是使用这个方法结束线程的时候,会立即释放该线程所持有的锁,而这些锁是用来保持数据一致性的,如果此时写入数据进行到一半,那么对象会被破坏。此时其他线程读取数据的时候,就可能产生数据不一致的情况。

线程中断


线程中断并不会让线程立即退出,而且通知线程,“有人希望你退出”而后至于之后如何处理,线程自行决定。

public void Thread.interrupt() //中断线程,实例方法
public boolean Thread.isInterrupted() //判断是否被中断,实例方法
public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态,类方法。

中断标志位表示当前线程已经被中断了。

添加中断处理:

if(Thread.currentThread().isInterrupted()){
    System.out.println("Interrupted");
    break;
}

Thread.sleep()方法


public static native void sleep(long millis) throws InterruptedException;

这个方法是让当前线程休眠若干时间,会抛出InterruptedException,不是运行时异常,也就是说程序必须捕获并处理它,当线程处于休眠状态的时候,被中断,就会抛出异常。 如下

  public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("Interrupted");
                        break;
                    }
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        System.out.println("Interrupted When Sleep");
                        //设置中断状态
                        Thread.currentThread().interrupt();
                    }
                    Thread.yield();
                }
            }
        });
        t1.start();
        Thread.sleep(2000);
        t1.interrupt();
    }

输出

:Interrupted When Sleep
:Interrupted

代码在线程休眠以后,收到了t1.interrupt(); 抛出异常,并设置中断状态,然后由于子线程是while(true),所以判断当前线程中断,并break结束线程。

caution:由于Thread.sleep()由于中断而抛出异常,此时会将中断标志位去除,如果不去除,在下一轮循环是将无法捕获这个中断。就是说原来已经中断了,然后捕获到异常,应该清除中断标志位,才能做后续if (Thread.currentThread().isInterrupted())判断处理。


wait和notify

属于Object类的方法:

public final void wait() throws InterruptedException();
public final native void notify()

  当线程A的obj.wait() 时,那么这个线程就会在这个对象上等待,进入等待状态,进入等待队列直到另外的线程调用obj.notify() 显然obj可以作为2个线程通信的有效手段。

在Object对象的等待队列中,可能有多个线程都在等待某一个对象,在执行某一对象的notify方法时,处于等待队列中的某一个会线程会被唤醒。是完全随机的选择,不公平的选择。另外还有notifyAll()方法,用于唤醒所有的线程。
Object.wait()语句需要包含在synchronized中,无论执行wait和notify都需要获取目标对象的监视器。

T1                 T2

取得obejct监视器

obejct.wait()

释放object监视器

                    取得obejct监视器

                    obejct.wait()

等待object监视器     释放object监视器

重获object监视器

继续执行

例子:

import java.util.Date;

/**
 * Created by loveqh on 2016/11/19.
 */
public class SimpleWaitNotify {
    final static Object obj = new Object();

    public static class T1 implements Runnable {
        @Override
        public void run() {
            synchronized (obj) {
                System.out.println(new Date(System.currentTimeMillis())+ "  T1 start");
                try {
                    System.out.println(new Date(System.currentTimeMillis()) + " wait for obj");
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(new Date(System.currentTimeMillis()) + " T1 end");
            }
        }
    }

    public static class T2 implements Runnable {
        @Override
        public void run() {
            synchronized (obj) {
                System.out.println(new Date(System.currentTimeMillis()) + " T2 start notify one thread ");
                obj.notify();
                System.out.println(new Date(System.currentTimeMillis()) + " T2 end");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new T1());
        Thread t2 = new Thread(new T2());
        t1.start();
        t2.start();
    }

}

Thread.sleep() 和Object.wait()的主要区别就是Thread.sleep()不会释放任何资源,而wait()方法会释放目标对象的锁。


suspend 和 resume 挂起和继续执行


依据被抛弃的方法,原因是suspend后必须等到resume后才能继续执行,而suspend不会释放任何资源,其他线程如果想要获取这些锁资源的时候,就会受牵连。但是如果意外地resume在suspend之前执行了,那么被挂起的线程很难被继续执行了,占有的锁也不会被释放。而被挂起的线程的线程状态居然还是Runnable,进入类似死锁的状态。


join和yield 等待线程结束和谦让


join

  join等待另一个线程的结束才执行另外一个线程,比如一个线程的输入是由另外一个线程的输出得到的。那么用到join非常合适。

   join的翻译通常就是加入的意思,在线程的体现得也比较合适,因为一个线程要加入另外一个线程,那么最好的方法就是等着它一起走。

public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException

  第一个表示无限等待,直到目标线程执行完毕,第二个给出了最大等待时间,如果超过最大时间,当前线程就会继续执行下去。

 /**
 * Created by loveqh on 2016/11/19.
 */
public class JoinMain {
    public volatile static int i = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (i = 0; i < 100000; i++) ;
            }
        });
        t.start();
        t.join();
        System.out.println(i);
    }
}

输出

100000

  如果没有t.join() 则可能输出0 或者一个很小的i值,原因是在不同的线程中,t开启了新的线程,而原来的线程继续往下执行了。这样就会直接输出0或者一个很小的值。来试一下结果是输出0,私以为是t线程还没有构建完成,System.out.println(i);就已经执行了。

caution

join()的本质是让调用线程wait()zai 当前线程实例上,核心JDK代码如下
join()内部调用join(0) 而参数为0时执行的代码如下

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    }

所以相当于锁住当前线程,一直在wait(0)直到 isAlive()为false,即目标线程退出。

yield

 public static native void yield();

静态方法,一旦执行,会使当前线程让出CPU,让出CPU不代表当前线程不执行了,让出CPU接下来会进行CPU的争夺,能不能分配到就不一定了。意思是说“我已经做了想做的事情啦,需要休息一下,给其他一些线程一些机会吧”

这种情况一般用在线程优先级低的或者不是很重要的工作,可以尝试Thread.yield()以减少它占用CPU资源。

如本文之前的例子,可以一探究竟,为什么同样都是sleep(2000)但是主线程会先t1.interrupt();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值