Java多线程

多线程学习:

一、线程和进程

线程是程序的一次执行过程,或是正在运行的一个程序。
进程可进一步细化为线程,是一个程序内部的一条执行路径。

二、并行和并发

并行:多个CPU同时执行多个任务。比如多个人做不同的事情
并发:一个CPU(采用时间片)同时执行多个任务。比如秒杀、多个人做同一件事

三、何时需要多线程

程序需要同时执行两个或多个任务;
程序需要实现一些需要等待的任务,比如用户输入、文件读取操作、网络操作、搜索等
需要一些后台运行的程序时

四、线程的创建和使用

     ①继承Thread重写run()方法
        将需要执行的语句写在run中,使用start()方法进行启动线程
        1、创建Thread的子类
        2、重写run()方法
        3、创建子类对象
        4、调用start()方法启动线程
// 1.创建一个Thread的子类
class MyThread extends Thread {
    // 2.重写Thread中run()
    @Override
    public void run(){
        for (int i = 1; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println("偶数:"+i+"--->"+Thread.currentThread().getName());
            }
        }
    }
}

public class ThreadTest {
  public static void main(String[] args){
      // 3.创建Thread类的子类的对象
      MyThread t1 = new MyThread();
      MyThread t2 = new MyThread();
      // 4、通过此对象调用start() :①启动当前线程,②调用当前线程的run()
      t1.start();
      // 问题一:
        // 不能使用直接调用run()的方式启动线程,
//      t1.run();
      // 问题二:
        // 不能再重新启动一个线程,

      // 再重新启动一个线程
      t2.start();
      // 以下属于在main线程中执行的
      for (int i = 1; i < 100; i++) {
          if(i % 2 != 0){
              System.out.println("不是偶数:"+i+"--->"+Thread.currentThread().getName());
          }
      }
  }

}
     ②实现Runnable接口
        1、创建实现类
        2、实现接口抽象方法run()
        3、创建实现类的对象
        4、将实现类的对象作为参数传递到Thread的构造器中
        5、调用Thread中的start()方法启动线程
// 1.创建一个实现Runnable接口的实现类
class MyRunnable implements Runnable{

    // 2.实现run()方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i%2==0){
                System.out.println(i);
            }
        }
    }
}


public class RunnableTest1 {
    public static void main(String[] args) {
        // 3.创建实现类的对象
        MyRunnable myRunnable = new MyRunnable();
        // 4.Runnable实现类对象作为参数传递到Thread的构造器中
        Thread t1 = new Thread(myRunnable);
        // 5.通过start()方法进行启动线程
        t1.start();
    }
}

     ③实现callable接口
        1、创建一个实现callable接口的实现类
        2、实现call()方法,将此线程需要执行的操作放到call()中
        3、创建callable实现类的对象
        4、将callable实现类对象作为参数传递到FutureTask构造器中,创建FutureTask对象
        5、将FutureTask对象作为参数传递到Thread类的构造器中,创建Thread对象,
        6、调用start()启动线程
        7、获取callable的返回值(看需求,可选)
// 1.创建一个实现Callable接口的实现类
class NumThread implements Callable{
    // 2.实现call()方法
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            if(i%2==0){
                System.out.println(Thread.currentThread().getName()+"-->偶数:"+i);
                sum += i;
            }
        }
        return sum;
    }
}


