java—13 多线程

一、创建线程

1、继承Thread类

  • 子类覆写父类中的run方法
  • 建立子类对象的同时线程也被创建
  • 调用start () 开启线程
public class Test {
    public static void main(String[] args) {
    2、建立子类对象
    Pop p = new Pop();
    
    3、开启线程
    p.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("main" + i);
        }
    }
}
class Pop extends Thread {  
    1、重写父类中的run方法
    public void run(){
        for (int i = 0; i <10 ; i++) {
            System.out.println("chi"+ i);
        }
    }
}

弊端:继承是单继承,不能实现多继承,因此使用 Runnable 接口来实现多继承

2、Runnable接口

  • 子类覆盖接口中的run方法
  • 通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数
  • Thread类对象调用start方法开启线程
  • 可使用匿名内部类来写
public class Test {
    public static void main(String[] args) {
        //1.创建一个Runnable接口的实现类对象
        Pop p = new Pop();
        //2.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t = new Thread(p);
        //3.调用Thread类中的start方法,开启新的线程执行run方法
        t.start();
        for (int i = 0; i < 50; i++) {
            System.out.println("main" + i);
        }
    }
}
class Pop implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 40; i++) {
            System.out.println("chi" + i);
        }
    }
}

3、匿名内部类实现

public class Test {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i <10 ; i++) {
                    System.out.println("nihao");
                }
            }
        }.start();
    }
}

lambda表达式

public class Test {
    public static void main(String[] args) {
        new Thread(()->{
                for (int i = 0; i <10 ; i++) {
                    System.out.println("nihao");
                }
        }).start();
    }
}

4、 实现Callable接口,重写call方法

比较
通过继承Thread类和实现Runnable接口来创建线程的缺点:
1)没有返回值
2)不支持泛型
3)异常必须处理

因此,使用Callable接口这种方法功能更加强大 ↓
public class Test2 {
    public static void main(String[] args) {
//        计算任务
        Compute c = new Compute();
        /*
        FutureTask同时实现了Runnable,Future接口
        它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
        可以看成FutureTask实现了Runnable接口
        */
        FutureTask<Integer> ft = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                TimeUnit.SECONDS.sleep(10);//10s
                return c.add(1, 1);
            }
        });
        new Thread(ft, "A").start();
        try {
            System.out.println("正在计算结果:");
            System.out.println(ft.get());
            System.out.println("计算完成了吗?"+ft.isDone());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
class Compute {
    public int add(int a, int b) {
        return a + b;
    }
}

当我们卖票的时候,同时有三个售票窗口,相当于三个线程,这时候如果同时卖出第100号票,那就会造成线程不安全问题,如何解决呢?就给CPU抢到的那条线程加上锁,锁住对象,后面的就进不来,直到执行完毕后,将1---->0,锁打开,其他线程继续抢夺执行权,重复操作,保证线程安全。

二、解决线程安全问题

1. 同步代码块

格式:
synchronized (锁对象) { 共享资源 }

public class Test2 {
    public static void main(String[] args) {
        Run p = new Run();
        new Thread(()->{
              p.run();
        },"A").start();
        new Thread(()->{
            p.run();
        },"B").start();
    }
}
-------------------------------------------------------------------------------------
public class Run implements Runnable {
    private int tk = 100;
    @Override
    public void run() {
        while(true){
          paytk();
        }
    }
    public /*synchronized*/ void paytk() {
        synchronized (this) {
            if (tk > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-->正在卖票第" + tk + "张");
                tk--;
            }
        }
    }
}

2. 同步方法

格式:
synchronized 返回值类型 方法名 (参数列表){ 共享资源 }

 public synchronized void paytk(){
        if(tk>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"-->正在卖票第"+tk+"张");
            tk--;
        }
    }

3. 静态方法的同步

注意:
静态的锁对象不能是this了,
this是创建对象后产生,
静态方法优先对象,
静态方法的锁对象是本类的class属性

public class Run implements Runnable {
    private static int tk = 100;

