JAVA多线程(基础知识笔记)

一、线程基础

1.1线程的实现方法

(1)继承Thread 类
(2)实现Runnable接口:Thread t1=new Thread(new Runnable(){重写run方法})。或者也可以Thread t1=new Thread(t2)
(3)实现Callable接口,使用futureTask 获得返回值。
自定义线程类中的成员变量针对其他的线程可以分为共享和不共享,多线程之间的交互是很重要的技术点。共享的成员变量就会出现线程安全问题。

1.2线程的常用API

mian()方法是被主线程调用的,自定义线程的构造方法也是父线程调用的,自定义的run()方法是由子线程调用的。

isAlive():判断当前线程是否处于活动状态。初始化(Thread t1=new Thread())之后是false,start之后是true.活动状态就是线程已经启动,且运行没有结束。

在线程的构造方法里面获取的当前线程(Thread.currentThread())是main主线程。在线程的run()方法里面不管是获取的当前线程,还是this都是只得当前线程。

sleep():在指定的毫秒数内让当前执行的线程等待。

getId():获取当前线程的唯一标识

1.3停止线程

停止一个线程意味着在线程处理完成任务之前结束正在执行的操作。

1、使用退出标志,使线程正常退出,也就是当run()方法执行完后退出。

boolean flag=true; 在run()方法里面当true时执行,在要结束时,设置false,退出线程。

2、stop()方法强制结束线程。

stop()方法已经作废,不建议使用。InterruptedException不会捕获到使用stop()方法停止线程的异常,ThreadDeath 异常可以捕获到。

强制让线程停止可能使一些请理性工作得不到完成。另一个原因是使用stop()会对锁定的对象解锁,导致数据得不到同步处理,出现数据不一致性。

3、使用interrupt()方法中断线程

使用interrupt()方法不会真正的结束线程,在当前线程中打上一个停止的标记,

Thread类提供了静态interrupted()方法测试当前线程是否中断,成员方法isinterrupted()方法测试线程是否中断。

成员方法:interrupt():打上中断标记;isinterrupted()判断是否打上了中断标记,如果打上了标记,会抛出interruptException异常。

静态方法:Thread.interrupted(),返回当前的中断标记,并清除。

t.isinterrupted()是检查线程t是否被打上停止标记。Thread.interrupted()是检查主线程是否被打上停止的标记。

当调用静态方法Thread.interrupted()方法,在测试当前线程是否打上了中断标志,同时还会清除这个状态。如果连续两次调用该方法,第二次就会返回false;

那怎么来停止线程呢?

要停止的时候用t.interrupt(),在run()方法里面判断t.isinterrupted()。

如果线程中有sleep代码,不管是否进入到sleep()状态,如果调用了interrupt()方法都会产生异常。

1.4暂停线程

成员方法:suspend()暂停。resume()继续。这两个方法也已经废弃了。因为当一个线程暂停了,将不能释放占用的资源的锁,会导致其他线程无法获得这个资源的锁,也会导致共享的数据不一致的现象。

1.5 yield方法

yield()让线程放弃当前CPU资源,将资源让给其他的任务去占用CPU时间,但是放弃的时间不确定,有可能刚放弃CPU资源,马上又获得了CPU资源。

1.6线程的优先级

在操作系统中线程可以划分优先级,优先级较高的线程会得到更多的CPU资源,也就让CPU会优先执行优先级较高的线程对象中的任务。设置线程优先级有助于帮助线程调度器确定下一次选择哪个线程优先执行。

使用setPriority()方法,优先级1-10,如果设置小于1或大于10,JDK会抛出illegalArgumentException异常。JDK默认设置3个优先级常量,MIN_PRIORITY=1,NORM_PRIORITY=5(默认),MAXPRIORITY=10

获取线程的优先级,可以用getPriority()方法。

线程的优先级具有继承性,在当前线程开启另一个线程,子线程默认优先级与父线程一样。

高优先级的线程总是大部分先执行完,但不代表所有的都先执行完。而且线程的优先也还有随机性,优先级高的不一定每一次都先执行完。

1.7守护线程

Java线程中有两种线程:一种是用户线程,一种是守护线程。

守护线程是一种特殊的线程,当进程中不存在用户线程时,守护线程会自动销毁。典型的守护进程的例子就是垃圾回收线程,当进程中没有用户线程,垃圾回收线程就没有存在的必要,就会销毁。

要先设置为守护进程t.setDaemon(true),再start().

二、线程的同步机制

java中的多线程中的同步,如何在java语言中开发出线程安全的程序,解决非线程安全带来的问题。

2.1synchronized同步方法

1、局部变量永远是线程安全的,因为是私有的。

2、成员变量不是线程安全的。在方法上加入synchronized关键字,将方法同步,可以实现成员变量的线程安全。

3、如果多个对象使用多个锁。synchronize取得的锁都是对象锁,而不是把一段代码或方法作为锁。所以那个线程先执行关键字修饰的方法,那个线程就持有该方法所属对象的锁,其他线程只能处于等待状态,前提是多个线程访问的是同一个对象。如果多个线程访问多个不同对象而不是同一个对象,JVM就会创建多个锁。

4、synchronized方法锁锁定的是整个对象

例如A线程先持有对象的锁,B线程就不可以异步方式调用对象使用synchronized修饰的方法,线程B只能等线程A的方法执行完成释放对象锁才能执行,也就是同步执行。虽然是锁住整个对象,但是B线程可以异步的调用对象里没有使用synchronize关键字的方法。

5、锁重入

关键词synchronized拥有锁重入的功能,也就是说在使用synchronized的时候,当一个线程得到一个对象锁后再次请求这些对象锁时是可以再次得到该对象锁。就是在当前的synchronized方法里面可以进入其他的synchronized方法。

可重入锁也支持父子类继承的环境中。

6、锁的自动释放

当一个线程执行的代码出现异常,线程所持有的锁都会释放。其他线程就可以得到释放的锁。

7、同步不具有继承性

父类的方法有synchronized关键字,子类继承的方法没有,这种情况子类的方法是不会被同步执行的。

2.2synchronized同步语句块

1、synchronized同步方法的缺点:所有的线程都要等待一个个执行。可能在一个方法里面有的操作可能不涉及线程安全的问题,他们也会耗费一定的时间,这样他们的执行也要等。

2、synchronized同步代码块:

synchronized(this){需要同步的代码块} :括号里面设置锁定的内容,this代表锁定当前的对象

