多线程...

1、进程与线程的关系

进程:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。

线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

1.1 多线程 执行原理 ( 并发执行原理 )

CPU核心: 每一个计算机中CPU核心, 就相当于人的一个大脑, 可以执行一件事情.

时间片: 将CPU未来可以用于执行的时间 ,碎片化, 拆分成一个个的小的时间片段. 每一个时间片段的大小 可以都不足一毫秒.

程序在争抢到时间片段后, 会进行执行 .

2、Java 实现线程三种方式

方式1. 继承Thread类

步骤:

                1. 编写一个类, 继承Thread类

                2. 重写父类的 run 方法 , 在此方法中调用执行的代码 就是在新的线程中执行的代码.

                3. 创建此类的对象, 并调用start方法, 来启动此线程 ;

案例:

public class Demo {

        public static void main(String[] args) {

                MyThread m1 = new MyThread();

                m1.start();

                for(int i=0;i<100;i++) {

                        System.out.println("MainThread"+i);

                }

        }

}

class MyThread extends Thread{

        @Override

        public void run() {

                for(int i=0;i<100;i++) {

                        System.out.println("MyThread"+i);

                }        

        }

}

方式2. 实现Runnable接口

步骤:

        1. 编写一个类, 实现Runnable接口 , 实现run方法 (run方法的逻辑, 就是在新的线程中执行的)

        2. 创建上述Runnable接口实现类的 对象

        3. 创建Thread对象, 并在构造方法中, 第二步创建的对象

        4. 调用Thread对象的start方法,启动线程

案例:

public class Demo2 {

        public static void main(String[] args) {

                //1. 先创建MyRunnable对象

                MyRunnable m1 = new MyRunnable();

                //2. 创建Thread 对象,通过构造方法, 传入MyRunnable对象

                Thread t1 = new Thread(m1);

                //3. 启动线程

                t1.start();

                for(int i=0;i<100;i++) {

                        System.out.println("Main"+i);

                }

        }

}

class MyRunnable implements Runnable{

        @Override

        public void run() {

                for(int i=0;i<100;i++) {

                        System.out.println("MyRunnable"+i);

                }

        }

}

方式1(继承Thread) 和 方式2(实现Runnable) 哪个方式 更好?

Thread类在设计时, 使用到了一个设计模式: 代理设计模式.

我们使用第一种方式 和 第二种方式, 其实在内部都会使用代理设计模式, 最终的结果 都是实现了Runnable接口

在Thread内部, 无论是通过第一种方式也好, 第二种方式也好, 最终的效率, 是完全一致的.

虽然效率一致, 但是两种方式在使用时 ,更建议使用 第二种方式:

原因: 

使用继承的方式, 会导致我们编写的类 无法再继承其他的类 .

而使用第二种方式 , 因为是实现Runnable接口, Java允许多实现 ,所以我们的类还可以继续实现其他的接口, 或者 继承其他的类.

方式3.

使用匿名内部类的方式, 快速的编写一个Thread的子类, 然后调用start方法 启动线程

格式:

        new Thread() {

                @Override

                public void run() {

                        for(int i=0;i<100;i++) {

                                System.out.println("T一"+i);

                        }

        }

}.start();

我们上述的三种方式, 都编写了一个run方法.

1.         直接通过对象 .run的方式, 不是启动线程.

            只有通过start调用, 才是启动新的线程路径.

2. 一个方法, 如果被某一个线程调用执行, 那么此方法本次就执行在这个线程中 .

案例:

                Util u = new Util();

                new Thread() {

                        public void run() {

                                u.x();

                        }

                }.start();

                new Thread() {

                        public void run() {

                                u.x();

                        }

                }.start();

Thread 类 常用方法

 - 常用的静态方法

    静态方法作用于,调用的线程。

        - static void sleep(int 毫秒数) ***

                此方法执行时 所在的线程睡眠 , 用于降低线程的活跃度 .

        - static Thread currentThread();

                获取此方法执行时 所在线程的 线程对象.

        - static void yield();

                此方法执行时所在的线程, 让出当前时间片. 用于降低线程的活跃度

- 常用的非静态方法

        - void start() ***

                启动此线程所表示的线程对象, 并在新的线程中执行run方法

        - void setName(String name) | String getName(); 了解

                设置/获取 线程名称; 线程拥有默认的名称

        - stop() 

                停止线程(已过时 , 不建议使用, 因为会造成固有的不安全性);

        - interrupt() 

                中断线程!

                此方法用于在某一个线程上 加入 中断标记.

                这个线程被加入标记后, 它正在执行的逻辑,如果触发一些特殊的方法, 会出现异常 !

                通常此操作用于通知线程停止 . 具体是否停止, 由线程自身决定.

        - setDaemon(boolean flag) 了解

                传入true设置线程为守护线程 ! 默认一个线程为用户线程 .

        - setPriority(int 优先级) 了解

                线程优先级: 取值1-10 , 表示线程的10个优先等级. 10最高 1最低

                优先级的作用是: 让线程更有可能争抢到时间片 .