    @Override
    public void run() {
        while (true) {
            paytk();
        }
    }
    //静态方法的同步,记得成员变量也要是静态的才行
    public static synchronized void paytk() {
        if (tk > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->正在卖票第" + tk + "张");
            tk--;
        }
    }

}
-------------------------------------------------------------------------

public static void paytk() {
        synchronized (Run.class) {
            if (tk > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-->正在卖票第" + tk + "张");
                tk--;
            }
        }
    }

4. Lock 锁

ReentrantLock 与synchronized 区别

ReentrantLock:
	是一个类,可设置。
	构造方法,默认的构造是非公平重入锁(false),也可以设置为公平锁。
	可以手动释放锁。finally中释放锁。
	可以设置超时时间。
	比如说:A线程持有锁,B线程可以等待一定的时间之后,
	如果得不到锁,就放弃,不会死等。
	看代码执行完了,就会释放锁。
	
synchronized:
	是一个关键字,不可以改变。
	一旦加上锁,不可以中断。
	比如说:A线程持有synchronized锁,那么B线程尝试获得锁,
	拿不到的时候就会进入EntryList中等待。
	这里是看锁标志位改了没有,没改就认为是锁住的。
public class Run implements Runnable {
    private int tk = 100;

    1.在成员属性位置创建一个ReentrantLock对象
   
    Lock lc = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            
            2.在可能出现安全的代码前面调用lock()
            
            lc.lock();
           
            if (tk > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-->正在卖票第" + tk + "张");
                tk--;
            }
           
            3.开锁
            lc.unlock();
        }
    }
}

对于开锁,要实现是否有异常出现,都释放掉锁,提高效率,使用 finally子句

  public void run() {
        while (true) {
           
            lc.lock();
            if (tk > 0) {
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + "-->正在卖票第" + tk + "张");
                    tk--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 3.开锁
                    lc.unlock();
                }
            }
        }
    }

三、等待唤醒 —— wait、notify

注意:

1、wait和notify方法必须是同一个锁对象调用
2、wait和notify方法都是属于Object类中的方法
3、wait和notify方法必须在同步代码块中使用

生产者与消费者问题

public class Test2 {
    public static void main(String[] args) {
        Object obj = new Object();
//       消费者排队领票
        new Thread() {
            @Override
            public void run() {
                while (true) {
                //使用同步保证两个线程只执行一个
                    synchronized (obj) {
                        System.out.println("现在排队领票看'你好,李焕英!'");
//                   调用wait()
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
//                   唤醒之后就可以拿票观看了
                        System.out.println("好的,我准备观看了!");
                        System.out.println("----------------------------");
                    }

                }
            }
        }.start();
//       生产者发票
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                     //使用同步保证两个线程只执行一个
                    synchronized (obj) {
                        System.out.println("拿好票,可以看电影了");
//                  唤醒消费者可以领票了
                        obj.notify();
                    }
                }
            }
        }.start();
    }
}

在这里插入图片描述

四、Daemon 守护线程

Java线程分为用户线程和守护线程

守护线程:处于后台运行,任务是为其他线程提供服务,是程序运行的时候在后台提供一种通用服务的线程。JVM的GC回收线程就是守护线程。

特点:当用户线程执行结束,守护线程就会结束。
设置后台线程:Thread对象setDaemon(true);

注意:
1、setDaemon(true)必须在start()调用前,否则出现IllegalThreadStateException异常;
2、用户线程创建的线程默认是用户线程,守护线程创建的线程也是守护线程;
3、判断是否是守护线程:使用Thread对象的isDaemon()方法。

public class Test2 {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            //判断守护线程创建的线程是否为守护线程?
            Thread t2 = new Thread(() -> {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2" + (Thread.currentThread().isDaemon() ? "是守护线程" : "不是守护线程"));
            });
            t2.start();

            int i=1;
            while(true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1" + (Thread.currentThread().isDaemon() ? "是守护线程," : "不是守护线程,")+"这是第"+i+"次");
//                当用户线程执行结束,守护线程就会结束。
                i++;
                if(i >= 5 ){
                    break;
                }
            } });
            
        t1.setDaemon(true);
        t1.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程结束了");
    }
}
/*t2是守护线程
t1是守护线程,这是第1次
主线程结束了*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值