Java多线程基础三

本篇文章转载自:Java多线程基础-使用多线程(三) - 简书

|-目录
|  线程间通讯
  -|wait与notify方式
  -|等待(join)方式
  -|管道(pipeStream)方式
|  -线程共享变量【ThreadLocal】

-线程间通讯

1.线程间通讯-wait与notify方式
  线程间通讯:调用wait方法前,首先获得wait对象的锁。执行wait方法后,当前线程释放锁,并且状态变更为Waiting状态,当前任务加入至“预处理队列”中;调用notify方法前,首先获得notify对象的锁。执行完notify synchronized同步代码块后,当前线程释放锁,如果该线程没有任务则该线程状态变更为Terminated状态。

1个实例中的多个synchronized方法使用的是同一把锁

图1-1 线程通讯

public class ThreadWaitNotifyStateDemo {
    public static void main(String[] args) throws InterruptedException{
        final ServiceWaitNotify service = new ServiceWaitNotify();
        Thread thread_1 = new Thread("thread_1") {
            public void run() { 
                try {
                    service.testMethodA(); 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        };
        Thread thread_2 = new Thread("thread_2") {
            public void run() {
                try {
                service.testMethodB();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        };
        thread_1.start();
        thread_2.start();
        Thread.sleep(1100);
        System.out.println(thread_1.getName() + ",state:" + thread_1.getState());
        Thread.sleep(500);
        System.out.println(thread_1.getName() + ",state:" + thread_1.getState());
    }
}

class ServiceWaitNotify {
    
    public void testMethodA() throws InterruptedException {
        Thread.sleep(1000);
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + ",进入Wait!" + Thread.currentThread().getState());
            this.wait();
            System.out.println(Thread.currentThread().getName() + ",退出Wait!" + Thread.currentThread().getState());
        }
        
    }
    
    public void testMethodB() throws InterruptedException {
        Thread.sleep(1500);
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + ",进入notify!" + Thread.currentThread().getState());
            this.notify();
            System.out.println(Thread.currentThread().getName() + ",退出notify!" + Thread.currentThread().getState());
        }
    }
}

图1-2 线程通讯

以上代码:介绍了wait与notify的简单使用,使用这两个方法时必须有同种锁的同步代码块。同时说明了线程执行相关方法时,线程所处状态。如【图1-2】可以看出当线程执行wait方法后所处状态为WATING,当notify线程同步代码块执行完后,wait线程进入RUNNABLE状态,等待wait线程任务执行完后进入TERMINATED状态。

补充1个测试案例,说明两个方法用的同一把锁

public void testMethodB() throws InterruptedException {
        Thread.sleep(1500);
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + ",进入notify!" + Thread.currentThread().getState());
            this.notify();
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName() + ",退出notify!" + Thread.currentThread().getState());
        }
    }



2.线程间通讯-等待(join)方式
  一个线程通过等待的方式,获取另一个线程数据执行的结果,就是说thread.join()要先执行完才能执行后面的

public class ThreadJoinDemo {
    public static void main(String[] args) {
        try {
            JoinThread thread = new JoinThread("thread_1");
            thread.start();
            thread.join();
            System.out.println("" + Thread.currentThread().getName() + ",获得["
                    + thread.getName() + "],value:" + thread.value);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class JoinThread extends Thread {
    public int value;
    
    public JoinThread (String name) {
        super(name);
    }
    
    @Override
    public void run() {
        System.out.println("" + Thread.currentThread().getName() + ",开始运行!");
        try {
            Thread.sleep(1000);
            value = 5;
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("" + Thread.currentThread().getName() + ",结束运行!");
    }
}

图1-3 线程通讯

从【图1-3】可以看出,join方法是等待子线程全部执行完后,主线程才往下执行,这样主线程就能获取子线程修改后的值。
  这里要产生一个怀疑点,join方法的柱塞方式是否与wait方法相似?
能够产生上述怀疑的有两点:
1)join方法会throws InterruptedException【InterruptedException说明:线程在活动前或活动中,处于等待或睡眠状态时,该线程被异常中断则报上述异常。】,故该异常必定与sleep或wait方法有关联。
2)join方法等待属于不确定的等待,故join底层不可能使用sleep。
  验证:

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

从上面源码可以看出,join底层就是使用wait方法,并且wait方法使用的锁是线程对象本身。【这样可以验证调用join方法是否与wait方法一样会释放当前锁对象】

public class ThreadJoinDemo {
    public static void main(String[] args) throws InterruptedException{
        JoinThread thread = new JoinThread("thread_1");
        synchronized (thread) {
            System.out.println("" + Thread.currentThread().getName() + ",获得thread锁!");
            thread.start();
            Thread.sleep(1000);
            System.out.println("" + Thread.currentThread().getName() + ",调用thread.join()");
            thread.join();
            System.out.println("" + Thread.currentThread().getName() + ",结束!");
        }
    }
}

class JoinThread extends Thread {
    public int value;
    
    public JoinThread (String name) {
        super(name);
    }
    
    @Override
    public void run() {
        synchronized (this) {
            System.out.println("" + Thread.currentThread().getName() + ",开始运行!");
            value = 5;
        }
        System.out.println("" + Thread.currentThread().getName() + ",结束运行!");
    }
}

图1-4 线程通讯

以上代码可以看出,如果join不能释放锁,则JoinThread run方法获取不了锁也执行不了方法体中程序。但是【图1-4】完全推翻了上述‘如果假设’,证明了join底层就是wait,调用join方法与wait方法一样会释放当前锁。

3.线程间通讯-管道(pipeStream)方式
  两个线程不借助文本,使用管道【输出管道-写数据,输入管道-读数据】方式直接进行数据传输。
 管道通讯两种方式:
 1)PipedInputStreamPipedOutputStream
 2)PipedReaderPipedWriter
 管道通讯使用注意点:
 1)管道通讯必须建立连接。src.connect(snk)/snk.connect(src)【src:未连接的管道输出流,snk:未链接的管道输入流】。
 2)PipedInputStream和PipedOutputStream不能在同一个线程使用。【会出现死锁现象】

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;