public class CallableTest {
    public static void main(String[] args) {
        // 3.创建Callable实现类的对象
        NumThread numThread = new NumThread();
        // 4.将Callable实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask对象
        FutureTask futureTask = new FutureTask(numThread);
        // 5.将FutureTask对象作为参数传递到Thread构造器中,创建Thread对象,
        // 6.调用start()方法启动线程
        new Thread(futureTask).start();

        try {
            // 7. 获取callable的返回值(看需求,可选)
            Object sum = futureTask.get();
            System.out.println("偶数和为:"+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}
     ④使用线程池
        好处:
            提高响应速度(减少了创建新线程的时间)
            降低资源消耗(重复利用线程池中线程,不需要每次都创建)
            便于线程管理
        步骤:
            1、提供指定线程数量的线程池
            2、执行指定的线程的操作。需要提供实现Runnable接口或Callable接口的实现类对象。
            3、关闭线程池
public class ThreadPollTest {
    public static void main(String[] args) {
        // 1.提供指定线程池数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        // 2.执行指定的线程操作。需要提供实现Runnable接口或者Callable接口的实现类对象
        service.execute(new NumberTest()); // 适用于Runnable
        service.execute(new NumberTest()); // 适用于Runnable
//        service.submit(); // 适用于Callable
        // 3.关闭连接池
        service.shutdown();
    }
}


class NumberTest implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2==0){
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
}

4.1 继承Thread类重写run()

常用方法:
    start() ---> 启动当前线程,调用当前线程的run方法
    run() ---> 通常需要重写Thread中的run 方法,将创建的线程需要执行的语句写在此方法中
    currentThread() ---> 静态方法,返回执行当前代码的线程
    getName() ---> 获取当前线程的名称
    setName() ---> 设置当前线程的名称
    yield() ---> 释放当前CPU的执行权,线程让步
    join() ---> 在线程a中调用线程b的join()方法,此时线程a就会进入阻塞状态,直到线程
            b执行完成后在进行执行线程a。
    stop() ---> 当执行此方法时强制结束当前线程(已经过时)
    sleep() ---> 让当前线程睡眠指定的时间,
    isAlive() ---> 判断当前线程是否存活

4.2 继承Thread和实现Runnable

在开发中优先选择实现Runnable接口的方式
原因:
    ①实现的方式没有类的单继承的局限性
    ②实现的方式更适合处理多个线程有共享数据的情况

两种方法都需要重写run()方法。

五、线程的生命周期

①新建
②就绪
③运行
④阻塞
⑤死亡

六、线程的同步

在Java中线程的同步主要是解决线程的安全问题。

实现同步的方式有:
    方式一:同步代码块
        synchronized(同步监视器){
            // 需要被同步的代码
        }
        说明:
            1.操作共享数据的代码,即为需要被同步的代码
                共享数据:多个线程操作的变量
            2.同步监视器:俗称,锁。任何一个类的对象都可以作为锁。
                要求:多个线程必须使用同一把锁。
            3.在实现Runnable接口创建的多线程的方式中可以使用this充当同步监视器
            4.在继承Thread类创建的多线程的方式中可以使用当前类本身(类名.class)充当同步监视器
class Window extends Thread {
    private static int ticket = 100;
    @Override
    public void run(){
        while (true){
            // 在继承Thread方式中使用同步代码块解决线程安全问题
            synchronized(Window.class){
                if (ticket > 0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName()+"卖票,票号为:"+ ticket);
                    ticket--;
                }else{
                    break;
                }
            }
        }
    }
}

class Window1 implements Runnable{
    private static int ticket = 100;
    @Override
    public void run() {
        while (true){
            // 在实现Runnable接口方式中使用同步代码块解决线程安全问题
            synchronized(this){
                if (ticket>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"卖票,票号:"+ticket);
                    ticket--;
                }else {
                    break;
                }
            }

        }
    }
}
    方式二:同步方法
        如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
        1.同步方法仍然涉及到同步监视器,不需要我们显示声明
        2.非静态的同步方法,同步监视器是:this
            静态的同步方法,同步监视器:当前类本身

继承Thread接口方式创建多线程中使用同步方法

private static synchronized void show(){
   if (ticket > 0){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ ticket);
        ticket--;
    }
}

实现Runnable接口方式创建多线程中使用同步方法

private synchronized void show(){
   if (ticket>0){
       try {
           Thread.sleep(10);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println(Thread.currentThread().getName()+"卖票,票号:"+ticket);
       ticket--;
   }
}
    方式三:lock锁
        1、实例化一个ReentrantLock
        2、调用锁定方法lock()
        3、调用解锁方法unlock()
class Window5 implements Runnable{
    private static int ticket = 100;
    // 1.实例化一个ReentrantLock
    private ReentrantLock lock = new ReentrantLock(true);
    @Override
    public void run() {
        while (true){
            try {
                // 2.调用锁定方法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 {
                // 3.调用解锁方法unlock()
                lock.unlock();
            }
        }
    }
}
缺点:
    效率低

6.21 线程的死锁问题

1、什么是死锁
    不同的线程分别占用对方需要的同步资源不放弃,
    都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
2、说明:
    1. 出现死锁后,不会出现异常,不会出现提示,只是所的线程都处于阻塞状态,无法继续
    2. 我们使用同步时,要避免出现死锁。
3、避免方法
	①专门的算法、原则
    ②尽量减少同步资源的定义
    ③尽量避免嵌套同步

七、线程的通信

1.wait() --> 执行此方法当前线程进入阻塞状态,并且释放同步监视器
2.notify() --> 执行该方法会唤醒一个使用wait()阻塞的线程
3.notifyAll() --> 执行该方法唤醒所有使用wait()阻塞的线程。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值