java多线程(通俗易懂)

线程创建方式1:继承thread类,重写run()方法,调用start开启线程

继承Thread类

  • 子类继承Thread类具备多线程能力
  • 启动线程:子类对象.start()
  • 不建议使用:避免OOP单继承局限性
//创建线程方法1:继承thread类,重写run()方法,调用start开启线程
//总结,线程开启不一定立即执行,由cpu调度执行
public class Demo1 extends Thread{
    @Override
    public void run() {
        //run 方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看线程代码!");
        }
    }
    public static void main(String[] args) {
        //main线程,主线程
        //创建一个线程对象
        Demo1 testThread1 = new Demo1();
        //调用start()方法主线程和创建的线程交替进行
        testThread1.start();
        //调用run()方法会按顺序执行线程
        //testThread1.run();
        for (int i = 0; i < 2000; i++) {
            System.out.println("我在学习多线程!");
        }
    }
}

练习Thread,实现多线程同步下载图片

import org.apache.commons.io.FileUtils;
import java.io.File;
import java.net.URL;
import java.io.IOException;
//联系Thread,实现多线程同步下载图片
public class Demo2 extends Thread{
    private String url;
    private String name;
    public  Demo2(String url,String name){
        this.url=url;
        this.name=name;
    }
    //下载图片线程的执行体
    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载名为"+name);
    }
    public static void main(String[] args) {
        Demo2 t1 = new Demo2("https://i0.hdslb.com/bfs/banner/ed6acbbedd81b81228ad6794b2026696b9fd1576.jpg","1.jpg");
        Demo2 t2 = new Demo2("https://i0.hdslb.com/bfs/banner/227c9bd19a8c39ff23162996b6f855f5eb2c4519.png","2.png");
        Demo2 t3 = new Demo2("https://i1.hdslb.com/bfs/face/d310dcbc0b0c5514869b5e7917c4df25e6992d94.jpg","3.jpg");
        t1.start();
        t2.start();
        t3.start();
    }
}
//下载器
class  WebDownloader{
    //下载方法
    public void downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出问题");
        }
    }
}

线程创建方式2:实现runnable接口,重写run方法

实现Runnable接口

  • 实现接口Runnable具有多线程能力
  • 启动线程:传入目标对象+Thread对象.start()
  • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
//创建线程方式2:实现runnable接口,重写run方法
//执行线程需要丢入runnable接口实现类,调用start方法
public class Demo3 implements Runnable{

    @Override
    public void run() {
        //run 方法线程体
        for (int i = 0; i < 200; i++) {
            System.out.println("我在看线程代码!");
        }
    }

    public static void main(String[] args) {
        //main线程,主线程
        //创建一个runnable接口的实现类对象
        Demo3 testThread3 = new Demo3();
        //创建线程对象,通过线程对象来开启我们的线程,代理
        new Thread(testThread3).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("我在学习多线程!");
        }
    }
}

初识并发问题:多个线程同时操作同一个对象,买火车票的例子

package thread;
//多个线程同时操作同一个对象
//买火车票的例子
//发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱。
public class Demo4 implements Runnable{
    //票数
    private int ticketNums = 10;
    @Override
    public void run() {
        while (true){
            if (ticketNums<=0) break;
            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"票");
        }
    }
    public static void main(String[] args) {
        Demo4 ticket =new Demo4();
        new Thread(ticket,"小明").start();
        new Thread(ticket,"小红").start();
        new Thread(ticket,"小亮").start();
    }
}

发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱。

线程创建方式三:实现callable接口

package thread;
import java.util.concurrent.*;
//线程创建方式三:实现callable接口
public class Demo6 implements Callable<Boolean> {
    private String url;
    private String name;
    public  Demo6(String url,String name){
        this.url=url;
        this.name=name;
    }
    //下载图片线程的执行体
    @Override
    public Boolean call() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载名为"+name);
        return true;
    }
    public static void main(String[] args) throws  ExecutionException,InterruptedException{
        Demo6 t1 = new Demo6("https://i0.hdslb.com/bfs/banner/ed6acbbedd81b81228ad6794b2026696b9fd1576.jpg","1.jpg");
        Demo6 t2 = new Demo6("https://i0.hdslb.com/bfs/banner/227c9bd19a8c39ff23162996b6f855f5eb2c4519.png","2.png");
        Demo6 t3 = new Demo6("https://i1.hdslb.com/bfs/face/d310dcbc0b0c5514869b5e7917c4df25e6992d94.jpg","3.jpg");

        //创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);

        //提交执行
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);

        //获取结果
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();

        //打印结果
        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);

        //关闭服务
        ser.shutdown();
    }

}