3、怎么样使用同步代码块解决

只在一部分代码上同步。

4、半异步半同步

不在synchronized块中的就是异步执行,在synchronized块中的代码就是同步执行的。

5、synchronized代码块间的同步性

在一个对象里使用多个synchronized(this)代码块时,当一个线程访问对象的一个synchronized(this)同步代码块时,其他线程对同一对象的其他synchronized(this)同步代码块的访问会被阻塞,说明synchronized使用的对象锁是一个。

6、synchronized(this)代码块与synchronized同步方法一样,也是锁定当前对象。

7、使用任意对象作为对象锁

除了可以使用synchronized(this)来同步代码块,java还支持任意对象作为对象锁来实现同步功能。这个任意对象通常是成员变量或方法的参数。使用格式:synchronized(非this对象)

(1)在多个线程持有对象锁为同一个对象的情况下,同一时间只有一个线程可以执行synchronized(非this对象)同步代码块中的代码。如果使用的不是同一个对象锁,运行的结果就是异步调用,会交叉运行。

(2)非this对象具有的优点:

1)如果一个类中有很多的synchronized方法,虽然可以同步,受到阻塞,所以影响效率。如果使用synchronized(非this对象)同步代码块,则synchronized(非this对象)代码块中的程序与同步方法是异步的,也不与其他的锁(this)争抢this锁,大大提高运行效率。

2)synchronized(非this对象)还可以解决脏读的问题,注意要是锁定的同一个对象。

synchronized(非this对象 x)格式的写法是将x对象本身作为对象锁,这样:

a、当多个线程同时执行synchronized(x)同步代码块时呈同步效果。

b、当其他的线程执行x对象中的synchronized同步方法时呈同步效果。

c、当其他线程执行x对象方法里面的synchronized(this)代码块时也呈同步效果。

注意:如果其他线程调用不加synchronized关键字的方法时,还是异步调用。

8、synchronized同步静态方法与synchronized(class)代码块

关键字synchronized还可以应用在静态方法上,如果这样就是对当前的*.java对于的Class类进行加锁。从效果上看,与在synchronized非静态方法效果一样,但从本质上讲,synchronized静态方法是给Class类上锁,synchronized非静态方法是对实例对象加锁。他们的区别在于当Class锁可以对类的所有对象实例起作用。

synchronized(class)同步代码块的作用和synchronized静态方法的作用是一样的

9、synchronized(String)同步代码块,如果其他类型的比如synchronized(非this对象)中间的对象都是同样的字符串(常量池),那么就是一个对象,所以这两个方法也会同步。所以如果我们想要避免一些不必要的同步,可以不用String,或者用new Object()重新new对象

10、怎么用synchronized同步代码块解决synchronized同步方法的无限等待的情况?

public class TestSynchronized1 {
    public static void main(String[] args) {
        Service2 service2=new Service2();
        Thread t1=new ThreadA(service2);
        t1.start();
        Thread t2=new ThreadB(service2);
        t2.start();
    }
}
class Service2{
    private Object lock1=new Object();
    private Object lock2=new Object();
     public void foo1(){
        synchronized (lock1){
        System.out.println("foo1方法开始执行。。。");
        boolean iscontinue=true;
        while (iscontinue){

        }
        System.out.println("foo1方法执行结束。。。");
        }
    }
    public void foo2(){
         synchronized (lock2) {
             System.out.println("foo2方法开始执行。。。");
             System.out.println("foo2方法执行结束。。。");
         }
    }
}
class ThreadA extends Thread{
    private Service2 service2;
    public ThreadA(Service2 service2){
        this.service2=service2;
    }

    @Override
    public void run() {
        service2.foo1();
    }
}
class ThreadB extends Thread{
    private Service2 service2;
    public ThreadB(Service2 service2){
        this.service2=service2;
    }

    @Override
    public void run() {
        service2.foo2();
    }
}

上面的代码中foo1()和foo2()方法分别使用不同的对象锁,可以避免foo1()方法死循环的时候其他的线程无法执行foo2()方法。

11、死锁

所谓死锁是指多个线程在运行过程中因争夺资源而造成的一种僵局,当线程处于这种僵持的状态时如果没有外力作用,这些线程都无法继续进行。

public class TestSynchronized3 {
    public static void main(String[] args) throws InterruptedException {
        ThreadA1 t=new ThreadA1();
        t.setFlag("a");
        Thread t1=new Thread(t);
        t1.start();
        Thread.sleep(10);
        t.setFlag("b");
        Thread t2=new Thread(t);
        t2.start();
    }
}
class ThreadA1 extends Thread{
    private String flag;//标志,控制代码以什么样的方式运行
    private Object lock1=new Object();
    private Object lock2=new Object();

    public void setFlag(String flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        try {
            if ("a".equals(flag)) {
                synchronized (lock1) {
                    System.out.println("flag=" + flag);
                    Thread.sleep(3000);
                    synchronized (lock2) {
                        System.out.println("按lock1=>lock2的顺序执行");
                    }
                }
            } else {
                synchronized (lock2){
                    System.out.println("flag=" + flag);
                    Thread.sleep(3000);
                    synchronized (lock1) {
                        System.out.println("按lock2=>lock1的顺序执行");
                    }
                }

            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

死锁产生的原因:
1)系统资源的竞争:系统资源的竞争会导致系统资源不足,以及资源分配不当,导致死锁。
2)线程运行的顺序不合适:线程在运行多个过程中请求和释放资源的顺序不当,会导致死锁。
产生死锁的必要条件:
1)互斥条件:线程要求对所分配的资源进行排他性控制,即在某个时间内资源仅为一个线程所用。
2)请求和操持条件:当线程因请求资源而阻塞时,对已获得的资源不释放。

3)不剥夺条件:线程在已获得的资源未使用之前,不能剥夺,只能在使用完时自己释放。

4、环路等待条件:在发生死锁时,必然存在一个线程等待另一个线程的环形链(lock1=>lock2=>lock1)

12、锁对象的内容改变

在synchronized(非this对象)同步的代码块里面,对这个非this的锁对象进行改变,这样在执行完改变之后,锁的对象发生的改变,其他的线程拥有这个锁对象的线程可以进入该同步代码块。当然要让第一个线程来的及改变锁对象。例如String s="123" 改成了“456”。因为String都是在常量池里,改变了字面量,引用的对象也变了。

注意:只要对象不变,即使对象的属性被改变了,运行的结果还是同步的。

2.3volatile关键字

