首页技术文章正文

多线程2【黑马java培训】

更新时间:2019年07月26日 11时11分02秒 来源:黑马程序员论坛



                              多线程2

3.1 volatile
Volatile的两个特性:
   可见性:对一个volatile变量的读,总是能够看到(任意线程)对这个volatile变量的最后写入。
   原子性:对任意单个volatile变量读写具有原子性,但是类似与volatile++这种复合操作不具有原子性。
3.1.1 volatile实现数据一致
Volatile-读的内存语义如下:
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
线程A在写flag变量后,本地内存A中被线程A更新过两个共享变量的值被刷新到主内存中,此时本地内存A和主内存中的共享变量一致。
当读取一个volatile变量是,JMM会把该线程的本地内存设为无效,线程接下来从主内存中读取共享变量。线程B的读取操作将会导致本地内存B和主内存中的共享变量变成一致。
Volatile之间的通信:
   
(1)线程A写一个volatile变量,实际上是线程A下接下来将要读这个变量的某个线程发出了消息(对共享变量的修改)。
(2)线程B读一个volatile变量,实质上线程B接收了之前某个线程发出的消息。
(3)线程A写一个volatile变量,随后线程B读这个变量,这个过程实际上是线程A通过主内存向线程B发送消息。
3.2 synchronized
   Synchronized的关键字可以修饰方法或者以同步代码块的形式使用,它主要保证多个线程在同一个时刻只有一个线程访问同步代码块或者方法,保证了线程对变量访问的可见性和排他性。
