本帖最后由 执迷不悟 于 2019-6-25 09:51 编辑
多线程2
3.1 volatileVolatile的两个特性: 可见性:对一个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等待和通知机制等待和通知的相关方法 | | | 随机选择一个在该对象上调用wait()方法的线程,从等待队列转移到同步队列 | | 解除所有在该对象上调用wait()方法的线程,所有线转移到等待队列 | | 调用该方法使线程进入waiting状态,只有等待其他线程通知和中断才会返回,调用wait()方法回释放锁 | | | | |
实例:
[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中具体实现:PipedOutputStream、PipedInputStream(面向字节)和PipedReader、PipedWriter(面向字符)
示例代码: [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方法在方法执行完后调用,用于测试方法的调用时间。
|