Java多线程入门详解

多线程

一、三种实现方式

1.继承Thread类

  1. 步骤

    1. 定义一个Thread子类;
    2. 覆盖run方法(线程执行事件方法);
    3. 创建该线程的一个实例:Thread t=new MyThread();
    4. 启动线程t1.start;
  2. 实例:

    public class Main3 extends Thread{
        int a;
        Main3(int a) {
            this.a=a;
        }
        @Override
        public void run() {
            super.run();
            while (true) System.out.println(a);
        }
        public static void main(String[] args) {
            Thread t=new Main3(1);
            t.start();
            Thread t1=new Main3(2);
            t1.start();
        }
    }
    
    

2. 实现Runable接口

  1. 步骤

    1. 定义一个实现Runable接口的类,在类中实现run()方法(线程执行事件的方法)。
    2. 创建一个上述类的对象:Thread t=new Thread(new MyThreadt.start());
    3. 调用start 方法:t.start();
  2. 实例:

    public class Main2 implements Runnable {
        int a;
        Main2(int a) {
            this.a = a;
        }
        @Override
        public void run() {
            while (true) System.out.println(a);
        }
        public static void main(String[] args) {
            Thread t = new Thread(new Main2(1));
            t.start();
            Thread t2 = new Thread(new Main2(2));
            t2.start();
        }
    
    }
    
实现Runnable接口比继承Thread类所具有的优势:
  1. 适合多个相同的程序代码的线程去处理同一个资源

  2. 可以避免java中的单继承的限制

  3. 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

  4. 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

3.实现Callable接口,并与Future结合使用 (可获取线程返回值)

  1. 实现步骤:

    1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。

    2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。

    3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。

    4. 调用Tread对象的start()方法启动线程,调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

  2. 实例:

    public class CallableThreadTest implements Callable<Integer> {
        @Override
        public Integer call() throws Exception
        {
            int i = 0;
            for(;i<100;i++)
            {
                System.out.println(Thread.currentThread().getName()+" "+i);
            }
            return i;
        }
        public static void main(String[] args) throws ExecutionException, InterruptedException {
    
            FutureTask<Integer> ft = new FutureTask<>(new CallableThreadTest());
            for(int i = 0;i < 100;i++)
            {
                System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
                if(i==20)
                {
                    new Thread(ft).start();
                }
            }
    
                System.out.println("子线程的返回值:"+ft.get());
    
        }
    }
    
    

二、多线程相关方法

  1. 线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。

  2. 线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。

  3. 线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。
    注意

    1. 从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。
    2. notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续 执行
    3. Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。(拥有对象锁才能继续执行)
  4. 线程让步:Thread.yield() 方法, yield()做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

  5. 线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。

  6. interrupt():它是向线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!

三、线程的优先级

  1. 每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。

  2. Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

  3. 默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。

  4. Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。

  5. 具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

四、注意事项

  1. main方法本身也是一个线程。
  2. main线程结束后,子线程也会接着执行完毕。
  3. 在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM

五、线程池

1.含义:

线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

2.两种创建方式

2.1 使用Runnable接口创建线程池
  1. 步骤:

    1. 创建线程池对象
    2. 创建 Runnable 接口子类对象
    3. 提交 Runnable 接口子类对象
    4. 关闭线程池
  2. 实例:

    class TaskRunnable implements Runnable{
    
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("自定义线程任务在执行"+i);
            }
        }
    }
    public class ThreadPool {
    
        public static void main(String[] args) {
            //创建线程池对象  参数5,代表有5个线程的线程池
            ExecutorService service = newFixedThreadPool(5);
            //创建Runnable线程任务对象
            TaskRunnable task = new TaskRunnable();
            //从线程池中获取线程对象
            service.submit(task);
            System.out.println("----------------------");
            //再获取一个线程对象
            service.submit(task);
            //关闭线程池
            service.shutdown();
        }
    
    }
    
    
2.2使用Callable接口创建线程池
  1. 步骤:

    1. 创建线程池对象
    2. 创建 Callable 接口子类对象
    3. 提交 Callable 接口子类对象
    4. 关闭线程池
  2. 实例:

    class TaskCallable implements Callable<Integer> {
    
        @Override
        public Integer call() throws Exception {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+"   "+i);
            }
            return null;
        }
    }
    public class ThreadPoolOfCallable {
    
        public static void main(String[] args) {
            ExecutorService service = Executors.newFixedThreadPool(3);
            TaskCallable c = new TaskCallable();
            TaskCallable c1 = new TaskCallable();
    
            //线程池中获取线程对象,调用run方法
            service.submit(c);
            //再获取一个
            service.submit(c1);
            //关闭线程池
            service.shutdown();
        }
    
    }
    
    

六、线程同步

1.synchronized关键字

  1. synchronized的作用域有二种:
    1. 是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
    2. 是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
  2. 注意
    1. 除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/区块/},它的作用域是当前对象;

    2. synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;

2. 线程同步示例:

  1. 题意:建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。

  2. 代码如下:

    public class MyThreadPrinter2 implements Runnable {   
      
        private String name;   
        private Object prev;   
        private Object self;   
      
        private MyThreadPrinter2(String name, Object prev, Object self) {   
            this.name = name;   
            this.prev = prev;   
            this.self = self;   
        }   
      
        @Override  
        public void run() {   
            int count = 10;   
            while (count > 0) {   
                synchronized (prev) {   
                    synchronized (self) {   
                        System.out.print(name);   
                        count--;  
                        
                        self.notify();   
                    }   
                    try {   
                        prev.wait();   
                    } catch (InterruptedException e) {   
                        e.printStackTrace();   
                    }   
                }   
      
            }   
        }   
      
        public static void main(String[] args) throws Exception {   
            Object a = new Object();   
            Object b = new Object();   
            Object c = new Object();   
            MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);   
            MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);   
            MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);   
               
               
            new Thread(pa).start();
            new Thread(pb).start();
            new Thread(pc).start();    }   
    }  
    
  3. 解析:由于是小白,原博客对此例题的解析看了很久才看懂,所以以小白角度写下对原文的理解:

    1. 程序一开始pa线程启动:执行打印A->释放A(此次无意义,后面循环有意义)->锁住C(注意:这里不是锁住c线程,而是暂停当前线程,等待C释放后继续执行当前线程,因为后面pc线程执行完毕后同样会释放C,那时候再执行当前线程,就能实现循环打印ABC,一开始我自己理解以为是锁住c进程,走了很多弯路)
    2. 当线程pa被暂停后,执行线程pb: 执行打印B—>释放B(此次无意义,后面循环有意义)—>锁住A(等待A释放,也就是当pa线程执行完后再执行当前线程)
    3. 当线程pb暂停后,执行线程pc:执行打印C—>释放C(现在开始就有意义了,因为一开始pa是锁住了C,现在释放后,意味着pa线程可以继续执行)—>锁住B(锁住当前线程,等待线程pb执行完毕后释放B,然后再执行当前线程)
    4. 由于上一步骤释放了C,所以pa线程启动:打印A->释放A(现在就有意义了,释放A后,线程pb就可以执行)->锁住C(暂停当前线程)后面就一直循环执行线程 pa,pb,pc 依次打印A、B、C

转载文章及博客:

	```
	线程同步博客:
	https://blog.csdn.net/zyplus/article/details/6672775
	https://www.cnblogs.com/yjd_hycf_space/p/7526608.html
	//线程学习文章
	https://www.runoob.com/java/java-multithreading.html

	```
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值