3.2.1 synchroized实现原理
代码块的同步:
  JVM基于进入和退出的monitor对象来实现方法和代码块的同步(每个对象有一个监视器锁monitor,代码块同步是使用monitorenter和monitorexit指令实现的,monitorenter是在编译后插入到同步代码的开始位置,monitorexit是插入到方法结束后的位置和异常处,当一个线程执行到monitorenter将尝试获取对象对应的monitor的所有权(获取锁),如果锁被其他线程持有该线程进入阻塞状态,如果没有直接获monitor执行代码块,执行到monitorexit后释放monitor对象
方法的同步:
  方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
   下图描述对象、监视器、同步队列和线程执行的关系

3.3等待和通知机制
等待和通知的相关方法
方法名称
描述
notify()
随机选择一个在该对象上调用wait()方法的线程,从等待队列转移到同步队列
notifyAll()
解除所有在该对象上调用wait()方法的线程,所有线转移到等待队列
wait()
调用该方法使线程进入waiting状态,只有等待其他线程通知和中断才会返回,调用wait()方法回释放锁
wait(long)
等待一段时间,如果没有通知就超时返回
wait(long,int)
对超时时间更细粒度的控制,可以达到纳秒

实例:

[Java] 纯文本查看 复制代码
public class WaitNotify {
    static boolean flag = true;
    static Object  lock = new Object();
    public static void main(String[] args) throws Exception {
        Thread waitThread = new Thread(new Wait(), "WaitThread");
        waitThread.start();
        TimeUnit.SECONDS.sleep(1);
        Thread notifyThread = new Thread(new Notify(), "NotifyThread");
        notifyThread.start();
    }
    static class Wait implements Runnable {
        public void run() {
            // 加锁,拥有lock的Monitor
            synchronized (lock) {
                // 当条件不满足时,继续wait,同时释放了lock的锁
                while (flag) {
                    try {
                        System.out.println(Thread.currentThread() + " flag is true. wait @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                        lock.wait();
                    } catch (InterruptedException e) {
                    }
                }
                // 条件满足时,完成工作
                System.out.println(Thread.currentThread()+ " flag is false. running @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            }
        }
    }
    static class Notify implements Runnable {
        public void run() {
            // 加锁,拥有lock的Monitor
            synchronized (lock) {
                // 获取lock的锁,然后进行通知,通知时不会释放lock的锁,
                // 直到当前线程释放了lock后,WaitThread才能从wait方法中返回
                System.out.println(Thread.currentThread() + " hold lock. notify @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                lock.notifyAll();
                flag = false;
                TimeUnit.SECONDS.sleep(5);            }
            // 再次加锁
            synchronized (lock) {
                System.out.println(Thread.currentThread()+ " hold lock again. sleep @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                TimeUnit.SECONDS.sleep(5);
            }
        }
    }
}
上述代码执行步骤:
(1)waitThread线程运行获得lock锁,随后调用lock.wait()方法线程进入等待,并释放锁,进入waitQueue队列,调用wait()后面的代码被阻塞。
(2)NotifyThread线程进入同步代码块获得lock锁,调用lock.notifyAll方法唤醒所有等待线 程进入同步队列,休眠5秒,再次加锁休眠5秒。
(3)此时waitThread线程变为阻塞状态,必须等待notifyThread线程休眠结束后释放锁, waitThread线程再次获得锁并从wait方法返回继续执行。
3.4 管道输入/输出流
   管道输入流/管道输出流主要包括如下4中具体实现:PipedOutputStreamPipedInputStream(面向字节)和PipedReaderPipedWriter(面向字符)

   示例代码:
[Java] 纯文本查看 复制代码
public class Piped {
        @SuppressWarnings("resource")
        public static void main(String[] args) throws IOException {
               
                PipedWriter out = new PipedWriter();
                PipedReader in = new PipedReader();
                //将输出流和输入流进行链接
                out.connect(in);
                Thread thread = new Thread(new Print(in),"printThread");
                thread.start();
                int receive=0;
                try {
                        while ((receive=System.in.read())!=1) {
                                out.write(receive);
                        }
                } finally{
                        out.close();
                }
        }
        static class Print implements Runnable{
                private PipedReader in;
                public Print(PipedReader in) {
                        super();
                        this.in = in;
                }
                @Override
                public void run() {
                        int receive =0;
                        try {
                                while((receive=in.read()) !=1){
                                        System.out.println((char)receive);
                                }
                        } catch (IOException e) {
                                e.printStackTrace();
                        }
                }
        }
}
运行该示例输入一组字符串,被printThread进行了原样输出。对于piped类型的流必须先进行绑定,也就是调用connect()方法。
3.5 Thread.join()的使用
[Java] 纯文本查看 复制代码
public class Join {       
public static void main(String[] args) throws InterruptedException {       
Thread currentThread = Thread.currentThread();       
for (int i = 0; i <10; i++) {               
Thread thread = new Thread(new Domino(currentThread),i+"");
        thread.start();
        currentThread=thread;
}
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName());
}
static class Domino implements Runnable{       
private Thread thread;
public Domino(Thread thread) {
super();
this.thread = thread;
        }
public void run() {
try {
                   thread.join();
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
             System.out.println(Thread.currentThread().getName());
}
}
}

上述示例代码中,创建了10个线程,每个线程在调用前一个线程的join()方法,线程1必须等待线程0结束之后才能从join()方法返回。
    每个线程终止的前提是前驱线程终止,每个线程等待前驱线程终止后才能从join()方法返回,这里涉及到了等待/通知机制(等待前驱线程结束,接收前驱线程结束通知)
3.6 ThreadLocal的使用
     ThreadLocal,即线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构,这个结构被绑定在线程上,一个线程可以通过ThreadLocal对象查询到绑定到这个线程上的一个值。
     代码示例:
[Java] 纯文本查看 复制代码
public class ThreadLocalTest {
private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>();
public static void main(String[] args) throws InterruptedException {
ThreadLocalTest.begin();
System.out.println(ThreadLocalTest.end());
}
/**
* 将一个值存放到本地线程中
*/
public static final void begin(){
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
/**
* 将一个中从本地线程中取出
* @return
* @throws InterruptedException
*/
public static final long end() throws InterruptedException{
Long begin = TIME_THREADLOCAL.get();
TimeUnit.SECONDS.sleep(1);
return begin-System.currentTimeMillis();
}
}
   可以通过set(T)方法来设置值,在当前线程下通过get()方法获取到原先设置的值,示例代码中它具有begin()end()两个方法,begin方法可以用来记录方法开始的时间,end方法在方法执行完后调用,用于测试方法的调用时间。





推荐了解热门学科

java培训 Python人工智能 Web前端培训 PHP培训
区块链培训 影视制作培训 C++培训 产品经理培训
UI设计培训 新媒体培训 产品经理培训 Linux运维
大数据培训 智能机器人软件开发




传智播客是一家致力于培养高素质软件开发人才的科技公司“黑马程序员”是传智播客旗下高端IT教育品牌。自“黑马程序员”成立以来,教学研发团队一直致力于打造精品课程资源,不断在产、学、研3个层面创新自己的执教理念与教学方针,并集中“黑马程序员”的优势力量,针对性地出版了计算机系列教材50多册,制作教学视频数+套,发表各类技术文章数百篇。

传智播客从未停止思考

传智播客副总裁毕向东在2019IT培训行业变革大会提到,“传智播客意识到企业的用人需求已经从初级程序员升级到中高级程序员,具备多领域、多行业项目经验的人才成为企业用人的首选。”

中级程序员和初级程序员的差别在哪里?
项目经验。毕向东表示,“中级程序员和初级程序员最大的差别在于中级程序员比初级程序员多了三四年的工作经验,从而多出了更多的项目经验。“为此,传智播客研究院引进曾在知名IT企业如阿里、IBM就职的高级技术专家,集中研发面向中高级程序员的课程,用以满足企业用人需求,尽快补全IT行业所需的人才缺口。

何为中高级程序员课程?

传智播客进行了定义。中高级程序员课程,是在当前主流的初级程序员课程的基础上,增加多领域多行业的含金量项目,从技术的广度和深度上进行拓展“我们希望用5年的时间,打造上百个高含金量的项目,覆盖主流的32个行业。”传智播客课程研发总监于洋表示。




黑马程序员热门视频教程【点击播放】

Python入门教程完整版(懂中文就能学会) 零起点打开Java世界的大门
C++| 匠心之作 从0到1入门学编程 PHP|零基础入门开发者编程核心技术
Web前端入门教程_Web前端html+css+JavaScript 软件测试入门到精通


在线咨询 我要报名
和我们在线交谈!