系统提供了建议使用的三种值:

                1: 较低的优先级

                5: 中等的优先级

                10: 较高的优先级

注意: 调整一个线程的优先级, 并不能保证一个线程绝对能争抢到时间片, 只是几率大了一丢丢 !

1和10的区别大概可以理解为: 50% 和51%

线程默认优先级

一个线程的默认优先级 与 启动它的线程完全一致 ;main线程的优先级为5

线程 共分为两类

1. 用户线程

通常情况下, 我们自己开启的线程都是用户线程, 所有的用户线程都是一条单独的执行路径, 一个软件的所有用户线程执行完毕, 程序会死亡 .

2. 守护线程

程序中存在一种特殊的线程, 叫做守护线程, 可以理解为 : 守护用户线程 .

当程序中所有的用户线程都死亡了. 守护线程无论是否执行完毕, 都会自动死亡.

Java中的垃圾回收器 GC 其实就是守护线程.

Java中存在单线程程序吗?

答: 不存在的 . Java在启动时必然存在的有两个线程:

1. main线程

2. GC垃圾回收器

3、线程的生命周期

3.1.创建

public class MyThread extends Thread{

        @Override

                public void run() {

                        //...

                }

}

//新建就是new出对象

MyThread thread = new MyThread();

当程序使用new关键字创建了一个线程之后,该线程就处于一个新建状态(初始状态),此时它和其他Java对象一样,仅仅由Java虚拟机为其分配了内存,并初始化了其成员变量值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。

3.2.就绪

当线程对象调用了Thread.start()方法之后,该线程处于就绪状态。

Java虚拟机 会为其创建方法调用栈和程序计数器,处于这个状态的线程并没有开始运行,它只是表示该线程可以运行了。从start()源码中看出,start后添加到了线程列表中,接着在native层添加到VM中,至于该线程何时开始运行,取决于JVM里线程调度器的调度(如果OS调度选中了,就会进入到运行状态)。

3.3.运行

当线程对象调用了Thread.start()方法之后,该线程处于就绪状态。

添加到了线程列表中,如果OS调度选中了,就会进入到运行状态

3.4.阻塞

阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况大概三种:

  • 1、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
  • 2、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
  • 3、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)。
  • 线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
  • 线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。唤醒线程后,就转为就绪(Runnable)状态。
  • 线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
  • 线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
  • 线程I/O:线程执行某些IO操作,因为等待相关的资源而进入了阻塞状态。比如说监听system.in,但是尚且没有收到键盘的输入,则进入阻塞状态。
  • 线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意性的,并在对实现做出决定时发生。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。

3.5.死亡

线程会以以下三种方式之一结束,结束后就处于死亡状态:

  • run()方法执行完成,线程正常结束。
  • 线程抛出一个未捕获的Exception或Error。
  • 直接调用该线程的stop()方法来结束该线程——该方法会产生固有的不安全性,不推荐.

4、线程同步

同步即排队 . 异步即并行.

线程安全问题:

当多个线程同时操作一段数据时,容易发生问题。

同步就是线程安全, 效率较低! 线程安全可以有效的结果 临界资源出错问题 !

异步表示线程非安全, 效率高

4.1、线程同步的两种实现方式

两种方式 都是通过 加锁 来完成同步操作的.

记住一点: 在同步代码块时 我们手动指定锁对象时 . 多个线程必须使用同一个锁 ,才可以完成同步操作 !

同步代码块

格式:

synchronized(Object 锁对象){

}

同步方法

非静态的同步方法的锁对象为this

静态方法的锁对象 , 是我们的类信息对象 (类名.class).

格式

        权限修饰符 synchronized 返回值声明 方法名(形式参数列表){

                }

4.2、同步方法 与 同步代码块, 何时使用, 选择哪个?

同步代码块 粒度更小 . 更灵活 !

同步方法的操作 最小单位就是一个方法所有的 代码 .

同步方法因为默认使用了this作为锁对象. 所以它存在一个特殊的操作:

同一个对象中的 所有同步方法, 均使用this这一把锁, 所以可以达到 多方法同时上锁的需求 !

案例:

public class Demo {

        @Test

        public void haha() throws Exception {

                for(int i=0;i<2000;i++) {

                        MyThread t1 = new MyThread();

                        t1.start();

                }

        }

}

class MyThread extends Thread{

        static int count = 1999;

        static Object o = new Object();

        @Override

        public void run() {

                //同步代码块

                synchronized(o) {

                        //1. 判断余票是否大于0

                        if(count>0) {

                                //余票充足

                                System.out.println(getName()+"余票充足:"+count);

                                System.out.println(getName()+"正在出票...");

                                count--;

                                System.out.println(getName()+"出票成功, 余票:"+count);

                        }else {

                                //余票不足

                                System.out.println(getName()+"余票不足:"+count);

                        }

                }

        }

}

4.3线程死锁

