Java 多线程

什么是线程?

线程是一个程序内部的一条执行流程。 程序如果只有一条执行流程,那这个程序就是单线程。

什么是多线程?

多线程是从软硬件上实现多条执行流程的技术(多线程是由CPU调度执行的)

创建线程有两种方法 

创建类,继承Thread 并重写run方法。 main方法是主线程,其他线程是子线程。

 

 不要把主线程任务放在启动子线程之前,这样导致每次都是主线程任务完成之后,才会启动子线程任务。


创建线程的第二种方法,实现一个Runnable接口的类

 



 

 先创建一个callable实现类,定义泛型

public class MyCallable implements Callable<String> {
    private int n;
    public MyCallable(int n){
        this.n = n;
    }

    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum = i+sum;
        }
        return "0到"+n+"之和为"+sum;
    }
}

public class ThreadTest01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //先创建一个Callable对象
        Callable<String> myCallable = new MyCallable(100);
        //把Call对象封装成一个FutureTask对象
        //未来任务对象的作用?
        //是一个任务对象,实现了Runnable接口
        //可以在线程执行完毕后,用外来任务对象调用get方法获取线程执行完毕执行后的结果
        FutureTask<String> f1 = new FutureTask<String>(myCallable);
        //获取线程执行完毕之后的返回结果
        new Thread(f1).start(); //启动线程

        //如果线程执行到这,假如上面线程还没有执行完毕,会
        //等着上面线程执行完毕之后,才能获取结果
        String s = f1.get();
        System.out.println(s);


    }
}

  如何通过构造器为当前线程设置名字

先到Mythead类中,因为Thread有提供名字的构造器,通过super()调用父类的名字

public class MyThread extends Thread{
    public MyThread(String name){
        super(name);
    }
    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}

 Thread t1 = new MyThread("线程1");

通过线程.set() 方法为线程起名字

 Thread t1 = new MyThread();
        //在线程启动之前,为线程起名字
        t1.setName("线程1");
        t1.start();
        System.out.println(t1.getName());
        Thread t2 = new MyThread();
        //在线程启动之前为线程起名字
        t2.setName("线程2");
        t2.start();
        System.out.println(t2.getName());
Thread t1 = new MyThread();
       t1.setName("线程1");
       t1.start();
       //join方法的作用,让当前调用这个方法的线程先执行完,再去执行其他线程
       t1.join();
       
       Thread t2 = new MyThread();
       t2.setName("线程2");
       t2.start();
       t2.join();

线程安全问题

多个线程,同时访问同一个共享资源,且存在修改该资源。

案例 小明和小红同时取共同账户的钱 

先创建一个共享类,Account

public class Account {
    private int id;
    private double money;

    public Account(int id, double money) {
        this.id = id;
        this.money = money;
    }

    public Account() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    public void drawMoney(double money) {
        String name = Thread.currentThread().getName();
            //细节,一定是this.money()  账户的钱, 如果写if(money >= 10000),
            // 这个是传入的参数money,是个固定值
        if (this.money >= 10000){
            System.out.println(name + "取钱" +money);
            this.money = this.money - money;
            System.out.println(this.money);
        }else{
            System.out.println("余额不足");
            System.out.println(this.money);
        }
    }
}

在测试类中,创建两个线程

 public static void main(String[] args) {
        Account account = new Account(001,10000);
        new Thread(new Runnable() {
            @Override
            public void run() {
                account.drawMoney(10000);
            }
        },"小明").start();

        new Thread(() -> {
           account.drawMoney(10000);
        },"小红").start();
    }

解决线程安全问题方法

线程同步,让多个线程先后依次访问共享资源。 加锁:每次只允许一个线程加锁,加锁后才能访问,访问完毕后自动解锁。

加锁的三种方式

1.同步代码块

2.同步方法

3.Lock锁

同步代码块:把访问共享资源的核心代码块上锁,以此保证线程安全。

 静态方法用类名.class 作为锁对象

  public static void test(){
        synchronized (Account.class){

        }
    }

