多线程基础篇
in Java后端 with 0 comment

多线程基础篇

in Java后端 with 0 comment

多线程

多线程概念

进程:正在进行中的程序

线程:是进程中一个负责程序执行的控制单元(执行路径)一个进行中可以有多个执行线程,称之为多线程。

一个进程中至少要有一个线程

开启多个线程是为了同时运行多部份代码

每一个线程都有自己运行的内容,这个内容可以称之为线程要执行的任务

好处:解决了多部分同时运行的问题

弊端:线程太多会导致效率降低

其实应用程序的执行都是cpu在做着快速的切换完成的,这个切换是随机的。

Jvm启动时就启动了多个线程,至少有两个小城可以分析出来。

  1. 执行main函数的线程,该小城的任务代码都定义在main函数中
  2. 负责垃圾回收的线程

如何创建一个线程

1)继承Thread类创建线程

2)实现Runnable接口创建线程

3)使用Callable和Future创建线程

继承Thread类创建

步骤:

  1. 定义一个类继承Thread
  2. 覆写Thread类中的run方法。
  3. 直接创建Thread的子类对象创建线程
  4. 调用start方法开启线程并调用小城的任务run方法执行
public class Demo1 extends Thread{
    @Override
    public void run() {
        // 覆写run方法用于执行线程任务,将要执行的任务定义在run方法中
        for (int i=0; i<10; i++) {
            System.out.println("副线程中执行的代码-->" + i);
        }
    }

    public static void main(String[] args) {
        //创建Thread的线程对象
        Demo1 demo = new Demo1();
        // run方法由Jvm去调用执行,我们只需要调用父类Thread的start方法开启线程即可
        demo.start();
        System.out.println("主线程结束");
    }
}

还可以调用父类ThreadgetName()方法获取线程名称,获取当前正在执行的线程名称则是Thread.currentThread.getName()

https://www.cnblogs.com/3s540/p/7172146.html

实现Runnable接口

通过实现Runnable接口创建并启动线程一般步骤如下:

  1. 定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体
  2. 创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
  3. 第三部依然是通过调用线程对象的start()方法来启动线程
public class Demo2 implements Runnable{
    @Override
    public void run() {
        // 覆写run方法用于执行线程任务,将要执行的任务定义在run方法中
        for (int i=0; i<10; i++) {
            System.out.println("副线程中执行的代码-->" + i);
        }
    }

    public static void main(String[] args) {
        Demo2 demo2 = new Demo2();
        Thread t1 = new Thread(demo2);
        t1.start();

        System.out.println("主线程结束......");
    }
}

相比继承Thread类的好处:

  1. 将线程的任务从线程的子类中分离出来,进行了单独的封装。按照面向对象的思想将任务封装成对象。
  2. 避免了java单继承的局限性

实现Callable接口

Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大:

call()方法可以有返回值

call()方法可以声明抛出异常

Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的任务。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务

V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值

V get(long timeout,TimeUnit unit):返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException

boolean isDone():若Callable任务完成,返回True

boolean isCancelled():如果在Callable任务正常完成前被取消,返回True

介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:

  1. 创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
  2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
  3. 使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

代码实例

public class Demo3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 也可以传一个Callable接口的实现类
        FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i=0; i<5; i++) {
                    System.out.println("副线程执行计算任务" + i);
                    sum = sum + i;
                }
                return sum;
            }
        });

        Thread thread = new Thread(task);
        thread.start();
        System.out.println("主线程结束......");
        // 获取线程的返回结果
        System.out.println("副线程计算任务执行结果" + task.get());
    }
}

三种创建线程方法对比

实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值,后者线程执行体run()方法无返回值,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:

1、线程只是实现Runnable或实现Callable接口,还可以继承其他类。

2、这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

3、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。

4、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。

注:一般推荐采用实现接口的方式来创建多线程

线程的生命周期

thread-life-circle.png

线程安全问题

产生原因:

  1. 多个线程在操作共享的数据
  2. 操作共享数据的线程代码有多条

当一个线程在操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题

解决线程安全问题的思路:

就是将多条操作共享数据的代码封装起来,当线程 在执行这些代码的时候,必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算

在Java中,用同步代码块就可以解决这个问题

同步代码块的格式:

synchronized(对象){
	需要被同步的代码;
}

同步的好处:

解决了线程安全问题

弊端:

相对降低了效率,因为同步外的线程都会判断同步锁,消耗资源

同步的前提:

