【Java SE】多线程

1.1 线程的生命周期

![](file://D:\资料\学习笔记\Java\多线程\1.png?msec=1648087619803)

方法名 说明
yield()
stop()
sleep()
wait() 阻塞
suspend() 挂起
notify()/notifyAll() 唤醒
resume() 取消挂起

1.2 线程的安全问题

1.2.1 通过同步机制解决线程安全问题

方式一:同步代码块

synchronized(同步监视器) {
    //需要被同步的代码
}
class Window implements Runnable {

    private int tickets = 100;
    private Object obj = new Object();//多线程共用同一把锁,即是同步监视器

    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                if (tickets > 0) {
                    System.out.println("售票,票号为:" + tickets);
                    tickets--;
                } else {
                    break;
                }
            }
        }
    }
}

说明:操作共享数据的代码,即为需要被同步的代码。

同步监视器,俗称锁。任何类的对象,都可以充当锁。但要求多个线程必须共用同一把锁。在实现Runnable接口创建多线程的方式中,可以考虑使用this充当同步监视器。在继承Thread创建多线程的方式中,可以使用当前类(Bank.class)充当同步监视器,类本身也是一个对象。

![](file://D:\资料\学习笔记\Java\多线程\2.png?msec=1648087619803)

方式二:同步方法

1.同步方法解决实现Runnable接口的线程创建方式的线程安全问题

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

    private synchronized void show() {
        if (tickets > 0) {
            System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
            tickets--;

    }

此时同步方法隐藏使用this作为同步监视器(锁)

2.同步方法解决实现继承Thread的线程创建方式的线程安全问题

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

    private static synchronized void show() {
        if (tickets > 0) {
            System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
            tickets--;
        }
    }

此时同步监视器为当前的类

1.2.2 线程同步解决单例模式懒汉式的线程安全问题

方式一:效率稍差

public class Bank {
    private static Bank bank = null;

    private Bank() {

    }

    public static Bank getBank() {
        synchronized (Bank.class) {
            if (bank == null)
                bank = new Bank();
            return bank;
        }
    }
}

方式二:效率更高

public class Bank {
    private static Bank bank = null;

    private Bank() {

    }

    public static Bank getBank() {
        if(bank == null) {
            synchronized (Bank.class) {
                if (bank == null)
                    bank = new Bank();
            }
        }
        return bank;
    }
}

1.3 线程的死锁问题 DeadLock

不同线程分别占用对方需要同步的资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。出现死锁,不会出现异常,不会出现提示,只是所有的线程都处于堵塞状态,无法继续。

public class theadsTest {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();

        new Thread() {
            @Override
            public void run() {
                synchronized (s1) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    s1.append("a");
                    s2.append("1");
                    synchronized (s2) {
                        s1.append("b");
                        s2.append("2");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2) {
                    s1.append("c");
                    s2.append("3");
                    synchronized (s1) {
                        s1.append("d");
                        s2.append("4");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

解决方法

专门的算法、原则

尽量减少共享资源的使用

尽量避免嵌套同步

1.4 Lock锁解决线程安全问题 jdk5.0新增

private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }finally {
                lock.unlock();
            }
        }
    }

lock方法默认this为同步监视器,因此线程创建方式只能使用实现Runnable接口的方式,继承的方式需要加static。synchronized在执行完同步代码后会手动释放同步监视器,lock方式需要手动启动同步(lock()),同时结束同步也需要

优先使用顺序:Lock->同步代码块(已经进入方法体,分配了相应资源)->同步方法(在方法体之外)

1.4 线程的通信

@Override
    public void run() {
        while(true) {
            synchronized (this) {
                notify();
                if (num <= 100) {
                    System.out.println(Thread.currentThread().getName() + ":" + num);
                    num++;

                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                else {
                    break;
                }
            }
        }
    }

线程一首先获得同步监视器(锁),执行输出后wait()进入了阻塞状态,释放了手中的锁,随后线程二执行notify()唤醒线程一,线程二获得锁进入执行输出(线程一此时没有锁不能够执行),随后线程二wait()进入堵塞状态释放手中的锁,进程一获得锁后执行notify唤醒线程二......以此实现交叉输出。

线程通信方法 说明
wait() 当前线程进入堵塞状态并释放同步监视器
notify() 唤醒一个wait的线程,若多个线程处于堵塞状态则唤醒优先级高的哪一个。
notifyAll() 唤醒所有的线程。

三个方法必须使用在同步代码块或者同步方法中,且三个方法的调用者必须是同步代码块或者同步方法的同步检测器,因此synchronized (this)参数不能为类或者obj。否则会出现IllegalMonitorStateException,或者写为obj.wait()。此外三个方法定义在java,lang.object中。

面试题 sleep() 和 wait()方法的异同

相同点:都能够使线程堵塞。

不同点:①Thread中定义的sleep,Object中定义的wait。

②wait只能使用在同步代码块和同步方法中,由同步检测器调用。

③wait会释放线程的同步检测器,sleep则不会。

生产者消费者问题

package com.hikaru.exer;

class Clerk {
    private static int num = 0;

    public synchronized void product() {
        if(num < 20) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + "正在生产第" + num + "产品...");
            notify();
        }else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void consume() {
        if(num > 0) {
//            try {
//                Thread.sleep(500);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            System.out.println(Thread.currentThread().getName() + "正在消费第" + num + "产品...");
            num--;
            notify();
        }else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Producer extends Thread{
    private Clerk clerk;
    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(this.getName() + "开始生产产品...");
        while(true) {
            clerk.product();
        }
    }
}

class Customer extends Thread{
    private Clerk clerk;
    public Customer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(this.getName() + "开始消费产品...");
        while(true) {
            clerk.consume();
        }
    }
}

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

        Producer p1 = new Producer(clerk);
        Customer c1 = new Customer(clerk);

        p1.setName("生产者一");
        c1.setName("消费者一");


        c1.start();
        p1.start();
    }
}

1.5 通过实现Callable接口新增线程 jdk5.0新增

优点:①call方法相比run()方法,可以有返回值

②方法可以抛出异常

③支持泛型的返回值

④需要借助FutureTask类,比如获取返回结果

FutureTask

FutureTask是Future的唯一实现类,可以对Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。FutureTask同时实现了Runnable、Callable接口,它既可作为Runnable被线程执行,又可作为Future得到Callable的结果。

        NumThread numThread = new NumThread();//创建Callable接口实现类
        FutureTask futureTask = new FutureTask(numThread);

        Thread t1 = new Thread(futureTask);//作为Runnable被线程执行
        t1.start();

        try {
            System.out.println(futureTask.get());//作为Future得到Callable的结果
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

1.6 使用线程池创建线程

经常创建和销毁线程、使用量特别大的资源,比如并发情况下的线程,对性能的影响很大。提前创建好多个线程放入线程池中,使用时直接获取,用完放回线程池。能够提高响应速度,降低资源消耗,便于资源管理。

public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);

        service.execute(new NumThread());//适用于Runnable
//        service.submit();//适用于Callable
        service.shutdown();
    }

ExecutorServic

真正的连接池接口。常见子类ThreadPoolExecutor

execute(Runnable command) 执行命令,没有返回值,一般用来执行Runnable
submit(Callable task) 执行命令,有返回值,一般用来执行Callable
shutdown 关闭连接池

Executors

工具类、线程池的工厂类,用于创建返回不同类型的线程池

posted @ 2022-03-24 10:12  Tod4  阅读(35)  评论(0)    收藏  举报