锁和同步
我们可以使用锁来保证同步。
如果不同步的话,多个线程start后,他们的执行时间和顺序是不确定的。
同步的意思就是 人为的确定 线程间的执行顺序; 可以用加 锁来解决。
用 synchronized 关键字,可同步方法 或者 代码块。
等待和通知
在用锁的时候,如果我们访问的对象是被锁起来了的,这时就是处于 阻塞状态
我们就需要不断的重新去尝试,直到拿到锁为止,这样就比较消耗性能和资源。
可以用 等待 和 通知解决:Object 的 wait()、 notify()、 notifyAll()
wait() 使一个线程进入 无限等待 或 限时等待 状态,同时会 释放锁。wait()必须放在同步块 或 同步方法中。
notify() 用来随机唤醒一个处于 无限等待 或 限时等待 状态的线程。
notifyAll() 用来唤醒 所有处于 无序等待 或 限时等待 状态的线程。
注意:他们作用的对象要是同一把锁,否则这把锁也是没有用的。
信号量
可以用 volatile 关键字来实现信号量通信。
volatile 关键字:
可以保证内存的可见性,
也就是用 volatile 声明的变量,在以一个线程改变了之后,其他线程使立马可以看见改变后的值的。
public class Signal {
private static volatile int signal = 0;
static class ThreadA implements Runnable {
@Override
public void run() {
while (signal <= 50) {
if (signal % 2 == 0) {
System.out.println("threadA : " + signal);
synchronized (this) {
signal = signal + 1; // 不是原子操作,所以要加锁
}
}
}
}
}
static class ThreadB implements Runnable {
@Override
public void run() {
while (signal <= 50) {
if (signal % 2 == 1) {
System.out.println("threadB : " + signal);
synchronized (this) {
signal++; // 不是原子操作,所以要加锁
}
}
}
}
}
public static void main(String[] args) {
// 交替执行
new Thread(new ThreadA()).start(); // 输出奇数
new Thread(new ThreadB()).start(); // 输出偶数。
}
}
注意: signal++ 或 signal = signal + 1 并不是一个原子操作。
信号量的应用场景:
在多个线程(2个线程以上)进行通信时,用信号量机制就比较方便了。
管道
是一种基于 管道流 的通信方法。
JDK 里提供了有 基于字符的: PipedReader 、 PipedWriter、
也有 基于字节的: PipedOutoutStream、 PipedInputStream 类。
public class Pipe {
static class ReaderThread implements Runnable {
private PipedReader reader;
public ReaderThread(PipedReader reader) {
this.reader = reader;
}
@Override
public void run() {
System.out.println("this is reader");
int receive = 0;
try {
while ((receive = reader.read()) != -1) {
System.out.println((char) receive);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
static class WriterThread implements Runnable {
private PipedWriter writer;
public WriterThread(PipedWriter writer) {
this.writer = writer;
}
@Override
public void run() {
System.out.println("this is writer");
int receive = 0;
try {
writer.write("test");
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException, IOException {
PipedReader reader = new PipedReader();
PipedWriter writer = new PipedWriter();
// 要先链接!!!!
writer.connect(reader);
new Thread(new ReaderThread(reader)).start();
Thread.sleep(1000);
new Thread(new WriterThread(writer)).start();
}
}
应用场景:
管道多半都与 IO 有关, 在线程间通信需要传入 字符串 或 文件的话, 就可以使用 管道的方式了。
join() 方法
在当前线程调用另一个线程的 join() 方法,就会使当前线程进入 无限时等待 或 限时等待 状态,
直到另一个线程的 任务执行完毕,当线程才会继续往下执行。
public class Join {
static class ThreadA implements Runnable {
@Override
public void run() {
try {
System.out.println("我是子线程,我先睡5秒。。。");
Thread.sleep(5000);
System.out.println("我是子线程,我睡完5秒了。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new ThreadA());
thread.start();
thread.join(); // thread 方法执行完了,再往下执行。
System.out.println("如果不加 join 方法, 我会先执行。。。。");
}
}
ThreadLocal 类
严格来说, ThreadLocal 类并不属于 线程间的通信范畴,只是他可以存储各个独自有的线程的变量,不被其他线程影响。
内部是一个 弱引用的 Map 来维护。最常用的 set()、get()方法。
当然可以用私有变量来解决,但这样用 ThreadLocal 类后,可以将静态变量和 线程的状态 关联起来。
应用场景:
数据库的链接,Session的管理,有着大量复杂对象的初始化和关闭,
就可以用 类的静态变量来时线程更为 “轻量” 些。
public class ThreadLocalDemo {
static class ThreadA implements Runnable {
private ThreadLocal<String> threadLocal;
public ThreadA(ThreadLocal<String> threadLocal) {
this.threadLocal = threadLocal;
}
@Override
public void run() {
threadLocal.set("A");
try {
// 看看是不是太快的原因,输出和set的一样。
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ThreadA输出:" + threadLocal.get());
}
}
static class ThreadB implements Runnable {
private ThreadLocal<String> threadLocal;
public ThreadB(ThreadLocal<String> threadLocal) {
this.threadLocal = threadLocal;
}
@Override
public void run() {
threadLocal.set("B");
System.out.println("ThreadB输出:" + threadLocal.get());
}
}
public static void main(String[] args) {
ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
new Thread(new ThreadA(stringThreadLocal)).start();
new Thread(new ThreadB(stringThreadLocal)).start();
}
}
InheritableThreadLocal : 他的子线程也可以存取这个副本值。
参考链接
https://redspider.gitbook.io/concurrent/di-yi-pian-ji-chu-pian/5#5-5-2-sleep-fang-fa