Java的三魂七魄 —— 高级多线程

目录

1、多线程的建立

==多线程的建立方法有:四种!!! #F44336==java

1.建立Thread子类

先上代码:面试

/**
 * 建立多线程的方法一:
 *      建立继承Thread的子类
 * 
 * @author 🏹☂࿈秋鹜࿈🏹️
 * @create 2020/3/7 18:41
 */

//线程类
class NumCount extends Thread{

    //run方法里是要执行的代码
    @Override
    public void run() {
        //输出0-99
        for (int i = 0; i < 100; i++) {
           System.out.println(NumCount.currentThread().getName()+":"+i);
        }
    }
}
//主类
public class MyThread {
    public static void main(String[] args) {
        //建立Thread子类的对象
        NumCount nc1 = new NumCount();
        //给线程起个名字
        nc1.setName("计数线程1");
        //开启线程
        nc1.start();
    }
}

注意事项windows

  • run()方法为线程的执行内容
  • 建立的对象须要调用start()方法开启线程(继承)
  • NumberCount.currentThread().getName()是获取当前线程的名称 (继承)

2.实现Runnable接口

先上代码:安全

/**
 * 建立多线程的方法二:
 *      实现Runnable接口
 *
 * @author 🏹☂࿈秋鹜࿈🏹️
 * @create 2020/3/7 18:41
 */

//线程类
class NumCount implements Runnable{

    //run方法里是要执行的代码
    @Override
    public void run() {
        //输出0-99
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
//主类
public class MyThread {
    public static void main(String[] args) {
        //实例化Runnable接口
        NumCount numberCount = new NumCount();
        //实例化一个线程,构造方法的参数为实例化的接口
        Thread nc1 = new Thread(numberCount);
        //给线程起个名字
        nc1.setName("计数线程1");
        //开启线程
        nc1.start();
    }
}

注意事项多线程

  • 因为是实现Runnable接口,获取线程名称时不能用NunberCount类
  • 建立线程时须要先实例化接口,而后建立线程对象

3.实现Callable接口

先上代码:ide

/**
 * 建立多线程的方法三:
 *      实现Callable接口  --- JDK5.0新增
 *
 * @author 🏹☂࿈秋鹜࿈🏹️
 * @create 2020/3/7 18:41
 */

//线程类
class NumCount implements Callable<Integer> {

    //重写call()方法
    @Override
    public Integer call() throws Exception {
        //计算1-100的数的和
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            sum += i;
        }
        return sum;
    }
}
//主类
public class MyThread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //建立Callable接口实现类的对象
        NumCount numCount = new NumCount();
        //将Callable接口实现类的对象传递到FutureTask构造器,建立FutureTask对象
        FutureTask<Integer> futureTask = new FutureTask<>(numCount);
        //建立线程,传递FutureTask对象到Thread构造器中
        Thread nc1 = new Thread(futureTask);
        nc1.setName("计数线程1");
        nc1.start();

        //获取Callable中call方法的返回值
        Integer sum = futureTask.get();
        System.out.println("总和为:" + sum);
    }
}

注意事项this

  • Callable方法更新于jdk 1.5
  • FutureTask实现了Runnab了接口,因此建立线程时能够传入FutureTask的实例化对象
  • Callable相较于Runnable更为灵活,支持泛型,能够回传值
  • call()方法的返回值由FutureTask的get()方法获取

4.建立线程池

先上代码:线程

/**
 * 建立多线程的方法四:
 *      建立线程池
 *
 * @author 🏹☂࿈秋鹜࿈🏹️
 * @create 2020/3/7 18:41
 */

//线程类
class NumCount implements Runnable {

    //重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}
//主类
public class MyThread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //建立指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //执行指定的线程的操做,须要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumCount());//用于Runnable
//        service.submit();//用于Callable
        //关闭线程池
        service.shutdown();
    }
}

注意事项设计

  • 线程池支持Runnable和Callable对象
  • 详细信息看另外一篇线程池详解

2、线程安全问题

问题引入:

  • 火车票问题: 三个售票窗口同时卖100张票

问题分析:

  1. 三个窗口同时买票须要使用多线程
  2. 买票过程当中会有一个共享数据:票数
  3. 执行过程当中会出现线程安全问题:重复卖同一张票和卖出错票

Q: 为何会重票错票?
A: 当出现极限状况:当多个线程同时调用共享数据,而且线程还未结束,就会出现重复调取同一个值的状况(重票),票数不知足线程数时,会出现负数(错票)。code

#### 解决方案:线程同步机制
线程同步机制:
当某一个线程在使用共用的数据(执行被同步的代码)时,其余线程要进行等候,不管这个线程是否处于阻塞状态。

若是不明白的话,就想象一下旅游景点女厕所门口排队的女性朋友们。

#### 具体办法:

