Java中的多线程

Java中的多线程

多线程的创建

方式一:继承于Thread类

  1. 创建一个继承于Thread类的子类
  2. 重写Thread类的run()
  3. 创建Thread类的子类的对象
  4. 通过此对象调用start()
public class ThreadTest {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();
        //如果执行t1.run()相当于直接调用run方法,不再是多线程
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i%2==0){
                System.out.println(i);
            }
        }
    }
}

方式二:实现Runnable接口

  1. 创建一个实现了Runnable接口的类
  2. 实现类去实现Runnable中的抽象方法:run()
  3. 创建实现类的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  5. 通过Thread类对象调用start()
class MThread implements Runnable{
    @Override
    public void run() {
        //TODO
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        MThread mThread = new MThread();
        Thread t1 = new Thread(mThread);
        t1.start();
    }
}

两种方式的比较

开发中优先选择实现Runnable接口的方式

  • 避免了单继承的局限性
  • 多个线程可以共享同一个接口实现类的对象,适合多个线程来处理同一份资源

方式三 实现Callable接口

  1. 创建一个实现Callable的实现类
  2. 实现call方法,将此线程需要执行的操作声明在call()中
  3. 创建Callable接口实现类的对象
  4. 将此对象作为参数传递到FutureTask构造器中,创建FutureTask对象
  5. 将FutureTask对象作为参数传递到Thread类的构造器中,创建Thread对象并用start方法启动
  6. 【可选】获取Callable中的call方法的返回值
class NumThread implements Callable<Integer> {
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            if(i%2==0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadNew {
    public static void main(String[] args) {
        NumThread numThread = new NumThread();
        //创建FutureTask对象
        FutureTask<Integer> futureTask = new FutureTask<Integer>(numThread);
        //启动线程
        new Thread(futureTask).start();
        
        //只当需要获取call的返回值时调用
        try {
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
            Object sum = futureTask.get();
            System.out.println("总和为:"sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
  • 相比run方法,可以有返回值
  • 方法可以抛出异常
  • 支持泛型的返回值
  • 需要借助FutureTask类,比如获取返回结果

方式四:使用线程池

提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁实现重复利用。

  1. 提供指定线程数量的线程池
  2. 执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象
  3. 关闭连接池
class NumberThread implements Runnable{
    @Override
    public void run() {
        //TODO
    }
}

class NumberThread1 implements Runnable{
    @Override
    public void run() {
        //TODO
    }
}

public class ThreadPool {
    public static void main(String[] args) {
        //ExecutorService为接口
        //多态,实际类型为ThreadPoolExecutor
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        //ThreadPoolExecutor是ExecutorService的实现类
        //可以通过该实现类的对象来进行线程管理
        ThreadPoolExecutor service = (ThreadPoolExecutor)executorService;
        service.setCorePoolSize(15);

        executorService.execute(new NumberThread()); //适合使用于Runnable
        executorService.execute(new NumberThread1()); //适合使用于Runnable
        //executorService.submit(Callable callable); //适合使用于Callable

        //关闭连接池
        executorService.shutdown();
    }
}
  • 提高响应速度
  • 降低资源消耗
  • 便于线程管理
    • corePoolSize :核心池的大小
    • maximumPoolSize :最大线程数
    • keepAliveTime :线程没有任务时最多保持多长时间后会终止

Thread类中常用方法

  1. start():启动当前线程,调用当前线程的run()

  2. run()

  3. currentThread():静态方法,返回执行当前代码的线程

    Thread.currentThread().setName("主线程"); //在主线程中执行设置主线程的名字

  4. getName():获取当前线程的名字

  5. setName():设置当前线程的名字

    设置线程名字还可以通过Thread类的构造器进行赋值

  6. yield():暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程

  7. join():在线程a中调用线程b的join(),此时线程a进入阻塞状态,直到线程b完全执行完以后结束阻塞状态。

    try {
    	t1.join();
    } catch (InterruptedException e) {
    	e.printStackTrace();
    }
    
  8. sleep(long millitime):令当前活动线程在指定时间段放弃对CPU控制,时间到后重新排队

  9. isAlive():判断线程是否还存活


以下方法必须使用在同步代码块或同步方法中,他们都定义在Object类中:

  1. wait():阻塞线程,并释放同步监视器
  2. notify():唤醒一个线程,如果有多个线程wait,就唤醒优先级高的线程
  3. notifyAll():唤醒所有wait的线程

这三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则出现异常

sleep()和wait()的异同

  • 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态
  • 不同点:
    • 两个方法声明的位置不同,Thread类中声明sleep(),Object类中声明wait()
    • 两个方法的要求不同:sleep()可以在任何需要的场景下调用,wait()必须在同步代码块或同步方法中调用
    • sleep方法不会释放锁,wait方法会

线程的优先级

静态变量:

  • MAX_PRIORITY: 10
  • MIN_PRIORITY: 1
  • NORM_PRIORITY: 5 默认优先级

设置优先级:setPriority( int )

高优先级的线程会抢占低优先级线程cpu的执行权

高优先级的线程有更高概率被执行,并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行

同步机制

方式一:同步代码块

相当于操作系统中的临界区,每次只能有一个线程进入临界区

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

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

同步监视器(锁):任何一个类的对象都可以充当锁,但多个线程必须要共用同一把锁。

class MThread implements Runnable{
    private int ticket = 100;
    Object obj = new Object(); //继承方法中必须加static确保唯一,这里不用
    @Override
    public void run() {
        while(true){
            synchronized (obj){//任意对象,也可以用this,继承方法中不可以用this
                if(ticket > 0){
                    System.out.println(Thread.currentThread().getName() + "售票号:" + ticket);
                    ticket--;
                }else
                    break;
            }
        }
    }
}

方式二:同步方法

​ 如果操作共享数据的代码完整的声明在一个方法中,我们可以将该方法声明为同步的

  • 实现接口方法中,此时的同步监视器为this
private synchronized void show(){
        if(ticket > 0){
            System.out.println(Thread.currentThread().getName() + "售票号:" + ticket);
            ticket--;
        }
}
  • 继承Thread类方式中,此时同步监视器为当前类即 xxx.class
private static synchronized void show(){ //必须为静态方法
        if(ticket > 0){
            System.out.println(Thread.currentThread().getName() + "售票号:" + ticket);
            ticket--;
        }
}

方式三:Lock锁

class MThread implements Runnable{
    private int ticket = 100;
    //实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

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

}

synchronized和lock的区别

synchronized机制在执行完相应的同步代码后,自动的释放同步监视器

lock需要手动的启动同步(lock()),结束同步也需要手动的实现(unlock())

解决懒汉式单例模式中的线程不安全问题

方式一:效率较差

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

方式二:效率更高,避免了后来的线程进入临界区等待

class Bank{
    private Bank(){}
    private static Bank bank = null;
    public static Bank getInstance(){
        if(bank == null){
            synchronized (Bank.class){
                if(bank == null)
                    bank = new Bank();
            }
        }
        return bank;
    }
}
posted @ 2021-08-10 10:19  hugeBlair  阅读(46)  评论(0)    收藏  举报