同步中必须有多个线程并使用同一个锁

案例分析:

package xyz.guqing.thread;

/**
 * 两个储户,每个都到银行存钱每次存100块,共存三次
 */
class Bank {
    // 银行总账户余额
    private int account;
    // 存钱方法
    public void add(int money) {
        account = account + money;
        System.out.println("account=" + account);
    }
}

class Customer implements Runnable {
    // 共享数据
    private Bank bank = new Bank();

    @Override
    public void run() {
        for(int i=0; i<3; i++) {
            bank.add(100);
        }
    }
}

class CustomerTest {
    public static void main(String[] args) {
        Customer customer = new Customer();
        // 两个线程表示两个储户
        Thread t1 = new Thread(customer);
        Thread t2 = new Thread(customer);
        t1.start();
        t2.start();
    }
}

分析上面代码是否存在线程安全问题,从产生线程安全问题的原因分析:

  1. 首先需要判断是否存在共享数据
  2. 操作共享数据的线程代码是否有多条

对于上面的代码Bank被线程任务执行

class Bank {
    // 共享数据
    private int account;
    
    public void add(int money) {
        // account共享数据被两条语句操作
        account = account + money;
        System.out.println("account=" + account);
    }
}

所以会产生线程安全问题,解决办法:加锁

  1. 同步代码块
//锁需要一个对象
private Object object = new Object();
public void add(int money) {
    synchronized(object) {
        account = account + money;
        System.out.println("account=" + account);
    }
}
  1. 同步函数(同步函数用的锁时this对象)
public synchronized void add(int money) {
    account = account + money;
    System.out.println("account=" + account);
}

同步函数和同步代码块的区别:

  1. 同步函数的锁是固定的this,也就是当前的对象
  2. 同步代码块使用的锁是任意对象

建议使用同步代码块

静态同步函数的锁:

public static synchronized void add(int money) {
    account = account + money;
    System.out.println("account=" + account);
}

还是以上的实例,这样也是可以的,这说明静态同步函数的锁绝对不是this,因为静态函数根本就没有this,那锁是什么呢?

同步函数有所属的对象this,静态之后就没有所属对象了。锁是有对象的,那么静态函数被加载进内存时有对象吗?,函数随着类的加载而被加载进内存但是该对象还没有通过new创建对象,而java的特点是字节码文件进内存先封装对象,所有的对象建立都有自己所属的字节码文件对象,通过getClass()方法获取,,所以函数被加载进内存时有对象,这个对象就是当前class文件所属的对象。而这个对象也就是同步静态函数所使用的锁

总结:静态的同步函数使用的锁是该函数所属的字节码文件对象,可以用getClass()获取也可以用当前类名.class形式表示

多线程下的单类

饿汉式:

/**
 * 饿汉式单类
 */
public class Single {
    private static final Single single = new Single();

    private Single() {

    }

    public static Single getInstance() {
        return single;
    }
}

懒汉式(延迟加载):

//懒汉式,延迟加载
public class Single {
    private static Single single = null;

    private Single(){}

    public static Single getInstance() {
        if(single == null) {
            single = new Single();
        }
        return single;
    }
}

分析懒汉式单类在多线程下是否存在线程安全问题

// 共享数据
 private static Single single = null;

//多条语句操作共享数据
// 1.线程0判断为空进入
// 3.线程1判断还是为空
if(single == null) {
    // 2.线程0执行到此没有创建对象,cpu执行权被切换走了,等待
    // 4.线程1执行到此,cpu执行权被切换走了,等待
    // 5.等待的线程0获得执行权,创建对象
    // 6.线程1获得执行权创建对象,造成了对象不唯一
    single = new Single();
}

通过上述分析存在线程安全问题,加同步

  1. 同步函数,每次判断锁进入后都需判断if,但是不管存不存在对象都需要判断锁
public static synchronized Single getInstance() {
    if(single == null) {
        single = new Single();
    }
    return single;
}

  1. 同步代码块(静态函数同步代码块,没有this),但是和同步代码函数没什么区别
public static Single getInstance() {
    // 不能用getClass()方法,因为该方法非静态
    synchronized(Single.class) {
        if(single == null) {
            single = new Single();
        }
    }

    return single;
}

改进:

public static Single getInstance() {
    // 多加一个判断解决效率问题当single!=null时不在需要判断锁
   if(single == null) {
       // 解决同步问题
        synchronized(Single.class) {
            if(single == null) {
                single = new Single();
            }
        }
    }

    return single;
}