1. 同步代码块
这个方法涉及到synchronized修饰词。
基本格式:

synchronized(同步监视器(锁)){
 *          //要被同步的代码
 *      }

说明:

  1. 操做共享数据的代码即为要被同步的代码
  2. 共享数据:多个线程要共同操做的同一个数据
  3. 同步监视器,俗称锁,任意一个类的对象均可担任。
  4. 同步的多个线程必须共用一把锁

使用示例:

/**
 * 用同步代码块解决线程安全问题
 *
 * 问题:三个窗口卖100张票,用线程解决。
 *
 *
 * @author 🏹☂࿈秋鹜࿈🏹️
 * @create 2020/3/5 17:50
 */

class TicketTread extends Thread{

    //保证多个线程共用一份数据,须要设置成静态的
    public static int ticket = 100;
    //同一把锁
    public static Object object = new Object();

    @Override
    public void run() {
        while (true){
            //同步代码块
            synchronized (object){
                if (ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(TicketTread.currentThread().getName() + ":正在售卖第" + ticket + "张票");
                    ticket--;
                }else{
                    break;
                }
            }
        }
    }
}


public class TicketDemo {
    public static void main(String[] args) {
        TicketTread t1 = new TicketTread();
        TicketTread t2 = new TicketTread();
        TicketTread t3 = new TicketTread();

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

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

2. 同步方法
这个方法涉及到synchronized修饰词。
基本格式:

synchronized 数据类型 方法名(){
    //须要被同步的代码
}

注意:

  • 这个时候仍是有同步监视器(锁),默认为this

使用示例:

class Windows1 implements Runnable{

    //这里无需设定静态变量,由于多个线程调用同一个接口
    public int ticket = 100;
    Object object = new Object();

    @Override
    public void run() {
        while (true){
               show();
        }

    }
    //同步方法
    public synchronized void show(){
        if (ticket>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(TicketTread.currentThread().getName() + ":正在售卖第" + ticket + "张票");
            ticket--;
        }
    }
}


public class TicketDemo2 {
    public static void main(String[] args) {
        Windows1 windows = new Windows1();
        Thread t1 = new Thread(windows);
        Thread t2 = new Thread(windows);
        Thread t3 = new Thread(windows);

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

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

3. ReentrantLock
jdk1.5新特性
用法和同步代码块相似。用调用方法的办法替代代码块。
使用示例:

class Windows implements Runnable{

    public static int ticket = 100;
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {

                //调用锁定的方法
                lock.lock();

                //lock以后的代码至关于同步代码块的效果

                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":正在售卖第" + ticket + "张票");
                    ticket--;
                } else {
                    break;
                }
            } finally {
                //调用解锁的方法
                lock.unlock();
            }

        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        Windows windows = new Windows();
        Thread t1 = new Thread(windows);
        Thread t2 = new Thread(windows);
        Thread t3 = new Thread(windows);

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

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

面试题:synchronized 和 Lock 的区别?

  • 相同点:都是实现线程同步机制
  • 不一样点:
    • synchornized机制在执行完相应的同步代码之后,自动释放同步监视器;
    • lock须要手动的启动同步(lock()),同时结束同步也须要手动实现(unlock())

3、线程通讯问题

问题引入:

两个线程打印1-100,交替打印
问题代码:

/**
 * 两个线程打印1-100,交替打印
 *
 * @author 🏹☂࿈秋鹜࿈🏹️
 * @create 2020/3/6 22:41
 */

class Number implements Runnable{

    private int number = 1;

    @Override
    public void run() {
        while (true){
            synchronized (this) {
                if (number<=100){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":数字:" + number);
                    number++;
                }else {
                    break;
                }
            }
        }
    }
}

public class Communicate {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);

        t1.setName("线程1");
        t2.setName("线程2");

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

当你运行此段代码时,会出现仅单一线程运行的状况

问题分析:

t1线程一致占用锁,没法进行交替打印

解决方案:线程通讯

须要利用三剑客:wait(),notify(),notifyAll()

  • wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器(锁)
  • notify():一旦执行此方法,就会唤醒被wait的一个线程,若是有多个线程被wait,就唤醒优先级高的那个。
  • notifyAll():一旦执行此方法,就会唤醒全部被wait的线程。

说明:

  1. 三个方法必须使用在同步代码块或同步方法中
  2. 三个方法的调用者,必须是同步代码块或同步方法中的同步监视器(锁为调用者,只有锁才能调用三个方法)不然,会出现IllegalMonitorStateException异常
  3. 三个方法定义在java.lang.Object类中

具体办法:

/**
 * 两个线程打印1-100,交替打印
 *
 * @author 🏹☂࿈秋鹜࿈🏹️
 * @create 2020/3/6 22:41
 */

class Number implements Runnable{

    private int number = 1;

    @Override
    public void run() {
        while (true){
            synchronized (this) {
                //使用notify()方法唤醒线程
//              this.notify();
                notify();
                if (number<=100){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":数字:" + number);
                    number++;

                    try {
                        //使用wait()方法使得线程处于阻塞状态
//                      this.wait();
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }else {
                    break;
                }
            }
        }
    }
}

public class Communicate {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);

        t1.setName("线程1");
        t2.setName("线程2");

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

面试题:sleep() 和 wait() 的异同?

  • 相同点:一旦执行方法,均可以使得当前的线程进入阻塞状态
  • 不一样点:
    • 两个方法声明的位置不一样:Thread类中声明sleep(),Object类中声明wait()
    • 调用的要求不一样:sleep()能够在任何须要的场景下调用。wait()必须在同步代码块和同步方法中使用
    • 关因而否释放同步监视器:若是两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁

4、更多实例

1.用线程同步的方法解决单例模式的线程安全问题

问题解决:

/**
 * 用线程同步的方法解决单例模式的线程安全问题
 *
 * @author 🏹☂࿈秋鹜࿈🏹️
 * @create 2020/3/6 16:32
 */
public class BankDemo {
}

class Bank{
    //单例模式(private构造方法,确保该类使用对象惟一)
    private Bank(){};

    public static Bank instence = null;

    //此处可能引起线程安全问题
    public static Bank getInstence(){

        //方式一,效率稍差(线程所有同步)
//        synchronized (Bank.class) {
//            if (instence==null){
//                instence = new Bank();
//            }
//        return instence;
//        }
        //方式二:效率较高(一小部分线程同步)
        if (instence==null){
            synchronized (Bank.class) {
                if (instence==null){
                    instence = new Bank();
                }
            }
        }
        return instence;
    }
}

2.银行存钱问题(线程安全问题)

问题描述:
银行有一个帐户,有两个储户分别向同一个帐户存3000元,每次存1000,存3次,每次存完打印帐余额。
问题解决:

/**
 * @author 🏹☂࿈秋鹜࿈🏹️
 * @create 2020/3/6 22:19
 */

//帐户(共享数据)
class Account{

    private double balance;
    public Account(double balance){
        this.balance =balance;
    }

    //存钱
    public synchronized void deposit(double amt){

        if (amt > 0){
            balance += amt;
            System.out.println(Thread.currentThread().getName() + ":存钱成功!当前余额为:" + balance);
        }
    }
}

//储户(线程)
class Customer implements Runnable{

    private Account acct;
    //初始化数据
    public Customer(Account acct){
        this.acct = acct;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            acct.deposit(1000);
        }
    }
}

public class AccountTest {
    public static void main(String[] args) {
        Account acct = new Account(0);
        Customer customer = new Customer(acct);
        Thread c1 = new Thread(customer);
        Thread c2 = new Thread(customer);

        c1.setName("甲");
        c2.setName("乙");

        c1.start();
        c2.start();
    }
}

3.生产者消费者问题(线程通讯问题)

问题描述:
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品。店员一次只能持有固定数量的产品(好比:20),若是生产者试图生产更多的产品,店员会叫生产者停一下,若是店中有空位放产品了再通知生产者继续生产;若是店中没有产品了,店员会告诉消费者等一下,若是店中有产品了再通知消费者来取走产品。
问题分析:

1.是不是多线程问题?是,生产者线程,消费者线程
2.是否有共享数据?是,店员(或产品)
3.如何解决现成的安全问题?同步机制,有三种方法
4.是否设计线程的通讯?是

问题解决:

/**
 * @author 🏹☂࿈秋鹜࿈🏹️
 * @create 2020/3/7 11:04
 */
class Clerk{

    private int productCount = 0;

    //生产产品
    public synchronized void produceProduct() {
        if (productCount<20){
            productCount++;
            System.out.println(Thread.currentThread().getName() + ":正在生产第" + productCount + "个产品");
            notify();
        }else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //消费产品
    public synchronized void consumeProduct(){
        if (productCount>0){
            System.out.println(Thread.currentThread().getName() + ":正在消费第" + productCount + "个产品");
            productCount--;
            notify();
        }else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Producer implements Runnable{//生产者

   private Clerk clerk;

    public Producer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":开始生产产品....");

        while (true){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.produceProduct();
        }
    }
}

class Consumer implements Runnable{//消费者

    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":开始消费产品....");

        while (true){
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumeProduct();
        }
    }
}

public class Product {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer producer = new Producer(clerk);
        Thread p1 = new Thread(producer);
        p1.setName("生产者1");

        Consumer consumer = new Consumer(clerk);
        Thread c1 = new Thread(consumer);
        c1.setName("消费者1");
        Thread c2 = new Thread(consumer);
        c2.setName("消费者2");

        p1.start();
        c1.start();
        c2.start();
    }
}
相关文章
相关标签/搜索