callable的好处

  • 1.可以定义返回值
  • 2.可以抛出异常

静态代理

package thread;
//静态代理总结:
//真实对象和代理对象都有实现

//好处:
//代理对象可以做很多真实对象做不了的事
//真实对象专注做自己的事情
public class Demo7 {
    public static void main(String[] args) {
        You you = new You() ;
        new WeddingCompany(you).HappyMarry();
        //WeddingCompany weddingCompany = new WeddingCompany(you);
        //weddingCompany.HappyMarry();
    }
}
interface Marry{
    void HappyMarry();
}

class You implements  Marry{
    @Override
    public void HappyMarry() {
        System.out.println("老师要结婚了,开心");
    }
}
//代理角色帮助你结婚
class  WeddingCompany implements  Marry{
    //代理谁-》真实目标角色
    private Marry target;
    public WeddingCompany(Marry target){
        this.target=target;
    }
    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry();//这是真实对象
        after();
    }
    private void before(){
        System.out.println("结婚之前,布置规划");
    }
    private void after(){
        System.out.println("结婚之后,收尾款");
    }
}

个人静态代理是线程的底部实现原理的理解

  • new WeddingCompany(you).HappyMarry();
    new Thread(testThread1).start();
  • 对比上述两行代码
    婚庆公司帮助you进行结婚的准备工作,you只需要结婚即可
    Thread 帮助testThread1实现多线程的工作,testThread1只需要完成自己的任务即可。

Lambda表达式

  • 函数式接口:如果只包含一个抽象方法,那么他就是函数式接口。

推导Lambda表达式

package thread;

/*
 * 推导Lambda表达式
 * */
public class Demo8 {
    //3.静态内部类
    static class Like2 implements ILike {
        @Override
        public void lambda() {
            System.out.println("i like lambda2");
        }
    }

    public static void main(String[] args) {
        ILike like = new Like();
        like.lambda();
        like = new Like2();
        like.lambda();
        //4.局部内部类
        class Like3 implements ILike {
            @Override
            public void lambda() {
                System.out.println("i like lambda3");
            }
        }
        like = new Like3();
        like.lambda();

        //5.匿名内部类
        like = new ILike() {
            @Override
            public void lambda() {
                System.out.println("i like lambda4");
            }
        };
        like.lambda();
        //6.用Lambda简化
        like = () -> {
            System.out.println("i like lambda5");
        };
        like.lambda();
    }
}
//1.定义一个函数式接口
interface ILike {
    void lambda();
}
//2.实现类
class Like implements ILike {
    @Override
    public void lambda() {
        System.out.println("i like lambda");
    }
}

测试Lambda

package thread;

/*
 * 测试Lambda
 * */
public class Demo9 {
    public static void main(String[] args) {
        ILove love = null;
        //Lambda表示简化
        love = (int a) -> {
            System.out.println("i love you " + a);
        };
        //简化1.参数类型
        love = (a) -> {
            System.out.println("i love you " + a);
        };
        //简化2.简化括号
        love = a-> {
            System.out.println("i love you " + a);
        };
        //简化3.去掉花括号
        love = a-> System.out.println("i love you " + a);

        //总结:
        // Lambda表达式只能有一行代码的情况下才能简化成为一行,
        //如果多行,那么就用代码块包裹。
        //前提是接口为函数式接口
        //多个参数也可以去掉参数类型,要去掉都去掉,必须加上括号
        love.love(1);
    }
}

interface ILove {
    void love(int a);
}

线程状态转换图

线程停止

  • 不推荐使用JDK提供的stop()、destroy()方法。【已废弃】
  • 推荐线程自己停止下来
  • 建议使用一个标志位进行终止变量,当flag=false,则终止线程运行。