volatile关键字是使变量可以在多个线程之间可见。

public class TestVolatile {
    public static void main(String[] args) {
        Service4 service4=new Service4();
        service4.foo();
        System.out.println(Thread.currentThread().getName()+"准备停止foo方法的循环");
        service4.flag=false;
    }


}
class Service4{
    public boolean flag=true;
    public void foo(){
        System.out.println("foo开始运行。。。");
        while (flag){

        }
        System.out.println("foo运行结束。。。");
    }
}

程序开始后并不能用flag停止下来,因为主线程一直在while循环里面。

public class TestVolatile {
    public static void main(String[] args) throws InterruptedException {
        Service4 service4=new Service4();
//        service4.foo();
        service4.start();
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName()+"准备停止foo方法的循环");
        service4.flag=false;
    }


}
class Service4 extends Thread{
    public boolean flag=true;
    public void foo(){
        System.out.println("foo开始运行。。。");
        while (flag){

        }
        System.out.println("foo运行结束。。。");
    }

    @Override
    public void run() {
        foo();
    }
}

这种方法是也是不能够停止的。这是因为JDK8以上版本默认用的是服务端模式,会对虚拟机优化。
在启动线程时,变量public boolean flag=true存储在公共堆栈及线程的私有堆栈中。当使用服务器模式时为了线程运行的效率,线程一直在私有堆栈中取得flag的值一直都是true.当service设置flag时,设置的是公共堆栈中的flag,所以一直都是死循环状态,当使用volatile修饰成员变量后,会强制线程从公共堆栈中获取变量的值。
synchronized和volatile的区别:
(1)volatile是线程同步的轻量级实现,他的性能比synchronized好,并且volatile只能修饰变量。而synchronized可以修饰方法及代码块。随着jdk的版本更新,synchronized在执行效率也得到了提升,在开发中synchronized的使用率还是较高。
(2)多线程访问volatile不会发生阻塞,而synchronized会发生阻塞。
(3)volatile能保证数据的可见性,不能保证原子性,而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存的数据做同步。
(4)volatile解决的是变量在多个线程之间的可见性,而synchronized是解决多个线程之间访问资源的同步性。

三、线程间的通信

线程是程序中独立的个体,但这些个体如果不经过处理就能成为一个整体。线程间的通信就是成为整体的必用方案之一,可以说使线程间通讯后,线程之间的交互性会更强大,大大提高CPU复用率还会使程序员对各线程任务处理过程中进行有效的把控与监督。
1、wait和notify
不使用wait和notify进行线程间通信会发生什么?

