锁和同步

我们可以使用锁来保证同步。

如果不同步的话,多个线程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