package thread;
//测试stop
//1.建议线程正常停止-》利用次数,不建议死循环
//2.建议使用标志位-》设置一个标志位
//3.不要使用stop或者destroy等过时或jdk不建议使用的方法
public class Demo10 implements Runnable{
    //1.设置一个标志位
    private  boolean flag = true;
    @Override
    public void run() {
        int i = 0;
        while (flag){
            System.out.println("run...Thread"+i++);
        }
    }
    //2.设置一个公开的方法停止进程,转换标志位
    public void stop(){
        this.flag=false;
    }
    public static void main(String[] args) {
        Demo10 testStop = new Demo10();
        new Thread(testStop).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("main"+i);
            if (i==900){
                //调用stop方法切换标志位,让线程停止
                testStop.stop();
                System.out.println("线程该停止了");
            }
        }
    }
}

线程休眠sleep

  • sleep(时间)指定当前线程阻塞的毫秒数
  • sleep存在异常InterruptedException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁
package thread;

import java.text.SimpleDateFormat;
import java.util.Date;

//模拟倒计时
public class Demo11{

    public static void main(String[] args) {
        //打印当前系统时间
        Date startTime = new Date(System.currentTimeMillis());//获取系统时间
        while (true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                startTime = new Date(System.currentTimeMillis());//更新当前时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //模拟倒计时
    public static void tenDown() throws InterruptedException{
        int num=10;
        while (true){
            Thread.sleep(1000);
            System.out.println(num--);
            if (num<=0) break;
        }
    }
}

线程礼让yield

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让cpu重新调度,礼让不一定成功!看CPU心情
package thread;
//测试礼让线程
//礼让不一定成功,看CPU心情
public class Demo12 {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield,"小明").start();
        new Thread(myYield,"小亮").start();

    }
}
class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"线程结束");
    }
}

线程强制执行join

join就是阻塞当前线程,等join的线程执行完后,进入就绪状态

package thread;
//测试join方法(插队
public class Demo13 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("线程vip来了"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException{
        Demo13 testJoin = new Demo13();
        Thread thread = new Thread(testJoin);
        thread.start();
        for (int i = 0; i < 500; i++) {
            if (i==200){
                thread.join();//插队
            }
            System.out.println("main"+i);
        }
    }
}

观测线程状态

package thread;

//观察测试线程的状态
public class Demo14 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {//每隔一秒输出一次
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("//");
            }
        });
        //观察状态
        System.out.println(thread.getState());//NEW
        //观察启动后状态
        thread.start();
        System.out.println(thread.getState());//RUN

        while (thread.getState() != Thread.State.TERMINATED) {//只有线程不停止,就一直输出状态
            Thread.sleep(100);
            System.out.println(thread.getState());
        }

        //死亡之后的线程不能再次启动
        //thread.start();//报错
    }
}

线程的优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
  • 线程的优先级用数字表示,范围从1~10.
  • Thread.MIN_PRIORITY = 1;
  • Thread.MAX_PRIORITY = 10;
  • Thread.NORM_PRIORITY = 5;
  • 使用以下方式改变或获取优先级
  • getPriority()/.setPriority(int xxx)
package thread;
//测试线程的有限度
public class demo15 {
    public static void main(String[] args) {
        MyPriority myPriority = new MyPriority();
        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);
        t1.setPriority(2);
        t2.setPriority(5);
        t3.setPriority(4);
        t4.setPriority(6);
        t5.setPriority(8);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}
class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    }
}

守护线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如,后台记录操作日志,监控内存,垃圾回收等待
package thread;

public class Demo16 {
    public static void main(String[] args) {
        God god = new God();
        You2 you2 = new You2();

        Thread thread =new Thread(god);
        thread.setDaemon(true);//默认是false表示是用户线程,正常的线程都是用户线程
        thread.start();//上帝守护线程启动

        new Thread(you2).start();

    }
}
class God implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("上帝保佑着你!");
        }
    }
}
class You2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你开心的活着!");
        }
        System.out.println("Goodbye World!");
    }
}

线程同步机制

线程同步

  • 并发:同一个对象被多个线程同时操作
    处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性﹐
在访问时加入锁机制synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。

存在以下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
  • 在多线程竞争下,加锁。释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。

三大不安全案例

