天天看点

< 笔记 > Java SE - 04 Java SE 多线程04 Java SE 多线程

04 Java SE 多线程

By Kevin Song
  • 04-01 多线程概述
  • 04-02 JVM中的多线程
  • 04-03 多线程创建
  • 04-04 多线程同步
  • 04-05 多线程中的单例模式
  • 04-06 多线程死锁
  • 04-07 多线程通信
  • 04-08 多线程停止

04-01 多线程概述

进程:正在进行中的程序

线程:进程中的一个负责程序执行的控制单元(执行路径)

  • 一个进程中可以有多个线程
  • 一个进程中至少有一个线程

多线程作用:开启多个线程是为了同时运行多部分代码

多线程的优缺点:

  • 优点:可以同时运行多个程序
  • 缺点:内存处理到程序频率变低,运行速度变慢

04-02 JVM中的多线程

JVM启动时就启动了多个线程,至少有两个线程

  • 执行主方法的线程:该线程的任务代码都定义在主方法中
  • 负责垃圾回收的线程

Object 类中的 finalize() 方法:

当垃圾回收器确定不存在该对象的更多引用时,由对象的垃圾回收器调用此方法

System类中的 System.gc() 方法:运行垃圾回收器,垃圾回收器运行时间随机

class Demo extends Object {
    public void finalize() {
        System.out.println("Recycle Complete");
    }
}
class ThreadDemo {
    public static void main(String[] args) {
        new Demo();
        new Demo();
        System.gc(); //
        new Demo();
        System.out.println("Hello World!");
    }
}
/*
输出
Hello World!
Recycle Complete
Recycle Complete
因为是两个线程,先运行主线程,JVM关闭前运行垃圾回收线程
*/
           

04-03 多线程创建

主方法单线程运行

不创建多线程

class Demo {
    private String name;
    Demo (Srting name) {
        this.name = name;
    }
    public void show() {
        for(int x = ; x < ; x++) {
            //y的for循环让每次输出都有一定的延迟,但是必须d1都输出完才输出d2,这时就需要多线程来让他们同时输出
           for(int y =-; y < ; y++) {}
           System.out.println(name+"...x="+x);
        }
    }
}
class ThreadDemo {
    public static void main(String[] args) {
        Demo d1 = new Demo("Sequence Initializing");
        Demo d2 = new Demo("序列初始化中");
        d1.show();
        d2.show();
    }
}
           

创建多线程

目的:开启一条执行路径,使得指定的代码和其他代码实现同时运行,而此执行路径运行的指定代码就是这个执行路径的任务

任务存放位置:

- JVM创建的主线程的任务都定义在了主方法中

- 自定义线程的任务定义在Thread类的run方法中

创建多线程有两种方法

  • 继承Thread类
  • 实现Runnable接口

创建线程方式一

继承Thread类

步骤:

  1. 定义一个类,继承Thread类
  2. 重写Thread类中的 run(); 方法
  3. 直接创建Thread类的子类对象(创建线程)
  4. 调用 start(); 方法开启线程,并调用线程的任务run(); 方法
class Demo extends Thread{
    private String name;
    Demo (Srting name) {
        this.name = name;
    }
    public void run() {
        for(int x = ; x < ; x++) {
           //currentThread()获取当前运行中线程的引用
           System.out.println(name+"...x="+x+"...Thread Name="+Thread.currentThread().getname());
        }
    }
}
class ThreadDemo {
    public static void main(String[] args) {
        Demo d1 = new Demo("Sequence Initializing");
        Demo d2 = new Demo("序列初始化中");
        d1.start();//开启线程,调用run方法
        d2.start();//开启线程,调用run方法
        //CPU在主线程,d1,d2之间随机高速切换
    }
}
           

Thread类中的方法&线程名称

getName()方法

获取线程的名称Thread-编号(从0开始)

主线程的名字main

currentThread()方法

获取当前运行中线程的引用,该方法为静态,可以直接被类名调用

多线程异常问题

有异常的线程结束运行,run() 方法出栈,线程之间互不影响。