案例:

        public class A {

                public synchronized void a1(B b) {

                        System.out.println("a1在执行");

                        b.b2();

                }

                public synchronized void a2() {

                        System.out.println("a2在执行");

                }

        }

public class B {

                public synchronized void b1(A a) {

                        System.out.println("b1在执行");

                        a.a2();

                }

                public synchronized void b2() {

                        System.out.println("b2在执行");

        }

}

public class Demo {

         public static void main(String[] args) {

                A a = new A();

                B b = new B();

                Thread t1 = new Thread() {

                        public void run() {

                        a.a1(b);

                }

        };

Thread t2 = new Thread() {

        public void run() {

                b.b1(a);

        }

};

                t1.start();

                t2.start();

        }

}

当两个或多个线程 互相加锁时 , 就形成了死锁 .

死锁应尽可能的避免 , 避免死锁应遵循如下三个原则:

1. 顺序上锁

2. 反向解锁

3. 不要回头

4.4、线程池 Executors

池: 表示容器, 为了重复利用而设计.

线程池执行的流程:

        池中会缓存一些空闲线程 ,

        当需要多线程执行某流程时 , 会去池中判断 是否还存在空闲线程 .

                如果存在空闲线程, 则使用空闲线程进行操作 .

                如果不存在空闲线程, 且池未满的情况下, 会创建空闲线程存储到池中, 然后使用.

                如果不存在空闲线程, 且池已满的情况下, 会排队等待线程空闲.

池不要用,池不符合规范。 设计有小缺陷。

阿里巴巴编程规约描述:这四种线程池有缺陷,不要用。要用自己造。

4.5、Java中的四种线程池 . ExecutorService

1. 缓存线程池

        /**

        * 缓存线程池.

        * (长度无限制)

        * 执行流程:

        * 1. 判断线程池是否存在空闲线程

        * 2. 存在则使用

        * 3. 不存在,则创建线程 并放入线程池, 然后使用

        */

        ExecutorService service = Executors.newCachedThreadPool();

        //向线程池中 加入 新的任务

        service.execute(new Runnable() {

                @Override

                public void run() {

                        System.out.println("线程的名称:"+Thread.currentThread().getName());

                }

        });

        service.execute(new Runnable() {

                @Override

                public void run() {

                        System.out.println("线程的名称:"+Thread.currentThread().getName());

                }

        });

        service.execute(new Runnable() {

                @Override

                public void run() {

                        System.out.println("线程的名称:"+Thread.currentThread().getName());

        }

});

2. 定长线程池

/**

* 定长线程池.

* (长度是指定的数值)

* 执行流程:

* 1. 判断线程池是否存在空闲线程

* 2. 存在则使用

* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用

* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程

*/

ExecutorService service = Executors.newFixedThreadPool(2);

service.execute(new Runnable() {

        @Override

        public void run() {

                System.out.println("线程的名称:"+Thread.currentThread().getName());

        }

});

        service.execute(new Runnable() {

                @Override

                public void run() {

                        System.out.println("线程的名称:"+Thread.currentThread().getName());

                }

});

3. 单线程线程池

效果与定长线程池 创建时传入数值1 效果一致.        

/**

* 单线程线程池.

* 执行流程:

* 1. 判断线程池 的那个线程 是否空闲

* 2. 空闲则使用

* 4. 不空闲,则等待 池中的单个线程空闲后 使用

*/

ExecutorService service = Executors.newSingleThreadExecutor();

service.execute(new Runnable() {

        @Override

        public void run() {

                System.out.println("线程的名称:"+Thread.currentThread().getName());

        }

});

service.execute(new Runnable() {

        @Override

        public void run() {

                System.out.println("线程的名称:"+Thread.currentThread().getName());

        }

});

4. 周期性任务定长线程池

public static void main(String[] args) {

/**

* 周期任务 定长线程池.

* 执行流程:

* 1. 判断线程池是否存在空闲线程

* 2. 存在则使用

* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用

* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程

* 周期性任务执行时:

* 定时执行, 当某个时机触发时, 自动执行某任务 .

*/

ScheduledExecutorService service = Executors.newScheduledThreadPool(2);

/**

* 定时执行

* 参数1. runnable类型的任务

* 参数2. 时长数字

* 参数3. 时长数字的单位

*/

service.schedule(new Runnable() {

        @Override

        public void run() {

                System.out.println("石鹏魏洋相视一笑~ 嘿嘿嘿");

        }

},5,TimeUnit.SECONDS);

*/

/**

* 周期执行

* 参数1. runnable类型的任务

* 参数2. 时长数字(延迟执行的时长)

* 参数3. 周期时长(每次执行的间隔时间)

* 参数4. 时长数字的单位

*/

service.scheduleAtFixedRate(new Runnable() {

        @Override

        public void run() {

                System.out.println("石鹏魏洋相视一笑~ 嘿嘿嘿");

        }

},5,2,TimeUnit.SECONDS);

}

1、Java存在单线程程序吗?

答案:不存在,一个Java程序运行时,必然会执行的有两个线程:main线程,GC线程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值