package thread;
//不安全的买票
//线程不安全,有小于等于0的数出现,
public class Demo17 {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket,"眉笔的我").start();
        new Thread(buyTicket,"欧皇的你").start();
        new Thread(buyTicket,"可恶的黄牛党").start();
    }
}
class BuyTicket implements Runnable{
    private int ticketNums=10;//票数
    boolean flag = true;//外部停止方式
    @Override
    public void run() {
        while (flag){
            try {
                buy();//买票
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void buy() throws InterruptedException {
        if (ticketNums<=0){//判断是否有票
            flag=false;
            return;
        }
        Thread.sleep(100);//模拟延迟
        System.out.println(Thread.currentThread().getName()+"买到了第"+ticketNums--+"张票");
    }
}

取到的票号有小于等于0的数出现,

package thread;

//不安全的取钱,两个人去银行取钱,账户
public class Demo18 {
    public static void main(String[] args) {
        Account account =new Account(100,"婚后共同财产");
        DrawingMoney man =new DrawingMoney(account,"男人",50);
        DrawingMoney woMan =new DrawingMoney(account,"女人",100);

        man.start();
        woMan.start();

    }
}

class Account {
    int money;
    String name;

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

class DrawingMoney extends Thread {
    Account account;
    int drawingMoney;
    int nowMoney;

    public DrawingMoney(Account account, String name, int drawingMoney) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (account.money - drawingMoney < 0) {
            System.out.println(Thread.currentThread().getName() + "取钱失败,余额不足!");
            return;
        }
        account.money -= drawingMoney;
        nowMoney += drawingMoney;
        System.out.println(Thread.currentThread().getName() + "取出" + drawingMoney + "万元,手中余额为" + nowMoney);
    }
}

结果会出现

  • 只有男人取到钱
  • 只有女人取到钱
  • 男人女人都取到钱
package thread;

import java.util.ArrayList;
import java.util.List;

//线程不安全的集合
public class Demo19 {
    public static void main(String[] args) {
        List<String> list =new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

同步方法及方法块

同步方法

  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法∶synchronized方法和synchronized块.
  • 同步方法:public synchronized void method(int args){}
    等价于:synchronized(this){}
  • synchronized方法控制对“对象”的访问﹐每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行﹐就独占该锁,直到该方法返回才释放锁﹐后面被阻塞的线程才能获得这个锁,继续执行
  • 缺陷:若将一个大的方法申明为synchronized将会影响效率

同步块

  • 同步块:synchronized (Obj ){ }
  • Obj称之为同步监视器
    • obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器﹐因为同步方法的同步监视器就是this ,就是这个对象本身,或者是class[反射中讲解]
  • 同步监视器的执行过程
    1.第一个线程访问,锁定同步监视器﹐执行其中代码.
    2.第二个线程访问,发现同步监视器被锁定﹐无法访问.
    3.第一个线程访问完毕,解锁同步监视器.
//方法1和2表达的效果一样
public void method1(){
    synchronized(this){
        System.out.println("000");
    }
}
public synchronized void method2(){
    System.out.println("000");
}
package thread;

//不安全的买票案例修改
public class Demo17 {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket, "眉笔的我").start();
        new Thread(buyTicket, "欧皇的你").start();
        new Thread(buyTicket, "可恶的黄牛党").start();
    }
}

class BuyTicket implements Runnable {
    private int ticketNums = 10;//票数
    boolean flag = true;//外部停止方式

    @Override
    public void run() {
        while (flag) {
            try {
                Thread.sleep(100);//模拟延迟
                buy();//买票
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //synchronized同步方法,锁的是this
    private synchronized void buy() {
        if (ticketNums <= 0) {//判断是否有票
            flag = false;
            return;
        }
        System.out.println(Thread.currentThread().getName() + "买到了第" + ticketNums-- + "张票");
    }
}

对比之前未线程同步的代码,在添加了synchronized关键字之后,修改了线程睡眠的位置后输出结果正常

package thread;

//不安全的取钱,两个人去银行取钱,账户
public class Demo18 {
    public static void main(String[] args) {
        Account account = new Account(100, "婚后共同财产");
        DrawingMoney man = new DrawingMoney(account, "男人", 50);
        DrawingMoney woMan = new DrawingMoney(account, "女人", 100);

        man.start();
        woMan.start();

    }
}

class Account {
    int money;
    String name;

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

class DrawingMoney extends Thread {
    Account account;
    int drawingMoney;
    int nowMoney;

    public DrawingMoney(Account account, String name, int drawingMoney) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (account) {
            if (account.money - drawingMoney < 0) {
                System.out.println(Thread.currentThread().getName() + "取钱失败,余额不足!");
                return;
            }
            account.money -= drawingMoney;
            nowMoney += drawingMoney;
            System.out.println(Thread.currentThread().getName() + "取出" + drawingMoney + "万元,手中余额为" + nowMoney);
        }
    }
}
  • 如果是直接给run方法加synchronized,在这例子里,因为是new了两个DrawingMoney对象,而synchronized方法默认锁的this,两个对象分别对应两个this,即使一个线程先获得了其对应对象的锁(this),也不影响另外一个线程获得另外一个对象的锁(this),这时相当于两个线程都进入到了这个方法内同时操作共享资源account,所以只需要用同步代码块将account资源锁住,即使两个线程都进入方法体内也无法同时访问account这个资源。
  • account是两个取钱操作的共用的资源,在对account进行修改前用同步块先把他锁住,修改完之后再释放给其他线程操作。
package thread;

import java.util.ArrayList;
import java.util.List;

//线程不安全的集合
public class Demo19 {
    public static void main(String[] args) {
        List<String> list =new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

死锁

多个线程各自占有一些共享资源﹐并且互相等待其他线程占有的资源才能运行﹐而导致两个或者多个线程都在等待对方释放资源﹐都停止执行的情形﹒某一个同步块同时拥有“两个以上对象的锁”时﹐就可能会发生“死锁”的问题.

package thread;
//死锁实例
public class Demo20 {
    public static void main(String[] args) {
        Makeup girl1 =new Makeup(0,"灰姑娘");
        Makeup girl2 =new Makeup(1,"白雪公主");

        girl1.start();
        girl2.start();
    }
}
class Lipstick{

}
class Mirror{

}
class  Makeup extends Thread{
    //需要的资源只有一份,用static来保证只有一份
    static Lipstick lipstick =new Lipstick();
    static Mirror mirror = new Mirror();

    int choice;//选择
    String girlName;//使用化妆品的女人

    Makeup(int choice,String girlName){
        this.choice=choice;
        this.girlName=girlName;
    }

    @Override
    public void run() {
        //化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void makeup() throws InterruptedException {//有死锁的makeup方法
        if (choice==0){
            synchronized (lipstick){
                System.out.println(this.girlName+"获得口红的锁");
                Thread.sleep(1000);
                synchronized (mirror){
                    System.out.println(this.girlName+"获得镜子的锁");
                }
            }
        }else {
            synchronized (mirror){
                System.out.println(this.girlName+"获得镜子的锁");
                Thread.sleep(2000);
                synchronized (lipstick){
                    System.out.println(this.girlName+"获得口红的锁");
                }
            }
        }
    }
    /*private void makeup() throws InterruptedException {//无死锁的makeup方法
        if (choice == 0) {
            synchronized (lipstick) {
                System.out.println(this.girlName + "获得口红的锁");
                Thread.sleep(1000);
            }
            synchronized (mirror) {
                System.out.println(this.girlName + "获得镜子的锁");
            }
        } else {
            synchronized (mirror) {
                System.out.println(this.girlName + "获得镜子的锁");
                Thread.sleep(2000);
            }
            synchronized (lipstick) {
                System.out.println(this.girlName + "获得口红的锁");
            }
        }
    }*/
}

产生死锁的四个必要条件:

  • 互斥条件:一个资源每次只能被一个进程使用。
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

Lock锁

package thread;

import java.util.concurrent.locks.ReentrantLock;

public class Demo21 {
    public static void main(String[] args) {
        TestLock testLock = new TestLock();

        new Thread(testLock,"线程1").start();
        new Thread(testLock,"线程2").start();
        new Thread(testLock,"线程3").start();
    }
}
class TestLock implements Runnable{
    int ticketNum=10;
    //定义lock锁
    private final ReentrantLock lock =new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.lock();//加锁
            if (ticketNum>0){
                System.out.println(Thread.currentThread().getName()+"获得了第"+ticketNum--+"张票");
            }else {
                lock.unlock();//解锁
                break;
            }
            lock.unlock();//解锁
        }
    }
}

synchronized 与Lock的对比

  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:Lock >同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

wait(),notify(),notifyAll()--线程通讯

  • wait():释放锁,线程进入休眠状态等待唤醒
    例: object.wait(); 释放object对象的锁并且本线程进入休眠状态,等待其他获得object对象的锁的线程执行notify()/notifyAll()方法进行唤醒。
  • wait(long timeout):指定等待的毫秒数
    如果一个线程调用共享对象的该方法挂起后,没有在指定的timeout ms 内被其他线程调用该共享变量的notify()或者 notifyAll() 方法唤醒,那么该函数还是会因为超时而返回。如果将 timeout置为0则和wait方法效果一样,因为在 wait()方法内部就是调用了 wait(0)。
  • notify():唤醒同一个对象上一个处于等待状态的线程,随机唤醒
  • notifyAll():唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

一篇大佬的博客足以完全理解https://blog.csdn.net/weixin_46448719/article/details/124069026

由于线程之间是抢占式执行的,因此线程之间的执行的先后顺序难以预知,但是实际的开发中有时候我们希望
合理的协调多个线程之间的执行先后顺序。线程通讯 就是通过实现多线程之间的交互,让线程的每一个独立
的“执行流”通过交互相互配合,在能够有效避免线程并发和死锁的的问题下发挥多线程的优势,更高效的处理
一些时间片短,任务量大的执行任务。

在 Java 当中,实现线程之间进行通讯和配合执行的功能有三个方法:
wait() / wait(long timeout):让线程进入等待状态。
notify():唤醒当前对象上一个休眠的线程。
notifyAll():唤醒当前对象上的所有线程。

使用无参的 wait 方法,线程会进入 WAITING 状态;
使用有参的 wait 方法,线程会进入 TIMED_WAITING 状态。

以上三个方法都时对象级别的方法不是锁级别的方法,而且都必须配合 synchronized() 方法使用,
因为只有是在获取了锁的前提下执行此线程,我们才可以在执行过程中对这个锁进行有必要的
释放-wait(),有选择的拿回-notify() ~


--wait vs sleep----
wait(0) : 线程无限期等待下去
sleep(0) : 相当于 Thread.yeild(),让出 CPU 执行权,但是 sleep(0) 会继续执行。

--
两种方法都可以让线程进入休眠状态。
都可以相应一个 Interrupt() 中断请求。

--
wait 必须配合 synchronized 使用,而 sleep 不需要。
wait 属于 Object(对象)的方法;而 sleep 属于 Thread(线程)的方法。
sleep 不会释放锁;而 wait 他会释放掉锁。
sleep 必须要传递一个数值类型的参数;而 wait 可以不传参。
sleep 让线程进入到 TIME_WAITING 状态;而无参的 wait 方法让线程进入了 WAITING 状态。
一般情况下 sleep 只能等待超时时间之后再恢复执行;而 wait 可以接收 notify/notifyAll 之后继续执行。
wait() 函数
当一个线程调用一个共享变量 wait() 方法时,该调用线程会被阻塞挂起,直到发生下面几件事情之一才返回:
(1)线程调用了该共享对象 notify() 或者 notifyAll() 方法,这样能通过调用方法进行唤醒也就是实现线
程通讯了~(线程通讯是形象表达,有时候在面试时会被提到这个名词,我们就要知道面试官想要问的就是现成的
    通知和等待了) 
(2)其他线程调用了该线程 interrupt() 方法 线程中断 interrupt(),该线程抛出 
InterruptedException 异常返回。

生产者消费者问题

管程法(缓冲区)

package thread;
//测试:生产者消费者模型-》》利用缓冲区解决:管程法
//生产者 , 消费者 , 产品 , 缓冲区
public class Demo22 {
    public static void main(String[] args) {
        SynContainer container =new SynContainer();

        new Productor(container).start();
        new Consumer(container).start();
    }
}

class Productor extends Thread {//生产者
    SynContainer container;

    public Productor(SynContainer container) {
        this.container = container;
    }

    @Override
    public void run() {//生产
        for (int i = 0; i < 100; i++) {
            container.push(new Chicken(i));
        }
    }
}

class Consumer extends Thread {//消费者
    SynContainer container;

    public Consumer(SynContainer container) {
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.pop();
        }
    }
}

class Chicken {//产品
    int id;//产品编号

    public Chicken(int id) {
        this.id = id;
    }
}

class SynContainer {//缓冲区
    //需要一个容器大小
    Chicken[] chickens = new Chicken[10];
    //容器计数器
    int count = 0;
    //生产者放入产品
    public synchronized void push(Chicken chicken) {
        //如果容器满了,就需要等待消费者消费
        while (count == chickens.length) {
            //通知消费者消费
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有满,我们就需要丢入产品
        chickens[count++] = chicken;
        System.out.println("生产了第" + chicken.id + "只鸡");
        //可以通知消费者消费了
        this.notifyAll();
    }

    public synchronized void pop() {
        //判断是否能消费
        while (count == 0) {
            //等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费
        Chicken chicken = chickens[--count];
        System.out.println("消费了第"+chicken.id+"只鸡");
        //如果吃完了,通知生产者生产
        this.notifyAll();
    }
}
  • 把老师代码中打印输出的部分放到push和pop方法中打印输出的结果才会没问题,否则控制台会输出连续超过十个的生产或者消费,原因是生产的时候先打印输出了生产某个鸡,但这时还没成功添加这只鸡,下一行代码才是,应该在成功添加push后再立刻打印输出生产了一只鸡。

信号灯法

package thread;

public class Demo23 {
    public static void main(String[] args) {
        Tv tv = new Tv();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}

//演员
class Player extends Thread {
    Tv tv;

    public Player(Tv tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i % 2 == 0) {
                this.tv.play("前面的区域以后再来探索吧!");
            } else {
                this.tv.play("进不去!怎么想都进不去吧!");
            }
        }
    }
}

//观众
class Watcher extends Thread {
    Tv tv;

    public Watcher(Tv tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}

//节目
class Tv {
    String voice;//节目名
    boolean flag = false;

    //表演
    public synchronized void play(String voice) {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.voice = voice;
        this.flag = !this.flag;
        System.out.println("应急食品:" + voice);
        this.notifyAll();
    }

    //观看
    public synchronized void watch() {
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了:" + voice);
        //通知演员观看
        this.flag = !this.flag;
        this.notifyAll();
    }
}

线程池

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

  • void execute(Runnable command)∶执行任务/命令,没有返回值,一般用来执行Runnable
  • <T>Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable
  • void shutdown():∶关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
package thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//测试线程池
public class Demo24 {
    public static void main(String[] args) {
        //1.创建服务,创建线程池
        //newFixedThreadPool 参数为:线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        //关闭链接
        service.shutdown();
    }
}

class MyThread implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

  • 16
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java面经是指在面试过程中常被问到的与Java相关的问题和知识点。下面是一些常见的Java面经问题及其解答: 1. Java的特点是什么? Java是一种面向对象的编程语言,具有跨平台性、简单性、可靠性、安全性和高性能等特点。 2. 什么是Java虚拟机(JVM)? JVM是Java程序运行的环境,它负责将Java源代码编译成字节码,并在不同的操作系统上执行。 3. 什么是面向对象编程(OOP)? 面向对象编程是一种编程范式,它将数据和操作数据的方法封装在一起,通过创建对象来实现程序的功能。 4. Java中的四种访问修饰符分别是什么? Java中的四种访问修饰符分别是public、protected、default和private,用于控制类、方法和变量的访问权限。 5. 什么是Java中的异常处理机制? 异常处理机制是Java中用于处理程序运行过程中出现的异常情况的一种机制,通过try-catch-finally语句块来捕获和处理异常。 6. 什么是Java中的多线程多线程是指在一个程序中同时执行多个线程,每个线程都可以独立执行不同的任务,提高程序的并发性和效率。 7. 什么是Java中的集合框架? 集合框架是Java中用于存储和操作一组对象的类库,包括List、Set、Map等常用的数据结构和算法。 8. 什么是Java中的反射机制? 反射机制是指在运行时动态地获取和操作类的信息,可以通过反射来创建对象、调用方法和访问属性等。 9. 什么是Java中的IO流? IO流是Java中用于输入和输出数据的一种机制,包括字节流和字符流,用于读取和写入文件、网络等数据源。 10. 什么是Java中的设计模式? 设计模式是一种解决常见软件设计问题的经验总结,包括单例模式、工厂模式、观察者模式等常用的设计模式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值