public class ThreadPipeStreamDemo {
    public static void main(String[] args) throws IOException {
        PipedInputStream in = new PipedInputStream();
        PipedOutputStream out = new PipedOutputStream();
        out.connect(in);
        ThreadWrite write = new ThreadWrite("write_thread", out);
        ThreadRead read = new ThreadRead("read_thread", in);
        write.start();
        read.start();
    }
}

class ThreadWrite extends Thread {
    private PipedOutputStream outputStream;
    public ThreadWrite (String name, PipedOutputStream out) {
        super(name);
        this.outputStream = out;
    }
    
    @Override
    public void run() {
        if (null == outputStream) return;
        try {
            System.out.println("" + Thread.currentThread().getName() + ",开始写入数据!");
            for(int i = 0; i < 10; i++) {
                String writeData = "" + (i + 1);
                outputStream.write(writeData.getBytes(), 0 ,writeData.getBytes().length);
            }
            outputStream.flush();
            outputStream.close();
            System.out.println("" + Thread.currentThread().getName() + ",完成!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ThreadRead extends Thread {
    private PipedInputStream inputStream;
    
    public ThreadRead (String name, PipedInputStream in) {
        super(name);
        this.inputStream = in;
    }
    
    @Override
    public void run() {
        if (null == inputStream) return;
        byte[] buffer = new byte[1048];
        System.out.println("" + Thread.currentThread().getName() + ",开始读数据!");
        String data = "";
        try {
            int length = inputStream.read(buffer, 0, buffer.length);
            while (length != -1) {
                String str = new String(buffer, 0, length);
                data += str;
                length = inputStream.read(buffer, 0, buffer.length);
            }
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("" + Thread.currentThread().getName() + ",数据:" + data);
    }
}

图1-5 线程通讯

-线程共享变量【ThreadLocal】

  ThreadLocal类提供了线程局部(thread-local)变量,ThreadLocal与普通变量的区别在于:1,每个线程持有的是变量初始化副本的弱引用【直译:每个线程都有自己的局部变量】。2,ThreadLocal与线程生命周期相关联【线程消失之后,其线程局部实例的所有副本都会被垃圾回收】

public class ThreadLocalDemo {
    public static void main(String[] args) {
        final Tools tools = new Tools();
        Thread thread_1 = new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(5000); //确保getUid在线程2执行完后面执行
                    int num = tools.getUid();
                    System.out.println("" + Thread.currentThread().getName() + ",num:" + num);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "thread_1");
        Thread thread_2 = new Thread(new Runnable() {
            public void run() {
                tools.setUid(10);
                int num = tools.getUid();
                System.out.println("" + Thread.currentThread().getName() + ",num:" + num);
            }
        }, "thread_2");
        thread_1.start();
        thread_2.start();
    }
}
class Tools {
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        protected Integer initialValue() {//ThreadLocal 获取初始默认值
            return 0;
        };
    };

    public int getUid() {
        return threadLocal.get().intValue();  //ThreadLocal get()方法获取当前变量值
    }

    public synchronized void setUid(int num) { //强迫同步观察threadLocal是否能根据线程区分变量
        threadLocal.set(num);  //ThreadLocal set()方法设置当前变量值
    }
}

图1-5 线程共享变量

从结果可以看出,同一个ThreadLocal静态变量但是获取出来的值不同,这就解释了上面局部变量的定义。

-总结

  这里主要讲解线程间通讯,以及线程共享变量简单使用

猜你喜欢

转载自blog.csdn.net/qq_35572013/article/details/127967027