线程的状态

  • new被创建
    • start() 运行(具备执行资格,具备执行权)
      • 冻结(释放执行权,释放执行资格)
        • sleep(time) 冻结 时间到自动唤醒
        • wait() 冻结 notify 唤醒
      • 阻塞(具备执行资格,不具备执行权,正在等待执行权)
      • 消亡
        • run() 方法结束
        • stop() 停止线程

CPU的执行资格:可以被CPU处理,在处理队列中排队

CPU的执行权:正在被CPU处理

创建线程方式二

实现Runnable接口

步骤:

  1. 定义一个类,实现Runnable接口
  2. 重写接口中的run方法,将线程的任务代码封装到run方法中
  3. 通过Thread类创建线程对象,将Runnable接口的子类对象作为构造方法的参数进行传递
  4. 调用线程对象的start(); 方法开启线程
class Demo extends Fu implements Runable{ //无法继承Thread但是需要多线程,通过接口形式完成
    public void run() {
        show();
    }
    public void show() {
        for(int x = ; x < ; x++) {
            System.out.println(Thread.currentThread().getName()+"..."+x);
        }
    }
}
class ThreadDemo {
    public static void main(String[] args) {
        Demo d = new Demo();
        Thread t1 = new Thread(d);
        Thread t2 = new Thread(d);
        t1.start;
        t2.start;
    }
}
           

第二种方法的好处:

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

04-04 多线程同步

卖票示例

四个线程,每个线程都卖100张票