死锁

常见情景之一:同步的嵌套,锁不一致

public class DeadLock 
{    
    public static void main(String[] args) 
    {
        byte[] lock1 = new byte[0];
        byte[] lock2 = new byte[0];

        Thread th1=new Thread(new Processer1(lock1,lock2));
        Thread th2=new Thread(new Processer2(lock1,lock2));
        th1.setName("th1");
        th2.setName("th2");

        th1.start();
        th2.start();
    }
}

class Processer1 implements Runnable
{
    private byte[] lock1;
    private byte[] lock2;
    Processer1(byte[] lock1,byte[] lock2){
        this.lock1=lock1;
        this.lock2=lock2;
    }

    public void run(){
        synchronized(lock1){
            System.out.println(Thread.currentThread().getName()+" get lock1,and is waiting for lock2.");
            try{
                Thread.sleep(5000);
            }catch(InterruptedException e){
                    e.printStackTrace();
            }

            synchronized(lock2){
                System.out.println(Thread.currentThread().getName()+" has get lock2.");
            }
        }
    }
}

class Processer2 implements Runnable
{
    private byte[] lock1;
    private byte[] lock2;
    Processer2(byte[] lock1,byte[] lock2){
        this.lock1=lock1;
        this.lock2=lock2;
    }

    public void run(){
        synchronized(lock2){
            System.out.println(Thread.currentThread().getName()+" get lock2,and is waiting for lock1.");
            
            try{
                Thread.sleep(5000);
            }catch(InterruptedException e){
                    e.printStackTrace();
            }

            synchronized(lock1){
                System.out.println(Thread.currentThread().getName()+" has get lock1.");
            }
        }
    }
}

线程间通信

多个线程在处理同一资源,但是任务却不同

package xyz.guqing.thread2;
//资源
class Resource {
    String name;
    String sex;
}

// 输入
class Input implements Runnable{
    Resource resource;

    public Input(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        int x = 0;
        while(true) {
            // 加同步
            synchronized (resource) {
                if(x == 0){
                    resource.name = "张三";
                    resource.sex = "男";
                }else {
                    resource.name = "lucy lucy lucy";
                    resource.sex = "女";
                }
            }

            x = (x+1)%2;
        }
    }
}

//输出
class Output implements Runnable{
    Resource resource;

    public Output(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        while(true) {
            // 输出也需要加同步
            synchronized (resource) {
                System.out.println(resource.name + "...." + resource.sex);
            }
        }
    }
}
class ResourceDemo{
    public static void main(String[] args) {
        // 创建资源传入Input和Output确保两个类操作的是同一个资源
        Resource resource = new Resource();

        //对资源进行输入和输入的两个类
        Input in = new Input(resource);
        Output out = new Output(resource);

        //开启线程任务
        Thread t1 = new Thread(in);
        Thread t2 = new Thread(out);
        t1.start();
        t2.start();

        /**
         * 出现线程安全问题:
         * lucy lucy lucy....男
         * 张三....女
         * lucy lucy lucy....男
         *
         * 共享数据:
         * resource
         * 有多条语句操作共享数据:
         * resource.name = "张三";
         * resource.sex = "男";
         *
         * 加同步也需要注意:
         * 锁该用什么需要保证两个线程锁是同一个
         * 1. this不行是两个不同的类
         * 2. 创建一个Object不行不唯一
         * 可以两个锁都用Resource.class或者resource
         */
    }
}

虽然改造完了加了锁,但是输出是一片一片的,我们希望赋值一个输出一个,交替出现。

在资源中加一个flag标记,默认没有值,如果有值flag=true输出,没有flag=true存值,但是如果线程赋值完还持有执行权此时flag=false,那么输出线程此时就需要等待,等着让输出线程输出后在赋值,这就是线程之前的等待唤醒机制

等待唤醒机制涉及的方法:

  1. wait():让线程处于冻结状态,被wait的线程会被存储到线程池中,失去cpu的执行权
  2. notify():唤醒线程池中的一个线程(任意),让线程具备执行资格
  3. notifyAll():唤醒线程池中的所有线程

这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法,必须要明确到底操作的是那个锁上的线程,所以wait()notify()在同步里面还需要用锁调用该方法标识出锁,例如resource.wait()代表resource这个锁在调用wait()方法,线程进入到resource锁后resource锁中的线程被wait()方法改变状态,也就是等待和唤醒必须要有所属,不能乱唤醒。

