第十五章 多线程

一、进程与线程

进程:几乎任何的操作系统都支持运行多个任务,通常一个任务就是一个程序,而一个程序(应用)就是一个进程。

线程:当一个进程运行时,内部可能包括多个顺序执行流,每个顺序执行流就是一个线程。

注:一个进程是可以包含一个或多个线程的集合

二、进程与线程的特点

进程的特点:

1.处于运行过程中的程序

2.系统调度的最小单元

3.分配独立的内存空间

4.资源保护要求高,开销大,效率相对较低

线程的特点:

1.线程共享整个进程的资源

2.CPU调度的最小单元

3.与同属一个进程的其它的线程共享进程(内存)的全部资源

4.占用资源少,便于通信

三、线程的三种创建方式

1.方式一:

  • 继承Thread类
  • 重写run方法
  • 创建Thread子类对象
  • 调用start方法
public class MyThread1 extends Thread {
    //线程要执行的任务
    @Override
    public void run() {
        for (int i=1;i<=1000;i++){
            System.out.println(i);
        }
    }

    //main方法在执行的时候会创建一个线程,叫做主线程
    public static void main(String[] args) {
        MyThread1 mt=new MyThread1();
        //开启线程一定要调用start方法,而不是调用run
        mt.start();

        MyThread1 mt2=new MyThread1();
        mt2.start();
    }
}

2.方式二:

  • 实现Runnable接口
  • 重写run方法
  • 创建实现类对象
  • 创建Thread对象,Runnable对象作为构造方法的实参
  • 调用start方法
//继承Thread类的类叫做线程类
//实现Runnable接口的类,叫做任务类,这个类不具备创建线程的能力
public class MyThread2 implements Runnable{
    @Override
    public void run() {
        for(int i=1;i<=100;i++){
            System.out.println(i);
        }
    }

    public static void main(String[] args) {
        //m1是任务对象,任务对象想要在多线程里执行,还需一个线程对象
        MyThread2 m1=new MyThread2();
        //让m1在t1里执行
        Thread t1=new Thread(m1);
        t1.start();
    }
}

3.方式三:

  • 实现Callable接口
  • 重写call方法
  • 创建Callable子类的实例化对象
  • 创建FutureTask对象,Callable对象作为构造方法的实参
  • 创建Thread对象,FutureTask对象作为构造方法实参
  • 调用start方法
public class MyThread3 implements Callable {
    @Override
    public Object call() throws Exception {
        int sum=0;
        for (int i = 0; i <=100 ; i++) {
            sum=sum+i;
        }
        return sum;
    }

    public static void main(String[] args) throws Exception{
        MyThread3 m1=new MyThread3();
        ExecutorService es = Executors.newSingleThreadExecutor();
        Future res = es.submit(m1);
        System.out.println(res.get());
        es.shutdown();
    }
}

start方法:启动一个线程,这时此线程处于并没有真正运行,一旦得到cpu时间片,执行run方法,这里的run方法称为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止。

(1)在Thread类中

(2)线程会被添加到线程组(ThreadGroup)中,等待线程调度器调用,当获取到资源时,就进入运行状态

(3)同一个线程只能start一次,多次调用start方法会抛出异常

(4)Java虚拟机调用该线程的run方法

run方法:线程要执行的任务需要写在run方法中

(1)在Thread类中

(2)如果线程重写了run方法,则调用该Runnable对象的run方法;否则,该方法不执行任何操作

(3)重写方法的权限修饰符必须使用public

(4)手动调用run方法,其只是Thread类中的一个普通方法调用,还是在主线程里执行

四、两种创建线程方式的对比

1.继承Thread类方式

  • 一个类只能继承一个父类,存在局限
  • 不能用一个实例建立多个线程
  • 能共享Thread类的static变量

2.实现Runnable接口方式

  • 一个类可以实现多个接口,扩展性较好
  • 多个线程共享同一个Runnable实例中的成员变量与静态变量

五、常用方法

1.currentThread()方法

  • Thread类的静态方法
  • 返回当前正在执行的线程对象的引用