class Ticket extends Thread{
    private int num = ;
    public void run() {
        sale();
    }
    public void sale() {
        while(true) {
            if(num > ) {
                System.out.println(Thread.currentThread().getName()+“...sale...”num--);
            }
        }
    }
}
class TicketDemo {
    public static void main(String[] args) {
        //四个对象,每个对象都有100张票
        Ticket t1 = new Ticket();
        Ticket t2 = new Ticket();
        Ticket t3 = new Ticket();
        Ticket t4 = new Ticket();

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

四个线程一起卖100张票

class Ticket implements Runnable {
    private int num = ;
    public void run() {
        sale();
    }
    public void sale() {
        while(true) {
            if(num > ) {
                System.out.println(Thread.currentThread().getName()+“...sale...”num--);
            }
        }
    }
}
class TicketDemo {
    public static void main(String[] args) {
        Ticket t = new Ticket(); //一个对象,所有一共只有100张票
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        Thread t4 = new Thread(t);

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

    }
}
           

线程安全问题的现象

会出现0,-1,-2张票的情况,因为线程1还没运行到num–的时候,线程2,线程3有可能就已经加载进来,当num–运行完之后,线程还会继续运行,这就导致0,-1,-2的出现。

安全问题产生的原因

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

当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算

多线程同步

将多条操作共享的数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。

同步代码块

格式

synchronized(对象) {
    需要被同步的代码;
}
           
class Ticket implements Runnable {
    private int num = ;
    Object obj = new Object; //此对象为了传给synchronized作为锁
    public void run() {
        sale();
    }
    public void sale() {
        while(true) {
            synchronized(obj) { //synchronized修饰的代码块只能同一时间被一个线程调用
                if(num > ) {
                    try {
                        Thread.sleep();
                    } catch (InterruptedException e) {}
                    System.out.println(Thread.currentThread().getName()+“...sale...”num--);
                }
            }
        }
    }
}
class TicketDemo {
    public static void main(String[] args) {
        Ticket t = new Ticket(); //一个对象,所有一共只有100张票
        Ticket t1 = new Ticket();
        Ticket t2 = new Ticket();
        Ticket t3 = new Ticket();
        Ticket t4 = new Ticket();

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

    }
}
           

同步的前提:

  • 同步中必须有多个线程并使用同一个锁
  • 当作锁的对象必须在 run() 方法外创建

同步的优缺点:

  • 优点:解决了线程的安全问题
  • 缺点:相对降低了效率,因为同步外的线程都会判断同步锁

同步方法

/*
两个客户每次去银行存100,存三次
*/
class Bank {
    private int sum;
    private Object obj = new Object();
    public synchronized void add(int num) {//同步方法
//        synchronized(obj) {
            sum = sum + num;
            System.out.println("sum="+sum);
//        }
    }
}
class Cus implements Runnable {
    private Bank b = new Bank();
    public void run() {
        for(int x = ; x < ; x++) {
            b.add();
        }
    }
}
class BankDemo {
    public static void main(String[] args) {
        Cus c = new Cus;
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();
    }
}
/*
输出:
sum=100
sum=200
sum=300
sum=400
sum=500
sum=600
*/
           

同步代码块和同步方法同时使用

class Ticket implements Runnable {
    private int num = ;
    Object obj = new Object; //此对象为了传给synchronized
    boolean flag = true;
    public void run() {
        if(flag)//flag为true则运行同步代码快
            while(true) {
                synchronized(this) {
                    if(num>) {
                        try {
                            Thread.sleep();
                        } catch(InterruptedException e) {}
                        System.out.println(Thread.currentThread().getName()+"...obj..."+num--);
                    }
                }
            }
        else //flag为false则运行同步方法
            while(true)
                this.show();
    }
    public synchronized void show() {
        if(num > ) {
            try {
                Thread.sleep();
            } catch ()
            System.out.println(Thread.currentThread().getName()+“...syn...”num--);
        }
    }
}
class TicketDemo {
    public static void main(String[] args) {
        Ticket t = new Ticket(); //一个对象,所有一共只有100张票
        Ticket t1 = new Ticket();
        Ticket t2 = new Ticket();

        t1.start();

        try {
            Thread.sleep();
        } catch(InterruptedException e) {}

        t.flag = false;
        t2.start();
    }
}
           

同步方法和同步代码快的区别:

  • 同步方法的锁是固定的this(当前对象)
  • 同步代码块的锁是任意的对象

静态同步方法使用的锁:

该方法所属字节码文件对象,可以用如下方式获取

  • this.getClass() 仅限非静态方法中使用
  • Ticket.class

04-05 多线程中的单例模式

使用同步虽然可以避免安全问题,但是会导致程序运行效率变低,因为每个线程都需要判断是否可以拿锁

//饿汉式(单例设计模式)
class Single {
    private static final Single s = new Single();
    private Single() {}
    public static Single getInstance() {
        return s;
    }
}
//懒汉式(延迟加载单例设计模式)
class Single {
    private static Single s = null;
    private Single() {}
    public static Single getInstance() {
        if(s == null) {//解决效率问题:多加一句判断,后续线程不会判断是否可以拿锁
            //不用this.getClass()是因为getClass()是非静态方法
            synchronized(Single.class) {//解决安全问题
                if(s == null)
                    s = new Single();
            }
        }
        return s;
    }
}
class SingleDemo {
    public static void main(String[] args) {
        System.out.println();
    }
}
           

04-06 多线程死锁

同步代码块嵌套

class Test implements Runnable{
    private boolean flag;
    Test(boolean flag) {
        this.flag = flag;
    }
    public void run() {
        if(flag) {
            synchronized(MyLock.locka) {
                System.out.println("if...locka...");
                synchronized(MyLock.lockb) {
                    System.out.println("if...lockb...");
                }
            }
        } else {
            synchronized(MyLock.lockb) {
                System.out.println("if...lockb...");
                synchronized (MyLock.locka) {
                    System.out.println("if...locka...");
                }
            }
        }
    }
}
class MyLock {
    public static final Object locka = new Object();
    public static final Object lockb = new Object();
}
class DeadLockTest {
    public static void main(String[] args) {
        Test a = new Test(true);
        Test b = new Test(false);

        Thread t1 = new Thread(a);
        Thread t2 = new Thread(b);

        t1.start;
        t2.start;
    }
}
           

04-07 多线程通信

定义:多个线程在处理同一资源,任务却不同

等待唤醒机制

涉及的方法:

  • wait(); 让线程处于冻结状态,释放执行权和执行资格,存储到线程池中
  • notify(); 唤醒线程池中一个线程
  • notifyAll(); 唤醒线程池中所有线程,防止多生产多消费的死锁问题

这些方法都必须定义在同步中,因为这些方法都是用于线程状态的方法,必须要明确到底操作的是哪个锁上的线程。

等待唤醒机制中的方法(wait(); notify(); notifyAll();)定义在Object类中,因为这些方法是监视器的方法,监视器其实就是锁

//资源
class Resource {
    private String name;
    private String sex;
    privateboolean flag = false; //用来判断是否可以输入输出
    public synchronized void set(String name) {
        if(flag) //如果flag为false,则不wait
            try {
                this.wait();
            } catch (InterruptException e) {}
        this.name = name;
        this.sex = sex;
        flag = true;
        this.notify();
    }
    public synchronized void out() {
        if(!flag)
            try {
                this.wait();
            } catch (InterruptException e) {}
        System.out.println(name+"..."+sex);
        flag = false;
        notify();
    }
}
//输入
class Input implements Runnable {
    Resource r;
    Input(Resource r) {
        this.r = r;
    }
    public void run() {
        int x = ;
        while(true) {
            if(x == ) { //控制两种数据输入
                r.set("Kevin","Male")
            } else {
                r.set("Lily","Female");
            }
            x = (x+)%;
        }
    }
}
//输出
class Output implements Runnable {
    Resource r;
    Output(Resource r) {
        this.r = r;
    }
    public void run() {
        while(true) { 
            r.out();
            }
        }
    }
}
class ResourceDemo {
    public static void mian(String[] args) {
        //创建资源
        Resource r = new Resource();
        //创建任务
        Input in  = new Input(r);
        Output out = new Output(r);
        //创建线程
        Thread t1 = new Thread(in);
        Thread t2 = new Thread(out);
        //开启线程
        t1.start();
        t2.start();
    }
}
           

多生产者多消费者问题

多生产多消费与单生产单消费的区别

flag判断语句区别