实例方法用this作为锁对象

 public void drawMoney(double money) {
        synchronized (this) {
            String name = Thread.currentThread().getName();
            //细节,一定是this.money()  账户的钱, 如果写if(money >= 10000),
            // 这个是传入的参数money,是个固定值
            if (this.money > 1000){
                System.out.println(name + "取钱" +money);
                this.money = this.money - money;
                System.out.println(this.money);
            }else{
                System.out.println("余额不足");
                System.out.println(this.money);
            }
        }


同步方法:把访问共享资源的核心方法上锁,以保证线程的安全

同步代码块性能好一点


第三种,Lock锁

Lock锁是JDK5开始提供的一个新的锁操作,通过它,可以创建出锁对象进行加锁和解锁,更灵活更方便。

Lock是接口,不能直接实例化,可以采用他的实现类ReentrantLock来构建锁对象

 public class Account {
        private int id;
        private double money;
        //一个账户有一个锁对象,用final修饰确定锁对象是固定的不能进行修改。
        private final Lock lk = new ReentrantLock();

        public Account(int id, double money) {
            this.id = id;
            this.money = money;
        }

        public Account() {
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public double getMoney() {
            return money;
        }

        public void setMoney(double money) {
            this.money = money;
        }


        public  void drawMoney(double money) {
            try {
                lk.lock(); //加锁
                String name = Thread.currentThread().getName();
                if (this.money >= 10000){
                    System.out.println(name + "取钱" +money);
                    this.money = this.money - money;
                    System.out.println(this.money);
                }else{
                    System.out.println("余额不足");
                    System.out.println(this.money);
                }

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lk.unlock(); //解锁   这样比较安全
            }

        }


线程通信

当多个线程共同操作共享资源时,线程间互相通过某种方式告知自己的状态,避免无效资源的争夺。

线程通信的前提是保证线程安全。

锁是可以跨方法的

public class Desk {
   private ArrayList<String> list = new ArrayList<>();
   //放包子
   public synchronized void put(){
       try {
           if (list.size() == 0){
               list.add("包子");
               String name  = Thread.currentThread().getName();
               System.out.println(name + "生产了包子");
               Thread.sleep(2000);
               this.notifyAll();
               this.wait();

           }else {
               this.notifyAll();
               this.wait();
           }
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
   //拿包子
    public synchronized void get(){
        try {
            if (list.size() == 1){
                String name = Thread.currentThread().getName();
                list.clear();
                System.out.println(name+"吃包子");

                this.notifyAll();
                this.wait();
            }else {
                this.notifyAll();
                this.wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
 public static void main(String[] args) {
        Desk desk = new Desk();
        //厨师1

            new Thread(() -> {
                while (true){
                    desk.put();
                }
            },"厨师1").start();
            //厨师2
            new Thread(() -> {
                while (true){
                    desk.put();
                }
            },"厨师2").start();
            //厨师3
            new Thread(() -> {
                while (true) {
                    desk.put();
                }
            },"厨师3").start();
            //消费者1
            new Thread(() -> {
                while (true){
                    desk.get();
                }
            },"消费者1").start();
            //消费者2
            new Thread(() -> {
                while (true){
                    desk.get();
                }
            },"消费者2").start();
        }


线程池

线程池就是复用线程的技术

不使用线程池出现的问题: 用户每发起一个请求,后台就需要一个新线程来处理,下次新任务来了又需要线程处理,创建新线程的开销很大,并且请求过多时,会产生大量的线程出来,严重影响系统性能问题。

谁代表线程池? jdk5.0起提供了代表线程池的接口ExecutorService

       

 任务队列必须是实现Runabble和Cabble接口

什么时候创建临时线程?

新任务提交时发现核心线程都在忙,任务队列已经满了,并且还可以创建临时线程,此时才会创建临时线程。

什么时候开始拒绝新任务?

核心线程和临时线程都在忙,任务队列已经满了,新的任务过来时才会开始拒绝任务。

线程池实现Runnable接口

1.创建Runnable实现类

public class MyRunnable1 implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+ "输出666");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.创建线程池测试类

public class ThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService pool = new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        Runnable target = new MyRunnable1();
        pool.execute(target);//线程池会自动创建一个新线程,自动处理任务,自动执行。
        pool.execute(target);//线程池会自动创建一个新线程,自动处理任务,自动执行。
        pool.execute(target);//线程池会自动创建一个新线程,自动处理任务,自动执行。
        pool.execute(target); //复用前面核心线程
        pool.execute(target); //复用前面核心线程

        //实际开发中,一般不会关掉线程
      //  pool.shutdown();//等线程池的任务完毕后,关掉线程
        pool.shutdownNow();//立即关掉线程,当核心线程执行完之后关闭,不会给复用线程的机会


    }
}


创建临时线程,需要把Thread.sleep(Integer.MAX_VALUE),这样任务会拖住线程

 public static void main(String[] args) {
        ExecutorService pool = new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        Runnable target = new MyRunnable1();
        pool.execute(target);//线程池会自动创建一个新线程,自动处理任务,自动执行。
        pool.execute(target);//线程池会自动创建一个新线程,自动处理任务,自动执行。
        pool.execute(target);//线程池会自动创建一个新线程,自动处理任务,自动执行。
        pool.execute(target); //会进入到任务队列  任务队列设置值为5,所以会有5个任务排队
        pool.execute(target); //会进入到任务队列
        pool.execute(target); //会进入到任务队列
        pool.execute(target); //会进入到任务队列
        pool.execute(target); //会进入到任务队列
        pool.execute(target); //当任务队列满了,会创建临时线程
        pool.execute(target); //创建临时线程
        
    }


线程池实现Callable接口

public static void main(String[] args) throws ExecutionException, InterruptedException {
       ExecutorService pool =  new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,
               new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        Future<String> submit = pool.submit(new MyCallable(100));
        Future<String> submit1 = pool.submit(new MyCallable(200));
        Future<String> submit2 = pool.submit(new MyCallable(300));
        Future<String> submit3 = pool.submit(new MyCallable(400));

        System.out.println(submit.get());
        System.out.println(submit1.get());
        System.out.println(submit2.get());
        System.out.println(submit3.get());
        

    }

 


 并行:在同一时刻上,同时有多个线程被CPU调度执行

并发:进程中的线程是由cpu调度执行的,但cpu能处理线程的数量有限,为了保证全部线程都在执行,cpu会轮询为每个系统的线程服务,由于cpu切换速度极快,给我们感觉线程在同时执行,这就是并发。


线程的生命周期

 


拓展:悲观锁和乐观锁

悲观锁:一上来就加锁,每次只能一个线程进入访问完毕后再解锁。线程安全,性能较差

乐观锁:一开始不上锁,等出现线程安全问题的时候才开始控制,线程安全,性能较好。

悲观锁就是加synchronized

乐观锁有很多种,后面慢慢补充

public class MyRunnable1 implements Runnable{
    //整数修改的乐观锁,原子类实现
    private AtomicInteger count = new AtomicInteger();
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("count==" +count.incrementAndGet());
        }
    }

}

代码题:有100份礼品,小明和小红同时送出,当礼品剩下10份不再送出。使用线程模拟出来,并统计小明和小红各自送出多少礼物。

public class MyRunnable1 implements Runnable{
    private List<String> gift;
    private String name;
    private int count;
    public MyRunnable1(){

    }
    public MyRunnable1(List<String> gift,String name){
        this.name = name;
        this.gift = gift;

    }


    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        Random r = new Random();
        while (true){
            //锁对象要唯一,这里不能用this,因为要创建两个线程对象,this指代当前类对象,不唯一
            synchronized (gift){
                if (gift.size() <= 10){
                    break;
                }
                String remove = gift.remove(r.nextInt(gift.size()));
                System.out.println(thread.getName()+remove);
                count++;
            }
        }

    }

    public List<String> getGift() {
        return gift;
    }

    public void setGift(List<String> gift) {
        this.gift = gift;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}
public class ThreadPoolTest {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<>();
        String[] names = {"口红","包包","computer","phone"};
        Random r = new Random();
        for (int i = 0; i < 100; i++) {
            list.add(names[r.nextInt(names.length)]);
        }
        MyRunnable1 runnable = new MyRunnable1(list,"小红");
        Thread thread = new Thread(runnable);
        thread.start();

        MyRunnable1 runnable1 = new MyRunnable1(list,"小明");
        Thread thread1 = new Thread(runnable1);
        thread1.start();

        thread.join();
        thread1.join();

        System.out.println(runnable.getCount());
        System.out.println(runnable1.getCount());


    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值