参考文档:

public final void wait()
             throws InterruptedException

导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法。换句话说,这个方法的行为就好像简单地执行呼叫wait(0)

当前的线程必须拥有该对象的监视器。 该线程释放此监视器的所有权,并等待另一个线程通知等待该对象监视器的线程通过调用notify方法或notifyAll方法notifyAll 。 然后线程等待,直到它可以重新获得监视器的所有权并恢复执行。

像在一个参数版本中,中断和虚假唤醒是可能的,并且该方法应该始终在循环中使用:

synchronized (obj) {
 while (<condition does not hold>)
     obj.wait();
 ... // Perform action appropriate to condition
} 

该方法只能由作为该对象的监视器的所有者的线程调用。

为什么操作线程的方法wait notify notifyAll定义在Object类中:

因为这些方法是监视器的方法,监视器其实就是锁。

锁可以是任意的对象,任意的对象调用的方法一定定义在Object类中

线程通信wait、notify例子:

//资源
class Resource {
    String name;
    String sex;
    boolean flag = false;
}

// 输入
class Input implements Runnable{
    Resource resource;

    public Input(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        int x = 0;
        while(true) {
            // 加同步
            synchronized (resource) {
                if(resource.flag) {
                    //如果flag为true说明要赋值但是已经有了线程等待
                    try {
                        resource.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 赋完值后设置flag和notify
                if(x == 0){
                    resource.name = "张三";
                    resource.sex = "男";
                }else {
                    resource.name = "lucy lucy lucy";
                    resource.sex = "女";
                }
                // 赋值完置为true
                resource.flag = true;
                //唤醒线程,取值
                resource.notify();
            }

            x = (x+1)%2;
        }
    }
}

//输出
class Output implements Runnable{
    Resource resource;

    public Output(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        while(true) {
            // 输出也需要加同步
            synchronized (resource) {
                if(!resource.flag) {
                    //如果flag为false说明要取值但是没有线程就需要等待
                    try {
                        resource.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 取完值,flag=true,notify唤醒线程
                System.out.println(resource.name + "...." + resource.sex);
                resource.flag = false;
                resource.notify();
            }
        }
    }
}
class ResourceDemo{
    public static void main(String[] args) {
        // 创建资源传入Input和Output确保两个类操作的是同一个资源
        Resource resource = new Resource();

        //对资源进行输入和输入的两个类
        Input in = new Input(resource);
        Output out = new Output(resource);

        //开启线程任务
        Thread t1 = new Thread(in);
        Thread t2 = new Thread(out);
        t1.start();
        t2.start();
    }
}

代码优化就是将资源中的属性设置为私有private提供get、set方法提供访问,如此才安全。

多生产者多消费者问题

class Resource {
    private int count = 1;
    private String name;
    private boolean flag = false;

    public synchronized void set(String name) {
        if(flag) {
            try {
                // 如果烤鸭没被消费,等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = name + count;
        count++;
        System.out.println(Thread.currentThread().getName()+"生产了一只烤鸭:"+this.name);

        flag = true;
        this.notify();
    }

    public synchronized void out() {
        if(!flag) {
            try {
                // 如果烤鸭没被消费,等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+"..消费了..."+this.name);

        flag = false;
        this.notify();
    }
}

class Producer implements Runnable {
    private Resource resource;

    public Producer(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        while (true) {
            resource.set("烤鸭");
        }
    }
}

class Consumer implements Runnable {
    private Resource resource;

    public Consumer(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        while (true) {
            resource.out();
        }
    }
}


class ProducerConsumer {
    public static void main(String[] args) {
        Resource resource = new Resource();

        Producer producer = new Producer(resource);
        Consumer consumer = new Consumer(resource);

        Thread t0 = new Thread(producer);
        Thread t1 = new Thread(producer);

        Thread t2 = new Thread(consumer);
        Thread t3 = new Thread(consumer);

        t0.start();
        t1.start();
        t2.start();
        t3.start();

        /**
         * 多生产者多消费者问题:
         * Thread-1生产了一只烤鸭:烤鸭13807
         * Thread-3..消费了...烤鸭13807
         * Thread-2..消费了...烤鸭13807
         * Thread-3..消费了...烤鸭13807
         * Thread-2..消费了...烤鸭13807
         * Thread-3..消费了...烤鸭13807
         * Thread-2..消费了...烤鸭13807
         * 经过分析是因为if标记判断时线程等待再次被唤醒后不在判断标记直接执行导致的
         */
    }
}

修改判断为while:

while(!flag) {
    try {
        // 如果烤鸭没被消费,等待
        this.wait();//线程再此等待被唤醒后会回到while判断
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

但是这么已修改运行立马发现线程死锁发生了。while标记判断导致运行过程中所有线程都被wait,没有线程能继续执行,所以死锁了,理想的情况是唤醒生产者线程后下次应该唤醒消费者线程,但是线程唤醒是随机的,所以解决办法可以是使用notifyAll(),把所有线程唤醒

class Resource {
    private int count = 1;
    private String name;
    private boolean flag = false;

    public synchronized void set(String name) {
        while(flag) {
            try {
                // 如果烤鸭没被消费,等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = name + count;
        count++;
        System.out.println(Thread.currentThread().getName()+"生产了一只烤鸭:"+this.name);

        flag = true;
        this.notifyAll();
    }

    public synchronized void out() {
        while(!flag) {
            try {
                // 如果烤鸭没被消费,等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+"..消费了..."+this.name);

        flag = false;
        this.notifyAll();
    }
}

class Producer implements Runnable {
    private Resource resource;

    public Producer(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        while (true) {
            resource.set("烤鸭");
        }
    }
}

class Consumer implements Runnable {
    private Resource resource;

    public Consumer(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        while (true) {
            resource.out();
        }
    }
}


class ProducerConsumer {
    public static void main(String[] args) {
        Resource resource = new Resource();

        Producer producer = new Producer(resource);
        Consumer consumer = new Consumer(resource);

        Thread t0 = new Thread(producer);
        Thread t1 = new Thread(producer);

        Thread t2 = new Thread(consumer);
        Thread t3 = new Thread(consumer);

        t0.start();
        t1.start();
        t2.start();
        t3.start();
    }
}

使用Lock解决多生产者多消费者问题

Lock实现提供了比使用synchronized方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition

锁用于通过多个线程控制对共享资源的访问的工具,通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。但是,一些锁可能允许并发访问共享资源,如ReadWriteLock的读锁。

使用synchronized方法或语句提供对与每个对象相关联的隐式监视器锁的访问,但是强制所有锁获取和释放以块结构的方式发生:当获取多个锁时,他们必须以相反的顺序被释放,并且所有的锁都必须被释放在与他们相同的词汇范围内。

虽然synchronized方法和语句的范围机制是的使用监视器锁更容易编程,并且有助于避免设计锁的许多常见变成错误,但是有时你需要以更灵活的方式处理锁。例如,用于遍历并发访问的数据结构的一些算法需要使用手动或者链锁定:你获取节点A的锁定,然后获取节点B,然后释放A并获取C,然后释放B并获得D等。所属的实施方式中lock接口通过允许获得并在不同范围释放的锁,并允许获得并以任何顺序释放多个锁是的能够使用这样的技术。

随着这种增加的灵活性,额外的责任,没有块结构化锁会删除使用synchronized方法和语句发生的锁自动释放,在大多数情况下应该使用Lock

使用方式:

Lock lock = new ReentrantLock();
 try {
     lock.lock(); // 获取锁
     // code......
 } finally {//确保异常时释放锁
     lock.unlock(); //释放锁
 }

ReentrantLock是接口Lock的实现,是一个可重入互斥Lock具有与使用synchronized方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。

jdk1.5以后将同步锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,隐式动作变成了显式动作。

Condition

public interface Condition

Condition因素出Object监视器方法( waitnotifynotifyAll )成不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的组合的效果Lock实现。Lock替换synchronized方法和语句的使用, Condition取代了对象监视器方法的使用。

条件(也称为条件队列条件变量 )为一个线程暂停执行(“等待”)提供了一种方法,直到另一个线程通知某些状态现在可能为真。 因为访问此共享状态信息发生在不同的线程中,所以它必须被保护,因此某种形式的锁与该条件相关联。 等待条件的关键属性是它原子地释放相关的锁并挂起当前线程,就像Object.wait

一个Condition实例本质上绑定到一个锁。 要获得特定Condition实例的Condition实例,请使用其newCondition()方法。

使用Condition后替代方法

class Resource {
    private int count = 1;
    private String name;
    private boolean flag = false;

    // 使用lock锁
    Lock lock = new ReentrantLock();

    // 通过已有的锁获取该锁上的监视器对象
    Condition condition = lock.newCondition();
    public void set(String name) {
        lock.lock();
        try {
            while (flag) {
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.name = name + count;
            count++;
            System.out.println(Thread.currentThread().getName() + "生产了一只烤鸭:" + this.name);

            flag = true;
            condition.signalAll();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    public void out() {
        lock.lock();
        try {
            while (!flag) {
                try {
                    // 如果烤鸭没被消费,等待
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "..消费了..." + this.name);

            flag = false;
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
}

这是基本用法和之前synchronized没什么区别,但是Lock一个锁可以有多组Condition以后,就不用在全唤醒了。

其他代码全不变,只需要更改Resource

class Resource {
    private int count = 1;
    private String name;
    private boolean flag = false;

    // 使用lock锁
    Lock lock = new ReentrantLock();

    // 通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者
    Condition producerCondition = lock.newCondition();
    Condition consumerCondition = lock.newCondition();

    public void set(String name) { // 生产者
        lock.lock();
        try {
            while (flag) {
                try {
                    // 已经有烤鸭了,生产者等待
                    producerCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.name = name + count;
            count++;
            System.out.println(Thread.currentThread().getName() + "生产了一只烤鸭:" + this.name);

            flag = true;

            // 生产完唤醒消费者
            consumerCondition.signal();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    public void out() { // 消费者
        lock.lock();
        try {
            while (!flag) {
                try {
                    // 消费者等待
                    consumerCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "..消费了..." + this.name);

            flag = false;
            // 消费完毕,唤醒生产者生产
            producerCondition.signal();
        }finally {
            lock.unlock();
        }
    }
}

总结:

Lock接口:它的出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成显式锁操作。同时更为灵活,可以一个锁上加上多组监视器。

lock():获取锁的方法

unlock():释放锁的方法,通常需要定义在finally代码块中,以确保即使发生异常也能释放锁

Condition接口: 出现代替了Object中的waitnotify,notifyAll方法。这些监视器方法单独进行了封装,变成Condition监视器对象。可以与任意锁进行组合,其中:

Wati和Sleep的区别

  1. wait可以指定时间也可以不指定。sleep必须指定时间
  2. 在同步中时,对于cpu的执行权和锁的处理不同,wait释放执行权且释放锁,sleep释放cput执行权,不释放锁。

停止线程

  1. stop方法(已过时)
  2. run方法结束,线程任务结束就会停止,

如何控制线程任务结束?

任务中都会有循环结构,只要控制住循环就可以结束任务,控制循环通常就用自定义标记来完成

但是如果线程处于了冻结状态,无法读取标记该如何结束?

可以使用public void interrupt()方法将线程从冻结状态强制恢复到运行状态,让线程具备cpu的执行资格。

但是强制动作会发生InterruptException,需要处理

public void interrupt()
中断这个线程。

除非当前线程中断自身,这是始终允许的,所以调用此线程的checkAccess方法,这可能会导致抛出SecurityException

如果该线程阻塞的调用wait()wait(long),或wait(long, int)的方法Object类,或者在join()join(long)join(long, int)sleep(long) ,或sleep(long, int),这个类的方法,那么它的中断状态将被清除,并且将收到一个InterruptedException

如果该线程在可阻止在I / O操作InterruptibleChannel则信道将被关闭,该线程的中断状态将被设置,并且螺纹将收到一个ClosedByInterruptException

如果该线程在Selector中被阻塞,则线程的中断状态将被设置,并且它将从选择操作立即返回,可能具有非零值,就像调用了选择器的wakeup方法一样。

如果以前的条件都不成立,则该线程的中断状态将被设置。

中断不存在的线程不需要任何效果。

线程类的其他方法

setPriority(int num)

public final int getPriority()

返回此线程的优先级

setDaemon(boolean b)

public final void setDaemon(boolean on)

将此线程标记为daemon线程或用户线程。当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。

线程启动前必须调用此方法。

  • 参数

on - 如果 true ,将此线程标记为守护线程

join()

public final void join()
             throws InterruptedException

等待这个线程死亡。

调用此方法的行为方式与调用完全相同

join (0)

toString()

public String toString()

返回此线程的字符串表示,包括线程的名称,优先级和线程组。

  • 重写:

toStringObject

  • 结果

这个线程的字符串表示形式。

线程优先级常量:

static int MAX_PRIORITY

线程可以拥有的最大优先级。

static int MIN_PRIORITY

线程可以拥有的最小优先级。

static int NORM_PRIORITY

分配给线程的默认优先级。