  • if只判断标记一次,会导致不该运行的线程开始运行,出现数据错误
  • while可以循环判断标记,解决了线程获取执行权后,是否要运行的问题

唤醒语句区别

  • notify()只能唤醒一个线程,如果唤醒了本方,没有意义,并且会导致死锁,所有线程都进线程池
  • notifyAll()解决了,本方线程一定会唤醒对方线程
//定义资源
class Resource {
    private String name;//资源名称
    private int count = ;//计数器
    private boolean flag = false;
    public synchronized void set(String name) {
        while(flag)//flag为false时不wait,直接运行下面内容。
            try {
                this.wait();//当flag为true时进入wait状态
            } catch (InterruptException e) {}
        this.name = name + count;
        count++;
        System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
        flag = true; //flag改成true
        notify(); //随机唤醒一个线程,如果是多生产多消费者则用notifyAll();
    }
    public synchronized void out() {
        while(!flag)
            try {
                this.wait();
            } catch (InterruptException e) {}
        System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
        flag = false;
        notify();//随机唤醒一个线程,如果是多生产多消费者则用notifyAll();
    }
}
class Producer implements Runnable {
    private Resource r;
    Producer(Resource r) {
        this.r = r;
    }
    public void run() {
        while(true) {
            r.set("烤鸭");
        }
    }
}
class Producer implements Runnable {
    private Resource r;
    Producer(Resource r) {
        this.r = r;
    }
    public void run() {
        while(true) {
            r.out();
        }
    }
}
class ProducerConsumerDemo {
    public static void main(String[] args) {
        Resource r = new Resource();
        Producer pro = new Producer(r);
        Consumer con = new Consumer(r);

        Thread t0 = new Thread(pro);
        Thread t1 = new Thread(con);
        t0.start();
        t1.start();
    }
}
           

JDK1.5新特性

Lock

Lock接口:代替了同步代码快或者同步方法,将同步的隐式锁操作变成显式锁操作,更为灵活,可以一个锁上加多组监视器。

  • lock(); 获取锁
  • unlock(); 释放锁,通常需要定义在finally代码块中
Lock lock = new ReentrantLock();
public void run() {
    lock.lock();
    try {
        code;
    } finally {
        lock.unlock();
    }
}
           

Condition

Condition接口:代替了Object中的wait notify notifyAll方法。这些监视器被封装,变成Condition监视器对象,可以任意锁进行组合

  • await(); 功能同wait()
  • signal(); 同notify()
  • signalAll(); 同notifyAll()

1.5版本的多生产者多消费者代码

//定义资源
class Resource {
    private String name;//资源名称
    private int count = ;//计数器
    private boolean flag = false;
    //创建一个锁对象
    Lock lock = new ReentrantLock();