2.getName()

  • Thread类的成员方法
  • 返回该线程的名称

3.getId()

  • Thread类的成员方法
  • 返回该线程的Id

4.isAlive:线程是否处于活动状态

5.sleep:指定的毫秒数内让当前正在执行的线程休眠

  • 中断后会抛出InterruptedException异常

6.interrupt:中断线程

7.stop:终止线程(不建议使用)

  • 标识变量结束线程

8.join:等待该线程终止(类似方法调用)

9.yield:线程让步,暂停当前正在执行的线程对象

  • setPriority:设置优先级
  • getPriority:获得优先级
  • 默认优先级是5,最小是1,最大是10

六、状态转换

1.新建、就绪、运行、死亡

2.新建、就绪、运行、就绪、运行、死亡

3.新建、就绪、运行、其他阻塞、就绪、运行、死亡

4.新建、就绪、运行、同步阻塞、就绪、运行、死亡

5.新建、就绪、运行、等待阻塞、同步阻塞、就绪、运行、死亡

七、线程同步与异步

1.synchronized关键字

格式:synchronized (this){

               代码块

                ……}

权限修饰符[static] synchronized 数据类型 方法名(){……}

修饰的内容(上锁的范围):

  • 代码块
  • 成员方法
  • 静态方法
public class CountTest implements Runnable{
    private int count;
    Object o=new Object();



    public synchronized  void test(){
        count++;
    }


    public static synchronized void test2(){
        
    }
    @Override
    public void run() {
       /* for (int i = 0; i < 1000; i++) {
            //synchronized (this)锁的是对象,当多个线程使用同一个任务对象的时候,才会受到锁的限制
            //如果多个线程使用的是不同的任务对象,则不受锁的影响
            synchronized (this){
                //使用synchronized对这行代码加锁,同一时刻,只有一个线程可以执行这行代码
                count++;
            }

        }*/

        for (int i = 0; i < 1000; i++) {
            //小括号可以写任意一种对象,写不同对象的时候,锁的范围也不一样
            synchronized (o){

            }
        }
    }

    public static void main(String[] args) throws Exception{
        CountTest c=new CountTest();
        Thread t1=new Thread(c);
        Thread t2=new Thread(c);
        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println(c.count);
    }
}

synchronized修饰代码块:

锁的范围:被修饰的代码块

(1)synchronized(this):

  • 锁的对象:调用代码块的对象

(2)synchronized(“字符串”):

  • 锁的对象:字符串对象
  • StringBuffer和StringBuilder的toString方法会创建新的对象
  • 可以调用intern()方法保证内容相同的字符串是同一对象

          intern方法:如果池已经包含与equals(Object)方法确定的相当于此String对象的字符串,则返回来自池的字符串。否则,此String对象将添加到池中,并返回对此String对象的引用。

synchronized修饰成员方法:

锁的范围:整个方法

锁的对象:调用成员方法的对象

等价形式:

    public synchronized void method()

    public void method(){                                                                                                                                synchronized(this){……                                                                                                                      }                                                                                                                                                     }

当一个线程访问对象的一个synchronized同步代码块时,另一个线程仍然可以访问该对象中的非synchronized同步代码块。

synchronized关键字不能被继承

  • synchronized并不属于方法定义的一部分
  • 子类重写父类中用synchronized修饰的被重写方法,无法自动获得同步

抽象方法不能使用synchronized关键字

构造方法不能使用synchronized关键字,但可以使用

synchronized代码块来进行同步

synchronized修饰类

格式:synchronized(MyClass.class){

                    ……

              }

锁的范围:代码块

锁的对象:此类中的所有对象

2.Lock类

(1)Lock类型的成员变量

锁的范围:调用lock()方法开始,调用unlock()方法结束

锁的粒度:此类的当前对象

(2)Lock类型的静态变量

锁的范围:调用lock()方法开始,调用unlock()方法结束

锁的粒度:此类中的所有对象

(3)tryLock(long time,TimeUnit unit)方法

表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其它线程获取),则返回false