public class TestWait {
    public static void main(String[] args) {
        List list=new ArrayList();
        Thread1 t1=new Thread1(list);
        t1.start();
        Thread2 t2=new Thread2(list);
        t2.start();
    }
}
class Thread1 extends Thread{
    private List list;
    public Thread1(List list){
        this.list=list;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                list.add(i);
                System.out.println("添加了" + (i + 1) + "个元素");
                Thread.sleep(1000);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
class Thread2 extends Thread{
    //强制线程从公共堆栈中获取
    volatile private  List list;
    public Thread2(List list){
        this.list=list;
    }

    @Override
    public void run() {
        try {
        while (true){
            if (list.size()==5){
                System.out.println("list已经有5个数据了,线程B退出");
                throw new InterruptedException();
            }
        }}
        catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

这样通过在list上加volatile关键可以强制线程2每次都从公共堆栈获取list,这样可以实现线程2对线程1操作的监控,但是这是一直轮训的方式。如果轮询的时间间隔很小,更浪费CPU资源,如果轮询时间间隔很大,可能会取不到想要的数据。所以就需要一种机制减少CPU资源的浪费,而且还可以实现在多个线程间通信。它就是wait和notify。
2、什么是等待和通知机制?
厨师和服务员上菜就是等待和通知的机制。
前面的例子也可以实现通信,原因是多个线程共同访问同一个变量,但是这种机制不是等待和通知的机制,两个线程完全是主动式的读取同一个共享变量,在花费读取时间的基础上,读取到的是不是我们想要的并不确定。所以使用等待和通知的机制满足开发的需求。
3、wait/notify机制的实现
wait()方法的作用是使当前正在执行的线程进入正在等待的状态,wait()方法是Object类的方法,该方法是用来将当前线程放入到预执行队列,并且在wait()所在的代码行停止执行,知道接到通知或中断为止。在调用wait()方法之前,线程必须获得该对象的对象锁,也就是说只能在同步方法或同步代码块中调用wait()方法。如果在执行wait()方法后,当前线程锁会自动释放。当wait()方法返回后,线程会与其他线程竞争获得锁。

public static void main(String[] args) {
        try{
            String s=new String();
            System.out.println("同步代码块之前");
          synchronized (s){
              System.out.println("同步代码块中wait前");
              s.wait();
              System.out.println("wait之后");
          }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

notify()方法也要在同步方法或者同步代码块中使用,在调用前线程必须获得该对象的对象锁,如果没有获得对象的锁,也会抛illegalMonitorStateException。这个方法是用来通知那些可能等待锁对象的其他线程,如果有多个线程等待,由线程调度器随机挑选一个处于该对象wait()状态的线程,向其发出通知,并使等待线程获取该对象的对象锁。
在执行notify()方法后,当前线程不会马上释放该对象锁,wait状态的线程也不能马上获得该对象锁,要等到执行notify()方法的线程将任务执行完成后,也就是退出synchronized代码块之后,当前线程才会释放锁,其他wait状态的线程才能获得该对象锁。
当第一个获得该对象锁的wait线程运行完成后,释放该对象锁,其他还在wait该对象锁的线程还会处于阻塞状态,除非该对象再次调用notify()方法。

public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        Object lock=new Object();
        Thread t1=new Demo3ThreadA(lock);
        t1.start();
        Thread.sleep(2000);
        Thread t2=new Demo3ThreadB(lock);
        t2.start();
    }

}
class Demo3ThreadA extends Thread{
    private Object lock;
    public Demo3ThreadA(Object lock){
        this.lock=lock;
    }

    @Override
    public void run() {
        try{
            synchronized (lock){
                System.out.println("线程A开始等待。。"+System.currentTimeMillis());
                lock.wait();
                System.out.println("线程A结束等待。。"+System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
class Demo3ThreadB extends Thread{
    private Object lock;
    public Demo3ThreadB(Object lock){
        this.lock=lock;
    }

    @Override
    public void run() {
        try {
        synchronized (lock){
            System.out.println("线程B准备发出通知。。"+System.currentTimeMillis());
            lock.notify();
            System.out.println("线程B结束发出通知。。"+System.currentTimeMillis());
            Thread.sleep(1000);
        }}catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

简单的说就是wait使线程停止运行,notify使停止的线程继续运行。
4、wait()方法自动释放锁与notify()方法不会释放锁
还可以在上面的代码,执行notify()方法之后,要执行完当前的synchronized代码块才会释放对象锁。执行wait()方法是马上就释放当前的对象锁。
5、当wait()方法遇到interrupt方法

public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        Object lock=new Object();
        Thread t1=new Demo4Thread(lock);
        t1.start();
        Thread.sleep(1000);
        t1.interrupt();
    }
}
class Demo4Service{
    public void foo(Object lock){
        try{
            synchronized (lock){
                System.out.println("准备开始等待");
                lock.wait();
                System.out.println("结束等待");
            }
        }catch (InterruptedException e){
            System.out.println("出现异常是因为wait状态的线程别interrupt了");
            e.printStackTrace();
        }
    }
}
class Demo4Thread extends Thread{
    private Object lock;
    public Demo4Thread(Object lock){
        this.lock=lock;
    }

    @Override
    public void run() {
        Demo4Service service=new Demo4Service();
        service.foo(lock);
    }
}

当线程呈wait状态时,调用线程对象的interrupt方法会产生InterruptedException异常。
通过前面的例子总结出:
(1)执行完同步代码块就会释放锁
(2)在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。
(3)在执行同步代码块的过程中,执行锁对象的wait()方法,也会释放对象锁,而此线程对象会进入线程等待池中等待被唤醒。

6、只唤醒一个线程或者所有的线程

public class Demo5 {
    public static void main(String[] args) throws InterruptedException {
        Object lock=new Object();
        Thread t1=new Demo5ThreadA(lock);
        t1.start();
        Thread.sleep(1000);
        Thread t2=new Demo5ThreadA(lock);
        t2.start();
        Thread.sleep(1000);
        Thread t3=new Demo5ThreadB(lock);
        t3.start();
    }
}
class Demo5Service{
    public void foo(Object lock){
        try{
            synchronized (lock){
                System.out.println(Thread.currentThread().getName()+"准备wait释放锁");
                lock.wait();
                System.out.println(Thread.currentThread().getName()+"得到了对象锁");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
class Demo5ThreadA extends Thread{
    private Object lock;
    public Demo5ThreadA(Object lock){
        this.lock=lock;
    }
    @Override
    public void run() {
        Demo5Service service=new Demo5Service();
        service.foo(lock);
    }
}
class Demo5ThreadB extends Thread{
    private Object lock;
    public Demo5ThreadB(Object lock){
        this.lock=lock;
    }
    @Override
    public void run() {
        synchronized (lock){
            lock.notifyAll();
//                lock.notify();
//                Thread.sleep(1000);
//                lock.notify();
            System.out.println(Thread.currentThread().getName()+"通知其他线程");
        }
    }
}

调用notify()方法只会通知一个wait该对象锁的线程唤醒,调用多次可以唤醒多个wait该对象锁的线程,不能保证系统中确定有多少个线程,所以可以使用notifyAll()唤醒所有等待对象锁的线程。

7、wait(long)的使用
带一个long参数的wait方法参数的作用是等待某一时间内是否有其他线程唤醒这个等待的线程,如果超过等待时间会自动唤醒。

    public static void main(String[] args) throws InterruptedException {
        Object lock=new Object();
        Thread t1=new Demo6ThreadA(lock);
        t1.start();
        Thread.sleep(4000);
        Thread t2=new Demo6ThreadB(lock);
        t2.start();
    }
}
class Demo6ThreadA extends Thread{
    private Object lock;
    public Demo6ThreadA(Object lock){
        this.lock=lock;
    }
    @Override
    public void run() {
        try{
            synchronized (lock){
                System.out.println("进入同步代码块"+System.currentTimeMillis());
                lock.wait(3000);
                System.out.println("唤醒于"+System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
class Demo6ThreadB extends Thread{
    private Object lock;
    public Demo6ThreadB(Object lock){
        this.lock=lock;
    }
    @Override
    public void run() {
        synchronized (lock){
            lock.notify();
            System.out.println("线程B唤醒其他线程");
        }
    }
}

sleep(long)与wait(long)非常相像,都是在指定时间后线程会自动唤醒,区别在于sleep是不会释放对象锁的,而wait()方法会释放对象锁。
8、通知过早
如果通知过早就会打乱程序正常运行。

Object lock=new Object();
        Thread t1=new Demo7ThreadA(lock);//执行wait
        Thread t2=new Demo7ThreadB(lock);//执行notify
        t2.start();
        Thread.sleep(100);
        t1.start();

解决方法:可以设置一个flag,当执行notify()时,设置为false,在线程执行wait()之前检查flag就可以避免notify先运行之后,再又执行wait()导致不能被唤醒。注意这个flag变量要是一个对象,不能像String一样导致对象锁变化。
9、wait的条件发生变化
在使用wait/notify模式时,需要注意另外一种情况,就是wait等待的条件发生了变化,很容易会导致程序逻辑的混乱。

public class Demo8 {
    public static void main(String[] args) throws InterruptedException {
        Demo8Service service=new Demo8Service();
        Thread t1=new Demo8ThreadB(service);
        t1.start();
        Thread t2=new Demo8ThreadB(service);
        t2.start();
        Thread.sleep(3000);
        Thread t3=new Demo8ThreadA(service);
        t3.start();
    }

}
class Demo8Service{
    private List list=new ArrayList();
    private Object lock=new Object();
    public void add(){
        synchronized (lock){
            list.add("a");
            lock.notifyAll();
        }
    }
    public void substrac(){
        try{
            synchronized (lock){
                if (list.size()==0){
                    System.out.println(Thread.currentThread().getName()+"开始等待数据");
                    lock.wait();
                    System.out.println(Thread.currentThread().getName()+"结束等待");
                }
                if (list.size()>0)//不加判断可能会出错
                list.remove(0);
                System.out.println(Thread.currentThread().getName()+":list 的大小是"+list.size());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
}
class Demo8ThreadA extends Thread{
    private Demo8Service service;
    public Demo8ThreadA(Demo8Service service){
        this.service=service;
    }

    @Override
    public void run() {
      service.add();
    }
}
class Demo8ThreadB extends Thread{
    private Demo8Service service;
    public Demo8ThreadB(Demo8Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.substrac();
    }
}

10、生产者/消费者模式的实现
(1)一个生产者与一个消费者(操作值)

public class Demo9 {
    public static String value="";

    public static void main(String[] args) {
        Object lock=new Object();
        Thread t1=new Demo9producerThread(lock);
        t1.start();
        Thread t2=new Demo9consumerThread(lock);
        t2.start();
    }
}
class Demo9producerThread extends Thread{
    private Object lock;
    public Demo9producerThread(Object lock){
        this.lock=lock;
    }
    @Override
    public void run() {
        try {
            while (true){
                synchronized (lock){
                    if (!"".equals(Demo9.value)){
                        lock.wait();
                    }
                    String value=System.currentTimeMillis()+"_"+System.nanoTime();
                    System.out.println("Set的值是"+value);
                    Demo9.value=value;
                    lock.notify();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
class Demo9consumerThread extends Thread{
    private Object lock;
    public Demo9consumerThread(Object lock){
        this.lock=lock;
    }
    @Override
    public void run() {
        try{
            while (true){
                synchronized (lock){
                    if ("".equals(Demo9.value)){
                        lock.wait();
                    }
                    System.out.println("Get的值是"+Demo9.value);
                    Demo9.value="";
                    lock.notify();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

如果在这个基础上设计多生产者和多消费者,在运行过程中很有可能会出现假死的情况,也就是所有的线程都是wait状态。
(2)多生产者与多消费者(操作值)

public class Demo10 {
    public static String value="";

    public static void main(String[] args) {
        Object lock=new Object();
        int size=2;
        Thread[] producers=new Thread[size];
        Thread[] consumers=new Thread[size];
        for (int i=0;i

假设生产者A在生产数据,其他3个线程(生产者B、消费者A、消费者B)都是呈等待状态,当生产者A生产完成后,随机唤醒1个线程,刚好唤醒了生产者B,生产者B发现value里面有数据,所以进入等待状态(生产者A竞争锁,生产者B、消费者A、消费者B等待状态),A又重新获得锁但是它发现自己之前创建出来的值还没有被消费,所以又进入等待状态,结果是4个线程都处于等待状态。怎么解决这个问题?使用notifyall()方法唤醒所有等待的线程,保证生产出来的值会被消费掉。
(3)一生产者和一消费者(操作集合)

public class Demo11 {
    public static void main(String[] args) {
        Demo11V v=new Demo11V();
        Thread producer=new Demo11Producer(v);
        producer.setName("生产者");
        producer.start();
        Thread consumer=new Demo11Consumer(v);
        consumer.setName("消费者");
        consumer.start();
    }

}
class Demo11V{
    private List list=new ArrayList<>();
    synchronized public void push(String val){
        try {
            if (list.size()==1){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            list.add(val);
            System.out.println(Thread.currentThread().getName()+"添加数据"+val);
            System.out.println(Thread.currentThread().getName()+"还有"+list.size()+"个数据");
            this.notify();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
    synchronized public void pop(){
        String returnString;
        try {
            if (list.size()==0){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            returnString=list.get(0);
            list.remove(0);
            System.out.println(Thread.currentThread().getName()+"消费数据 "+returnString);
            System.out.println(Thread.currentThread().getName()+"还有"+list.size()+"个数据");
            this.notify();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }

}
class Demo11Producer extends Thread{
    private Demo11V v;
    public Demo11Producer(Demo11V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.push(Math.random()+"");
        }
    }
}
class Demo11Consumer extends Thread{
    private Demo11V v;
    public Demo11Consumer(Demo11V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.pop();
        }
    }
}

(4)一生产者和多消费者(操作集合)

public class Demo12 {
    public static void main(String[] args) {
        Demo12V v=new Demo12V();
        Thread producer=new Demo12Producer(v);
        producer.setName("生产者");
        producer.start();
        Thread[] consumers=new Thread[5];
        for (int i=0;i list=new ArrayList<>();
    synchronized public void push(String val){
        try {
            if (list.size()==1){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            list.add(val);
            System.out.println(Thread.currentThread().getName()+"添加数据"+val);
            System.out.println(Thread.currentThread().getName()+"还有"+list.size()+"个数据");
            this.notify();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
    synchronized public void pop(){
        String returnString;
        try {
            while (list.size()==0){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            returnString=list.get(0);
            list.remove(0);
            System.out.println(Thread.currentThread().getName()+"消费数据 "+returnString);
            System.out.println(Thread.currentThread().getName()+"还有"+list.size()+"个数据");
            this.notifyAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }

}

class Demo12Producer extends Thread{
    private Demo12V v;
    public Demo12Producer(Demo12V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.push(Math.random()+"");
        }
    }
}

class Demo12Consumer extends Thread{
    private Demo12V v;
    public Demo12Consumer(Demo12V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.pop();
        }
    }
}

注意:
1、消费者在判断集合是否有数据时不能使用if,有可能重复唤醒的还是消费者,需要使用while语句判断,保证集合中一定是有数据的
2、消费者唤醒线程不使用notify,notify是随机唤醒一个线程,如果唤醒的还是消费者,将进入无限等待。所以需要使用notifyAll保证可以唤醒生产者重新生产数据
(5)多生产者一个消费者(操作集合)

public class Demo13 {
    public static void main(String[] args) {
        Demo13V v=new Demo13V();
        Thread[] producers=new Thread[5];
        for (int i=0;i list=new ArrayList<>();
    synchronized public void push(String val){
        try {
            while (list.size()==1){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            list.add(val);
            System.out.println(Thread.currentThread().getName()+"添加数据"+val);
            System.out.println(Thread.currentThread().getName()+"还有"+list.size()+"个数据");
            this.notifyAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
    synchronized public void pop(){
        String returnString;
        try {
            if (list.size()==0){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            returnString=list.get(0);
            list.remove(0);
            System.out.println(Thread.currentThread().getName()+"消费数据 "+returnString);
            System.out.println(Thread.currentThread().getName()+"还有"+list.size()+"个数据");
            this.notify();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }

}

class Demo13Producer extends Thread{
    private Demo13V v;
    public Demo13Producer(Demo13V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.push(Math.random()+"");
        }
    }
}

class Demo13Consumer extends Thread{
    private Demo13V v;
    public Demo13Consumer(Demo13V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.pop();
        }
    }
}

注意:
1、因为有多个生产者,如果要保证集合中始终不超过一个元素,应该使用while判断,同时生产完数据后应该使用notifyAll保证唤醒那一个消费的线程,防止所有线程都处于等待状态。
(6)多生产者与多消费者(操作集合)

public class Demo14 {
    public static void main(String[] args) {
        Demo14V v = new Demo14V();
        int size = 5;
        Thread[] producers = new Thread[size];
        Thread[] consumers = new Thread[size];
        for (int i = 0; i < size; i++) {
            producers[i] = new Demo14Producer(v);
            producers[i].setName("生产者" + i);
            producers[i].start();
            consumers[i] = new Demo14Consumer(v);
            consumers[i].setName("消费者" + i);
            consumers[i].start();
        }
    }
}
class Demo14V{
    private List list=new ArrayList<>();
    synchronized public void push(String val){
        try {
            while (list.size()==1){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            list.add(val);
            System.out.println(Thread.currentThread().getName()+"添加数据"+val);
            System.out.println(Thread.currentThread().getName()+"还有"+list.size()+"个数据");
            this.notifyAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
    synchronized public void pop(){
        String returnString;
        try {
            while (list.size()==0){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            returnString=list.get(0);
            list.remove(0);
            System.out.println(Thread.currentThread().getName()+"消费数据 "+returnString);
            System.out.println(Thread.currentThread().getName()+"还有"+list.size()+"个数据");
            this.notifyAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }

}

class Demo14Producer extends Thread{
    private Demo14V v;
    public Demo14Producer(Demo14V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.push(Math.random()+"");
        }
    }
}

class Demo14Consumer extends Thread{
    private Demo14V v;
    public Demo14Consumer(Demo14V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.pop();
        }
    }
}

3.2join方法

在很多情况下,都由主线程创建并运行子线程,如果子线程中需要进行大量的耗时运行,主线程往往将早于子线程结束。这时,如果主线程想等待子线程执行完成后再结束,比如子线程处理一个数据,主线程必须要取得这个数据处理的结果,可以使用join方法。join方法是等待线程对象销毁。
1、join方法的使用

public class Demo15 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Demo15Thread();
        t1.start();
        t1.join();
        System.out.println("子线程执行完成之后再执行");
    }
}
class Demo15Thread extends Thread{
    @Override
    public void run() {
        try {
            int val= (int) (Math.random()*10000);
            System.out.println("需要等待"+val+"毫秒");
            Thread.sleep(val);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

join方法可以使所属的线程对象(子线程)执行run方法中的任务,而使当前线程main进行无限期的阻塞。等待子线程销毁后再继续执行主线程后面的代码。方法join具有能使线程排队运行的作用,有些类似于同步代码的执行效果。
join与synchronized的区别:
join的内部使用wait方法进行等待,而synchronized使用的是对象锁的机制作为同步。
2、join方法与异常

public class Demo16 {
    public static void main(String[] args) throws InterruptedException {
        Demo16ThreadB t=new Demo16ThreadB();
        t.start();
        Thread.sleep(10);
        Thread t1=new Demo16ThreadC(t);
        t1.start();
    }
}
class Demo16ThreadA extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            //耗时操作
            String s = new String();
            Math.random();
        }
    }
}
class Demo16ThreadB extends Thread {
    @Override
    public void run() {
        try {
            Thread t1 = new Demo16ThreadA();
            t1.start();
            t1.join();
            System.out.println("线程B正常结束");
        } catch (InterruptedException e) {
            System.out.println("线程B异常结束");
            e.printStackTrace();
        }
    }
}
class Demo16ThreadC extends Thread {
    private Demo16ThreadB threadB;

    public Demo16ThreadC(Demo16ThreadB threadB) {
        this.threadB = threadB;
    }
    @Override
    public void run() {
        threadB.interrupt();
    }
}

join与interrupt方法如果彼此遇到,则会出现异常,但是进程并没有结束,原因是join的所属线程A还在继续运行,A没有出现异常,还是正常状态下执行。(就是说如果主线程在等待子线程join结束的过程中,被interrupt打断,就会抛出异常)
3、sleep(long)不会释放锁,而join(long)底层还是wait,所以会释放锁。join(2000)的运行效果和sleep(2000)是一样的。
4、方法join(long)的功能是在内部使用wait(long)来实现同步,所以join(long)具有释放同步锁的特点。

3.3 ThreadLocal类

变量值共享可以使用静态变量形式,所有的线程都可以使用同一个静态变量。如果想使每一个线程都有自己的共享变量如何解决?java提供ThreadLocal类解决这个问题。
ThreadLocal类主要解决每个线程绑定自己的值,可以将ThreadLocal类比喻成全局存储数据的盒子,盒子中可以存储每个线程的私有数据。

 public static void main(String[] args) {
        ThreadLocal t1=new ThreadLocal();
        if (t1.get()==null){//获取当前线程存储的数据
            System.out.println("从未放过值");
            t1.set("a");//保存当前线程的数据
        }
        System.out.println(t1.get());
        System.out.println(t1.get());
    }

ThreadLocal,使用get方法获取当前线程保存的数据,使用set方法将数据保存到当前线程对于的ThreadLocal。

4、lock

4.1ReetranLock

在java多线程中,可以使用synchronized关键字来实现线程之间的同步互斥,但在jdk1.5中新增加ReentranLock类也可以达到同样的效果,并且在扩展功能上更加强大,比如具有嗅探锁定、多路分支通知等,而且在使用上也比synchronized更加灵活。

4.1.1ReentranLock的使用

public class Demo1 {
    public static void main(String[] args) {
        Lock lock=new ReentrantLock();
        Thread t1=new Demo1Thread(lock);
        Thread t2=new Demo1Thread(lock);
        Thread t3=new Demo1Thread(lock);
        t1.start();
        t2.start();
        t3.start();
    }
}
class Demo1Thread extends Thread{
    private Lock lock;
    public  Demo1Thread(Lock lock){
        this.lock=lock;
    }
    @Override
    public void run() {
        lock.lock();//加上同步锁
        for (int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+","+(i+1));
        }
        lock.unlock();//解开同步锁
    }
}

当前线程打印完毕后将锁释放,其他线程才可以继续打印。
调用lock()方法的线程就会持有对象锁,其他线程只能等待锁被释放(调用unlock())才可以再次争抢锁,效果和使用synchronized关键字一样,线程之间还是按照顺序执行。
2、ReentrantLock实现wait和notify
使用synchronized和wait/notify可以实现等待/通知模式。ReentrantLock类也可以实现同样的功能,但需要借助Condition对象。Condition是jdk5出现的技术,使用它会有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里面可以创建多个Condition实例,线程对象可以注册在指定的Condition中,从而可以有选择地进行线程通知,在通知线程上更加灵活。
在使用wait/notifyAll方法进行通知的时候,被通知的线程是JVM随机选择的,但使用ReentrantLock结合Condition类可以实现选择性通知,这个功能是非常重要的,而且在Condition类中是默认提供的。
而synchronized就相当于整个Lock对象中就只有一个Condition对象,所有的线程都是注册在一个Condition对象上的,所以只能在notifyAll的时候是没有选择权的,会出现效率问题。

public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        Demo3Service service = new Demo3Service();
        Thread t1 = new Demo3Thread(service);
        t1.start();
        Thread.sleep(2000);
        service.signal();
    }
}

class Demo3Service {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void await() {
        try {
            lock.lock();
            System.out.println("await方法开始于" + System.currentTimeMillis());
            condition.await();//需要在同步代码中调用
            System.out.println("await方法结束于" + System.currentTimeMillis());
            lock.unlock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void signal() {
        lock.lock();
        System.out.println("signal方法开始于" + System.currentTimeMillis());
        condition.signal();//需要在同步代码中调用
        lock.unlock();
    }
}

class Demo3Thread extends Thread {
    private Demo3Service service;

    public Demo3Thread(Demo3Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.await();
    }
}

Condition 是通过ReentrantLock对象创建的,condition有await()方法和signal()方法,分别用来等待和通知,注意他们两个也是必须在同步代码中使用。
3、使用Condition唤醒不同的线程
分别唤醒不同的线程,就需要使用多个condition 对象,也就是Condition对象可以唤醒部分指定的线程,有助于提高程序的运行效率。

public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        Demo4Service service=new Demo4Service();
        Thread t1=new Demo4ThreadA(service);
        t1.start();
        Thread t2=new Demo4ThreadB(service);
        t2.start();
        Thread.sleep(2000);
        service.signalAll_B();
    }
}
class Demo4Service{
    private Lock lock=new ReentrantLock();
    private Condition conditionA=lock.newCondition();
    private Condition conditionB=lock.newCondition();
    public void awaitA(){
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"开始执行awaitA方法"+System.currentTimeMillis());
            conditionA.await();
            System.out.println(Thread.currentThread().getName()+"结束执行awaitA方法"+System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void awaitB(){
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"开始执行awaitB方法"+System.currentTimeMillis());
            conditionB.await();
            System.out.println(Thread.currentThread().getName()+"结束执行awaitB方法"+System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void signalAll_B(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"唤醒所有的线程在"+System.currentTimeMillis());
            conditionB.signalAll();
        }finally {
            lock.unlock();
        }
    }
}
class Demo4ThreadA extends Thread{
    private Demo4Service service;
    public Demo4ThreadA(Demo4Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.awaitA();
    }
}
class Demo4ThreadB extends Thread{
    private Demo4Service service;
    public Demo4ThreadB(Demo4Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.awaitB();
    }
}

使用不同的condition对象调用await()方法。
5、使用ReentrantLock实现生产者/消费者

public class Demo5 {
    public static void main(String[] args) {
        Demo5Service service=new Demo5Service();
//        Thread t1=new Demo5Producer(service);
//        t1.start();
//        Thread t2=new Demo5Consumer(service);
//        t2.start();
        int size=5;
        Thread[] producers=new Thread[size];
        Thread[] consumers=new Thread[size];
        for (int i=0;i

以上是ReentrantLock实现多生产者多消费者模式。
6、公平锁与非公平锁。
Lock分为公平锁和非公平锁。公平锁表示线程获得锁的顺序是按照加锁的顺序来分配的,也就是先来先得。而非公平锁就是一种锁的抢占机制,是随机获得锁,与公平锁不一样的就是先来的不一定先得到锁,这种方法可能会导致某些线程一直拿不到锁,导致不公平的现象。

public class Demo6 {
    public static void main(String[] args) throws InterruptedException {
        Demo6Service service=new Demo6Service(true);
        Thread[] threads=new Thread[20];
        for (int i=0;i

7、实现线程的顺序执行

public class Demo7 {
   private static Lock lock=new ReentrantLock();
   private static Condition condition1=lock.newCondition();
   private static Condition condition2=lock.newCondition();
   private static Condition condition3=lock.newCondition();
   volatile private static int nextPrintWho=1;
   public static void main(String[] args) {
       Thread t1=new Thread(){
           @Override
           public void run() {
               try {
                lock.lock();
                while (nextPrintWho!=1){
                    condition1.await();}
                for (int i=0;i<3;i++){
                    System.out.println("线程1打印第"+(i+1)+"次");
                }
                nextPrintWho=2;
                condition2.signalAll();
               }catch (InterruptedException e){
                   e.printStackTrace();
               }finally {
                   lock.unlock();
               }
           }
       };
       Thread t2=new Thread(){
           @Override
           public void run() {
               try {
                   lock.lock();
                   while (nextPrintWho!=2){
                       condition2.await();}
                   for (int i=0;i<3;i++){
                       System.out.println("线程2打印第"+(i+1)+"次");
                   }
                   nextPrintWho=3;
                   condition3.signalAll();
               }catch (InterruptedException e){
                   e.printStackTrace();
               }finally {
                   lock.unlock();
               }
           }
       };
       Thread t3=new Thread(){
           @Override
           public void run() {
               try {
                   lock.lock();
                   while (nextPrintWho!=3){
                       condition3.await();}
                   for (int i=0;i<3;i++){
                       System.out.println("线程3打印第"+(i+1)+"次");
                   }
                   nextPrintWho=1;
                   condition1.signalAll();
               }catch (InterruptedException e){
                   e.printStackTrace();
               }finally {
                   lock.unlock();
               }
           }
       };

       Thread[] threads1=new Thread[5];
       Thread[] threads2=new Thread[5];
       Thread[] threads3=new Thread[5];
       for (int i=0;i

4.2ReentrantLockReadWriteLock

ReentrantLock具有完全互斥排他锁,也就是同一时间内只有一个线程可以在执行lock()方法后面的任务。这样虽然可以保证实例变量的线程安全性,但是效率却是非常的低下。所以在jdk中提供了一种读写锁ReentrantLockReadWriteLock类,使用它可以加快运行的效率,在某些不需要操作实例变量的方法中,完全可以使用读写锁来提高方法的执行效率。
读写锁表示有两个锁,一个是读锁也叫共享锁,另一个写锁也叫排他锁。多个读锁之间不互斥,读锁与写锁是互斥,写锁与写锁之间也是互斥的。在没有线程进行写入操作时,进行读取操作的多个线程都可以获得读取锁,而进行写入操作时只有在获取写锁之后才能进行写入操作。即可以有多个线程同时进行读取操作,但同一时间只能有一个线程进行写操作。

public class Demo8 {
    public static void main(String[] args) {
        Demo8Service service=new Demo8Service();
        Thread t1=new Demo8ThreaA(service);
        t1.setName("读线程A");
        t1.start();
        Thread t2=new Demo8ThreaA(service);
        t2.setName("读线程B");
        t2.start();
        Thread t3=new Demo8ThreaB(service);
        t3.setName("写线程B");
        t3.start();
    }

}
class Demo8Service {
    private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();

    public void read(){
        try {
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName()+"获得读锁:"+System.currentTimeMillis());
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()+"解除读锁:"+System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }

    }
    public void write(){
        try {
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName()+"获得写锁:"+System.currentTimeMillis());
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()+"解除写锁:"+System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();
        }
    }
}
class Demo8ThreaA extends Thread{
    private Demo8Service service;
    public Demo8ThreaA(Demo8Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.read();
    }
}
class Demo8ThreaB extends Thread{
    private Demo8Service service;
    public Demo8ThreaB(Demo8Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.write();
    }
}

读写或写读操作都是互斥的,只要出现写操作的过程,就是互斥的,读读是共享的。

5、知识补漏

5.1线程的状态

1、new :尚未启动的线程
2、runnable :在和Java虚拟机中正在执行的线程的状态
3、blocked :被阻塞等待对象锁的线程
4、waiting :正在等待另一个线程执行特定动作的线程
5、time_waiting: 正在等待另一个线程执行达到指定等待时间的线程
6、terminated :已经退出运行的线程

5.2Callable接口

Runnable接口是执行工作的独立任务,但是它不返回任何值。但是如果希望任务在完成的同时能够返回一个值,可以通过实现Callable接口。在jdk5中引入Callable接口是一种具有类型参数的泛型,它的类型参数表示从反方法call中返回值的类型。

public class Demo2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable callable=new Demo2Callable();
        FutureTask task=new FutureTask<>(callable);
        Thread t=new Thread(task);
        t.start();
        System.out.println("线程返回的值是"+task.get());
    }
}
class Demo2Callable implements Callable{
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"调用了Callable接口的实现类");
        int val=(int)(Math.random()*10);
        System.out.println("准备返回的值是"+val);
        return val;
    }
}

5.3线程池

线程池的作用:就是限制在系统中可以执行的线程的数量。根据系统环境的情况,自动或者手动设置线程数量,以达到运行的最佳效果。
用线程池控制线程的数量,多余的线程必须排队等候,一个任务执行完毕,再从队列中取最前面的任务开始执行。如果队列中没有等待的线程,线程池的资源就处于等待状态。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行,否则任务进入等待队列。
为什么要使用线程池?
1、减少创建销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2、可以根据系统的承受能力调整线程池中工作线程的数量,防止因为消耗过多的内存,导致服务器崩溃。
java里面线程池的顶级接口是Executor,但严格意义来讲,Executor并不是一个线程池,而是一个执行线程的工具。
比较重要的几个类:
ExecutorService:真正的线程池接口
ScheduleExecutorService:解决那些需要任务重复执行的问题
ThreadPoolExecutor:ExecutorService的默认实现
ScheduleThreadPoolExecutor :继承ThreadPoolExecutor,实现ScheduleExecutorService接口的类,周期性任务调试的类的实现。

Executor类里面提供了一些静态工厂,生成一些常用的线程池。
-newCachedThreadPool(ThreadPoolExecutor):创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需要的线程,就会回收部分空闲的线程(60秒没有执行的线程),当任务的数量增加时,又可以智能的添加新的线程来处理任务。这个线程池不会对线程池的大小进行限制,线程池的大小完全依赖于操作系统(JVM)能够创建的最大的线程数。
-newFixedThreadPool(ThreadPoolExecutor):创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程池内线程数达到最大数量。线程的数量达到最大后就会保持不变,如果某个线程因为执行而异常结束,那么线程池会补充一个新的线程。
-newSingleThreadExecutor(ThreadPoolExecutor):创建一个单线程的线程池。这个线程池只有宇哥工作线程,也就是相当于单线程串行的执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程替代它,保证所有任务的执行顺序,会按照任务的提交顺序执行。

-newScheduleThreadPool(ScheduleExecutorService):创建一个无限大小的线程池,这个线程池支持定时以及周期性执行任务的需求。
-newSingleThreadScheduleExecutor(ScheduleExecutorService):创建一个单线程用于定时以及周期性执行任务。
代码里面怎么用线程池?

public class Demo3 {
    public static void main(String[] args) {
        Runnable r=new Demo3Runnable();
//        ExecutorService service=Executors.newCachedThreadPool();
//        ExecutorService service=Executors.newFixedThreadPool(2);
//        ExecutorService service=Executors.newSingleThreadExecutor();
        //execute就是执行代码
//        service.execute(r);
        //关闭线程池
//        service.shutdown();
//        for (int i=0;i<10;i++){
//            service.execute(r);
//        }
        ScheduledExecutorService service=Executors.newScheduledThreadPool(2);
        System.out.println(Thread.currentThread().getName()+"准备执行"+System.currentTimeMillis());
        //延迟执行
        service.schedule(r,3, TimeUnit.SECONDS);
        //定时执行()scheduleAtFixedRate(需要执行的线程,第一个需要执行的线程延迟多长时间执行,两个线程间相隔的时间,时间单位)
        service.scheduleAtFixedRate(r,2,5,TimeUnit.SECONDS);
        //关闭线程池
        service.shutdown();
    }
}
class Demo3Runnable implements Runnable{
    static long waitTime=1000;
    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName()+"开始于"+System.currentTimeMillis());
            synchronized (this){
                waitTime+=100;
            }
            Thread.sleep(waitTime);
            System.out.println(Thread.currentThread().getName()+"结束于"+System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
}

你可能感兴趣的:(JAVA多线程(基础知识笔记))