    //通过已有的锁获取该锁上的监视器对象
    //Condition con = lock.newCondition();

    Condition Producer_con = lock.newCondition();
    Condition Consumer_con = lock.newCondition();

    public void set(String name) {
        lock.lock();
        try {
            while(flag)//flag为false时不wait,直接运行下面内容。
                try {
                    Producer_con.await();//当flag为true时进入wait状态
                } catch (InterruptException e) {}
            this.name = name + count;
            count++;
            System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
            flag = true; //flag改成true
            //notifyAll();
            //con.signalAll();
            consumer_con.signal();
        } finally {
            lock.unlock();
        }
    }
    public synchronized void out() {
        lock.lock();
        try {
            while(!flag)
                try {
                    consumer_con.await();
                } catch (InterruptException e) {}
            System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
            flag = false;
            //notifyAll();
            //con.signalAll();
            //Producer_Con.signal();
        } finally {
            lock.unlock();
        }
    }
}
class Producer implements Runnable {
    private Resource r;
    Producer(Resource r) {
        this.r = r;
    }
    public void run() {
        while(true) {
            r.set("烤鸭");
        }
    }
}
class Consumer implements Runnable {
    private Resource r;
    Producer(Resource r) {
        this.r = r;
    }
    public void run() {
        while(true) {
            r.out();
        }
    }
}
class ProducerConsumerDemo {
    public static void main(String[] args) {
        Resource r = new Resource();
        Producer pro = new Producer(r);
        Consumer con = new Consumer(r);

        Thread t0 = new Thread(pro);
        Thread t1 = new Thread(con);
        t0.start();
        t1.start();
    }
}
           

wait和sleep区别

  • 时间指定不同
    • wait可以指定时间也可以不指定
    • sleep**必须**指定时间
  • 在同步中时,对CPU的执行权和锁的处理不同
    • wait:释放执行权,释放锁
    • sleep:释放执行权,不释放锁

04-08 多线程停止

停止线程

  • stop方法,已经过时
  • 定义标记,使run方法结束

定义标记

定义一个标记flag,当flag为true时线程可以运行,当flag为负时线程停止运行。

局限性:当有wait() 的时候会导致线程无法继续判断flag而进程冻结

class StopThread implements Runnable {
    boolean flag = true; //定义flag标记
    public void run() {
        while(flag) { //flag标记为true时运行run方法
            System.out.println(Thread.currentThread().getName()+"...");
        }
    }
    public void setFlag() { //定义设置flag为false的方法
        flag = false;
    }
}
class StopThreadDemo {
    public static void main(String[] args) {
        StopThread st = new StopThread();

        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);

        t1.start();
        t2.start();

        int num = ;
        for(;;) {
            if(++num==) {
                st.setFlag();
                break;
            }
            System.out.println("main..."+num);
        }
        System.out.println("over");
    }
}
           

interrupt()方法

Interrput() 方法可以把线程从冻结状态强制恢复到运行状态中来

class StopThread implements Runnable {
    boolean flag = true; //定义flag标记
    public synchronized void run() {
        while(flag) { //flag标记为true时运行run方法
            try {
                wait();
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getNmae()+"..."+e);
                flag = false;
            }
            System.out.println(Thread.currentThread().getName()+"...");
        }
    }
    public void setFlag() { //定义设置flag为false的方法
        flag = false;
    }
}
class StopThreadDemo {
    public static void main(String[] args) {
        StopThread st = new StopThread();

        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);

        t1.start();
        t2.start();

        int num = ;
        for(;;) {
            if(++num==) {
                //st.setFlag();
                t1.interrupt();//强制恢复运行
                t2.interrupt();//强制恢复运行
                break;
            }
            System.out.println("main..."+num);
        }
        System.out.println("over");
    }
}
           

setDaemon()方法

使线程变成守护线程(后台线程),前台线程结束之后后台线程自动结束

join()方法

执行权让给t1

t1.join();
           

setPriority()方法

设置线程优先级

  • MAX_PRIORITY
  • MIN_PRIORITY
  • NORM_PRIORITY