参数:

  • time尝试的时间
  • unit时间的单位

说明:低版本中Lock类性能更高,使用CAS实现,JDK1.8之后synchronized两者差异不大

八、锁的分类

1.公平锁与非公平锁:在多个线程获取锁的时候,是否排队,公平锁要排队,非公平锁不需要排队,公平锁可以保证都获取到锁,非公平锁的效率更高

synchronized是非公平锁,lock可以使用公平锁

2.乐观锁与悲观锁:悲观锁认为一定会产生线程安全的问题,必须加锁才能解决问题;乐观锁认为不会产生线程安全问题,出了问题后才会加锁。

public class LockTest implements Runnable{
    //构造方法,true公平锁,false非公平锁
    private Lock lock=new ReentrantLock(true);


    public void test1(){
        //调用lock方法进行加锁
        lock.lock();
        //执行受保护的代码
        try {
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName()+"获取到了锁");
            Thread.sleep(3000);
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            //执行完一定要记得释放锁,最好放在finally里,保证他一定会执行
            lock.unlock();
        }
    }

    public void test2(){
        //尝试获取锁,如果三秒没有获取到锁,就不等了

        try {
            boolean tryLock = lock.tryLock(3, TimeUnit.SECONDS);
            if (!tryLock){
                System.out.println("人太多了,不等了");
                return;
            }
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName()+"获取到了锁");
            Thread.sleep(5000);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }


    }

    @Override
    public void run() {
            test2();

    }

    public static void main(String[] args) {
        LockTest task=new LockTest();
        Thread t1=new Thread(task);
        Thread t2=new Thread(task);
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }
}

3.死锁:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止

产生死锁的条件(需要同时满足以下条件)

互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放

请求和保持,即当资源请求者在请求其它的资源的同时保持对原有资源的占有

循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

public class DeadLock {

    private Object a=new Object();
    private Object b=new Object();

    public void sleep(int time){
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }



    //a线程调用test1
    public void test1(){
        synchronized (a){
            System.out.println("a线程获取到了a锁");
            sleep(10);
            System.out.println("a线程尝试获取b锁");
            synchronized (b){

            }
        }
    }


    //b线程调用test2
    public void test2(){
        synchronized (b){
            System.out.println("b线程获取到了b锁");
            sleep(10);
            System.out.println("b线程尝试获取a锁");
            synchronized (a){

            }
        }
    }

    public static void main(String[] args) {
        DeadLock d=new DeadLock();
        //匿名类对象
        Runnable r1=new Runnable() {
            @Override
            public void run() {
                d.test1();
            }
        };
        //当接口里只有一个抽象方法时,可以用lambda的方式来实现
        Runnable r2=()->{
            d.test2();
        };

        Thread t1=new Thread(r1);
        Thread t2=new Thread(r2);
        t1.start();
        t2.start();
    }




}
public class WaitTest extends Thread{
    private Object o=new Object();

    @Override
    public void run() {
        System.out.println("线程开始了");
        try {
            synchronized (o){
                //wait方法是Object类提供的,必须先获取到锁,才可以调用wait
                //让当前线程进入到等待状态,必须有其他线程来唤醒它,否则会一直等
                //sleep不会释放锁,wait会释放锁
                o.wait();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        WaitTest w=new WaitTest();
        w.start();
    }
}
public class NotifyTest {

    private Object o=new Object();

    public void test1(){
        synchronized (o){
            try {
                System.out.println("a线程进入到等待状态");
                o.wait();
                System.out.println("a线程被唤醒了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public void test2(){
        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (o){
            System.out.println("b线程开始执行了");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //唤醒a线程的等待状态
            o.notify();
        }
    }

    public static void main(String[] args) {
        NotifyTest n=new NotifyTest();
        //当lambda表达式的大括号里只有一行代码的时候,可以省略大括号
        Runnable r1=()->n.test1();
        Runnable r2=()->n.test2();

        Thread t1=new Thread(r1);
        Thread t2=new Thread(r2);
        t1.start();
        t2.start();
    }


}

线程进入死亡(终止)状态,但是该线程对象仍然是一个Thread对象,在没有被垃圾回收器回收之前仍可以像其它对象一样引用它

线程调用了sleep()方法后,该线程将进入阻塞状态

notify()方法的作用是唤醒线程

内存回收程序负责释放无用内存

九、练习题

通过加锁与释放锁更好的了解先后顺序

public class CookTest {
    /*
    实现以下逻辑:
    1. a去买菜
    2. b等待a买菜,a买回来之后,b来洗菜
    3. b洗完菜之后,a来炒菜
    4. a炒完菜之后,b来端菜
     */
    //用a和b两个对象来控制两个线程的等待状态
    private Object a=new Object();
    private Object b=new Object();

    public void test1(){
        try{
            Thread.sleep(5);
            System.out.println("a去买菜");

            synchronized (b){
                System.out.println("a买菜回来了,通知b去洗菜");
                b.notify();
            }
            synchronized (a){
                a.wait();
            }
            System.out.println("a开始炒菜");
            synchronized (b){
                System.out.println("a炒完菜,通知b端菜");
                b.notify();
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }


    public void test2(){
        try{
            synchronized (b){
                b.wait();
            }
            System.out.println("b去洗菜");
            synchronized (a){
                System.out.println("b洗完菜了,通知a去炒菜");
                a.notify();
            }
            synchronized (b){
                b.wait();
            }
            System.out.println("b把菜端到了桌上");
        }catch (Exception e){
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        CookTest n=new CookTest();
        Runnable r1=()->n.test1();
        Runnable r2=()->n.test2();
        Thread t1=new Thread(r1);
        Thread t2=new Thread(r2);
        t1.start();
        t2.start();
    }

}

十、线程池

public class PoolTest1 {
    //线程池
    public static void main(String[] args) {
        //创建一个单线程的线程池,里面只有一条线程
        //线程池处理任务的逻辑是我们把任务交给线程池,由线程池来给任务分配线程,执行任务
        //应用场景:任务如果需要按照提交顺序来执行,可以使用这个线程池
        ExecutorService es = Executors.newSingleThreadExecutor();
        Runnable r1=()-> System.out.println("r1");
        Runnable r2=()-> System.out.println("r2");
        es.submit(r1);
        es.submit(r2);
        //关闭线程池
        es.shutdown();


    }
}
public class PoolTest2 {
    public static void main(String[] args) {
        //创建一个定长线程池,里面线程的数量由参数来控制
        ExecutorService es = Executors.newFixedThreadPool(5);

        for (int i = 0; i < 20; i++) {
            final int j=i;
            Runnable r=()->{
                System.out.println("正在执行第"+j+"个任务");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
            es.submit(r);
        }
        es.shutdown();
    }
}
public class PoolTest3 {
    public static void main(String[] args) {
        //可缓存的线程池,线程数不够就会创建,空闲了就会删除,适合在一些任务数不会突发特别高的情况下
        ExecutorService es = Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++) {
            final int j=i;
            Runnable r=()->{
                System.out.println("正在执行第"+j+"个任务");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
            es.submit(r);
        }
        es.shutdown();
    }
}
public class PoolTest4 {
    public static void main(String[] args) {
        //定时任务线程池,可以周期性执行任务
        ScheduledExecutorService es = Executors.newScheduledThreadPool(1);
        Runnable r=()-> System.out.println("hello");
        //第一个参数是任务对象,第二个参数是初始的延迟时间,第三个参数是频率时间,第四个参数是时间单位
        es.scheduleAtFixedRate(r,1,2,TimeUnit.SECONDS);

    }
}
public class PoolTest5 {
    public static void main(String[] args) {
        //手动创建一个线程池对象
        //第一个参数:核心线程数
        //第二个参数:最大线程数,线程池最多能拥有的线程数量
        //第三个和第四个参数:线程空闲存活时间和时间单位
        //第五个参数:任务的等待队列
        ExecutorService es=new ThreadPoolExecutor(5,15,0, TimeUnit.SECONDS,new LinkedBlockingQueue<>(100));
        
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值