进程和线程

1.什么是线程

线程,又称轻量级进程(Light Weight Process)。 进程中的一条执行路径,也是CPU的基本调度单位。 一个进程由一个或多个线程组成,彼此间完成不同的工作, 同时执行,称为多线程。

迅雷是一个进程,当中的多个下载任务即为线程。

Java虚拟机是一个进程,当中默认包含主线程(main),可通过代码创建多个独立线程,与main并发执行。

public class Test1 {
    public static void main(String[] args) {
        My my = new My();
//为线程 创建名字
        my.setName("wmd");
//开启 线程
        my.start();
//主线程的代码块
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"---------------");

        }
    }
//写一个类继承 Thread
    static class My extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
//Thread.currentThread().getName  获得线程的名字
                System.out.println(Thread.currentThread().getName()+"************");
            }
        }
    }
}

 由控制台输出的情况可以看出 进程和主线程任务是交替执行的

2.进程和线程的区别

1. 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。

2. 一个程序运行后至少有一个进程。

3. 一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义。

4. 进程间不能共享数据段地址,但是同进程的线程之间可以。

3.线程的组成

任何一个线程都具有基本的组成部分  

 CPU时间片: 操作系统(OS)会为每个线程分配执行时间

运行数据:                

堆空间: 存储线程需要的对象,多个线程可以共享堆中的数据。

栈空间:  存储线程需使用的局部变量,每个线程都拥有独立的栈。

线程的逻辑代码.

4.线程的特点

1. 线程抢占式执行  

效率高 可防止单一线程长时间独占CPU

2.在单核CPU中,宏观上同时执行,微观上顺序执行

5.创建线程(1)--第一种方式: 继承Thread类

(上面代码块中就是第一种方式)

1.继承 Thread

2.重写run方法

3.创建线程对象

4.开启线程

获取线程ID和线程名称:

1. 在Thread的子类中调用this.getId()或this.getName()

2. 使用Thread.currentThread().getId()和 Thread.currentThread().getName()

修改线程名称:

1. 调用线程对象的setName()方法

2. 使用线程子类的构造方法赋值

示例:使用线程Thread类实现4个窗口各卖100张票

分析 一个买票线程 创建4个对象 同时循环执行100次

6.创建线程(1)--第二种方式: 实现Runnable接口

1.实现Runnable

2.重写run方法

3.创建线程对象

4.开启线程

实例:实现四个窗口共卖100张票

public class Ticket2 implements Runnable{
//实现接口

  private int ticket = 100;
//重写方法
    @Override
    public void run() {
            while (true){
                if(ticket>0){
                    ticket--;
                    System.out.println(Thread.currentThread().getName()+"卖票一张,还剩下"+ticket);
                }else {
                    break;
                }
            }
    }
}
class TestTicket2{
    public static void main(String[] args) {
//创建线程对象
        Ticket2 ticket2 = new Ticket2();
        Thread t1 = new Thread(ticket2,"窗口A");
        Thread t2 = new Thread(ticket2,"窗口B");
        Thread t3 = new Thread(ticket2,"窗口C");
        Thread t4 = new Thread(ticket2,"窗口D");
//开启线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

实例:你和你女朋友公用一张银行卡,你向卡中存钱,你女朋友从卡中取钱,使用线程模拟过程!(两个线程对一个类的操作)


/**
 * 存取钱 两个线程对同一进程的操作
 */
public class TestBankCard {
    private static String a = new String();
    public static void main(String[] args) {

        BankCard bc = new BankCard(0);//卡里的初始余额是0;
        SaveMoney saveMoney = new SaveMoney(bc);//存钱操作的对象
        TakeMoney takeMoney = new TakeMoney(bc);//取钱操作的对象
        Thread t1 = new Thread(saveMoney,"男友");
        Thread t2 = new Thread(takeMoney,"女友");
        t1.start();
        t2.start();
    }

}
/**
 * 存钱
 */
class SaveMoney implements Runnable{
//引用银行卡类对象
    private BankCard bankCard;

    public SaveMoney(BankCard bankCard) {
        this.bankCard = bankCard;
    }
    @Override
    public void run() {
        synchronized (this){
            //进行10次存钱操作
            for (int i = 0; i < 10; i++) {
                //在原来余额的基础上加上1000,编辑为现在的余额
                bankCard.setYue (bankCard.getYue()+1000);
                System.out.println(Thread.currentThread().getName()+"存入1000,目前的余额是"+ bankCard.getYue());
            }
        }
    }
}
/**
 * 取钱
 */
class TakeMoney implements Runnable{
    private BankCard bankCard;

    public TakeMoney(BankCard bankCard) {
        this.bankCard = bankCard;
    }
    @Override
    public void run() {
        synchronized (this){
            //进行10次存钱操作
            for (int i = 0; i < 10; i++) {
                //取钱之前先判断,余额够不够用
                if(bankCard.getYue()>=1000){
                    //在原来余额的基础上减去1000,编辑为现在的余额
                    bankCard.setYue (bankCard.getYue()-1000);
                    System.out.println(Thread.currentThread().getName()+"取出1000,目前的余额是"+ bankCard.getYue());
                }else {
//如果余额不够,取钱次数减去1,并进行提醒
                    i--;
                    System.out.println("余额不足,请及时存钱");
                }

            }

        }
    }
}

/**
 * 抽取一个银行卡类
 */
class BankCard{
    private double yue;//余额属性

    public BankCard() {
    }

    public BankCard(double yue) {
        this.yue = yue;
    }

    public double getYue() {
        return yue;
    }

    public void setYue(double yue) {
        this.yue = yue;
    }

    @Override
    public String toString() {
        return "BankCard{" +
                "yue=" + yue +
                '}';
    }
}

7.线程的常见操作方法

1.休眠:    

 public static void sleep(long millis)    

 当前线程主动休眠millis毫秒。、

/**
 * Thread 休眠方法
 */
public class TestSellp1 {
    public static void main(String[] args) {
        T t = new T();
        t.setName("wmd");
        t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"PPPPPPPPP"+i);
        }
    }
}
class T extends Thread{
    @Override
    public void run() {
        try {
//设置休眠时间并解决异常
//由于设置了休眠时间 主线程执行完后,t 才开始进行了
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"============="+i);
        }
    }
}

2.放弃:  

    public static void yield()

      当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片

public class TestYield {
    public static void main(String[] args) {
        T t1 = new T();
        T t2 = new T();
        t1.start();
        t2.start();
    }
}
class T extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            Thread.yield();
//放弃方法,抢夺下一次时间片
//这种操作增加了两个线程的交替频率

            System.out.println(Thread.currentThread().getName()+"................."+i);
        }
    }
}

3加入:    

  public final void join()

  允许其他线程加入到当前线程中

public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
        T t1 = new T();
        T t2 = new T();
        T t3 = new T();
        t1.start();
        t1.join();//用join方法的作用,t1循环执行完后,t2才能执行,t2执行完,t3才能执行
        t2.start();
        t2.join();
        t3.start();
        t3.join();
    }
}
class T extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"........."+i);
        }
    }
}

4.守护线程:

线程对象.setDaemon(true);设置为守护线程。

线程有两类:用户线程(前台线程)和守护线程(后台线程)

如果程序中所有前台线程都执行完毕了,后台线程也会自动结束。

 垃圾回收线程属于守护线程

 /**
     * 守护线程
     */
    public static void main(String[] args) {
      T t = new T();
      t.setDaemon(true);//设置t 为守护线程 当main 循环结束后,t的 线程任务也就结束了
        //所以t的循环可能达不到20次就会结束
      t.start();


        for (int i = 0; i < 5; i++) {
            System.out.println("main***************"+i);
        }
    }
}
class T extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+".........."+i);
        }
    }

 8.线程安全问题

 需求: A线程将“Hello”存入数组;B数据将“World”存入数组。

 //设置数组的初始下标
    private static int index = 0;
    //设置一个长度为5的数组
    private static String[] arr = new String[5];

    public static void main(String[] args) throws InterruptedException {
        //匿名函数
        Runnable hello = new Runnable() {
            @Override
            public void run() {
                //加锁 使代码块运行时无法再分割
                synchronized (arr){
                    //如果一个数组某个 位置是空的,name就把hello 存进去,下标+1,
//world也是同样的原理
                    if (arr[index] == null) {
                        arr[index] = "hello";
                        index++;
                    }
                }
            }
        };
        Runnable world = new Runnable() {
            @Override
            public void run() {
                synchronized (arr){
                    if(arr[index] == null){
                        arr[index] ="world";
                        index++;
                    }
                }
            }
        };
        //运行添加hello的线程
        Thread t1 = new Thread(hello);
        //运行添加world的线程
        Thread t2 = new Thread(world);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(Arrays.toString(arr));
    }
synchronized (临界资源对象){//对临界资源对象加锁
//代码(原子操作)
}

注意:

每个对象都有一个互斥锁标记,用来分配给线程的

只有拥有对象互斥锁标记的线程才能进入该对象加锁的同步代码块

线程退出同步代码块时候,会释放相应的互斥锁标记

 经典问题:死锁

当A线程拥有锁资源a时,这时A线程需要锁资源b, 而B线程拥有锁资源b,B线程需要锁资源a, 这样会导致A等待B线程释放资源b, B线程等待A线程释放锁资源a。 从而二个处于永久等待。从而操作死锁。

public class TestLockObject {
//测试代码
    public static void main(String[] args) {
            Girl g = new Girl();
            Boy b = new Boy();
            g.setName("女生");
            b.setName("男生");
            g.start();
            b.start();
    }
}
//一个线程
class Boy extends Thread {
    @Override
    public void run() {
        synchronized (LockObject.b){
            System.out.println(Thread.currentThread().getName()+"获得了b线程");
        synchronized (LockObject.a){
            System.out.println(Thread.currentThread().getName()+"获得了a线程");
            System.out.println(Thread.currentThread().getName()+"得到了两个线程");
        }
        }
    }
}
//一个线程
class Girl extends Thread{
    @Override
    public void run() {
     
        synchronized (LockObject.a){
            System.out.println(Thread.currentThread().getName()+"获得了a线程");

        synchronized (LockObject.b){
            System.out.println(Thread.currentThread().getName()+"获得了b线程");
            System.out.println(Thread.currentThread().getName()+"得到了两个线程");
        }
        }
    }
}
//锁对象
class LockObject {
   public static Object a = new LockObject();
   public static Object b = new LockObject();
}

操作死锁得原因:

 锁与锁之间有嵌套导致。

如何解决死锁:

1. 尽量减少锁得嵌套。
2. 可以使用一些安全类。
3. 可以使用Lock中得枷锁,设置枷锁时间。

9.线程通信

我们无法决定哪个线程先获取cpu,这样也就无法决定哪个线程先执行,我们如果需要指定线程的执行顺序,就需要使用线程的通信技术

简单介绍线程通信中的方法

public final void wait() 必须在对obj加锁的同步代码块中,在一个线程中,调用方法,此线程会释放期拥有的所有锁标记,同时此线程会阻塞在o的等待队列中。释放锁,进入等待队列

public final void notify() 唤醒等待队列中的线程,进入到就绪队列中,参与cpu的竞争

实例代码:

public class TestBankCard {
    public static void main(String[] args) {
        //创建一个银行卡对象
        BankCard bc =new BankCard();
        //女生银行卡操作对象是那张银行卡
        GirlCard g = new GirlCard(bc);
        //男生银行卡操作对象是那张银行卡
        BoyCard b = new BoyCard(bc);
        Thread t1 = new Thread(g,"女生");
        Thread t2 = new Thread(b,"男生");
        t1.start();
        t2.start();
    }
    //一个女生类
    static class GirlCard implements Runnable{
        //一个银行卡属性
        public BankCard bc;

        public GirlCard(BankCard bc) {
            this.bc = bc;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    bc.take(1000.0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //一个男友类
    static class BoyCard implements Runnable {
        //一个银行卡属性
        private  BankCard bc;

        public BoyCard(BankCard bc) {
            this.bc = bc;
        }

        @Override
        public void run() {
            //进行十次存钱操作
            for (int i = 0; i < 10; i++) {
                try {
                    bc.save(1000.0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //一个银行卡类
    static class BankCard{
        private double balance;//余额属性
        private boolean flag = false;//钱的状态  有钱的时候是true 没钱的时候是false;

        public BankCard(Double balance, Boolean flag) {
            this.balance = balance;
            this.flag = flag;
        }

        public BankCard() {
        }

        public Double getBalance() {
            return balance;
        }

        public void setBalance(Double balance) {
            this.balance = balance;
        }

        public Boolean getFlag() {
            return flag;
        }

        public void setFlag(Boolean flag) {
            this.flag = flag;
        }
        //一个加锁的存钱方法
        public synchronized void save(Double money) throws InterruptedException {
            //如果里面有钱就进入等待状态
            if(flag==true){
                wait();
            }
            //不然就进入存钱状态
            this.balance=this.balance+money;
            System.out.println(Thread.currentThread().getName()+"往里面存"+money+"现在里面有"+this.balance);
            //改变银行卡状态
            flag=true;
            //唤醒等待队列状态   
            notify();
        }
        //一个加锁的取钱方法
        public synchronized void take(Double money) throws InterruptedException {
            //如果里面有没钱就进入等待状态
            if(flag==false){
                wait();
            }
            //不然就进入取钱钱状态
            this.balance=this.balance-money;
            System.out.println(Thread.currentThread().getName()+"往外面取"+money+"现在里面有"+this.balance);
            //改变银行卡状态
            flag=false;
            //唤醒等待队列状态   
            notify();
        }
    }
}

思考:

sleep和wait得区别?

1.所在得类不同。sleep属于Thread类,wait属于Object类。
2.使用的地方: sleep可以使用再任何代码块。wait只能再同步代码块中。
3.是否释放锁资源: sleep不释放锁资源,wait会释放锁资源。
4.sleep时间到了自动唤醒,wait必须需要使用notify和notifyAll唤醒

10.线程池

什么是线程池!

该池子中预先存储若干个线程对象。整个池子就是线程池。

线程是宝贵的内存资源,单个线程约占1MB空间,过多分配易造成内存溢出,频繁的创建和销毁线程会增加虚拟机回收的频率,资源开销,造成程序性能下降。

线程池的作用:

线程容器,可以设定线程分配的数量上限

将预先创建的线程对象存入池中,并重用线程池中的先吃对象。

避免频繁的创建和销毁。

线程池的创建方式有哪些?

所有的线程池---封装了一个父接口---java.util.concurrent.Executor.

​ 它的实现接口: ExecutorService.

有一个工具类。Executors可以帮你创建相应的线程池。

[1] 创建单一线程池 newSingleThreadExecutor()

只能创建一个线程

[2] 创建定长线程池。newFixedThreadPool(n);

可以创建长度为n的固定线程

[3] 创建可变线程池. newCachedThreadPool()

可以创建的线程的长队根据循环的次数来决定

[4] 创建延迟线程池 .newScheduledThreadPool(n);

可以设置时间单位,进行相应的延迟

public class Test1 {
    public static void main(String[] args) {
        //创建单一线程
        //ExecutorService pool = Executors.newSingleThreadExecutor();
        //创建固定线程
       // ExecutorService pool = Executors.newFixedThreadPool(8);
        //创建可变线程
       // ExecutorService pool = Executors.newCachedThreadPool();
//        for (int i = 0; i < 8; i++) {
//           pool.submit(new Runnable() {
//               @Override
//               public void run() {
//                   System.out.println(Thread.currentThread().getName()+"=============");
//               }
//           });
//
//        }
//        pool.shutdown();
        //创建延迟线程
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            (pool).schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~~");
                }
            },10, TimeUnit.SECONDS);
        }
pool.shutdown();
        }


}

11.使用最原始的方式创建线程池

上面讲解的使用Executors创建线程池的方式,都是使用底层ThreadPoolExecutor,而阿里开发手册,建议使用最原始的方式。

线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

public class Test02 {
    public static void main(String[] args) {
        /**
         * int corePoolSize, 核心线程数
*          int maximumPoolSize, 最大线程数
*          long keepAliveTime, 空闲时间
*          TimeUnit unit, 时间单位
*          BlockingQueue<Runnable> workQueue: 堵塞队列,
         *
         *  根据你自己的业务以及服务器配置来设置。
         */
        //LinkedBlockingDeque:可以设置等待的个数,如果不设置默认为Integer的最大值。
        BlockingQueue blockingQueue=new LinkedBlockingDeque(3);
        ThreadPoolExecutor threadPoolExecutor=
                new ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,blockingQueue);


        for (int i = 0; i <5 ; i++) {
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~");
                }
            });
        }

        threadPoolExecutor.shutdown();
    }
}

12.创建线程的第三种方式

实现Callable接口,它和实现Runnable接口差不多,只是该接口种的方法有返回值和异常抛出

应用场景,适合大文件上传

public class Test2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        My m1 = new My();
        My2 m2 = new My2();
//创建一个定量长度为5的线程
        ExecutorService executorService = Executors.newFixedThreadPool(5);
//分别提交线程
        Future<Integer> submit = executorService.submit(m1);
        Future<Integer> submit1 = executorService.submit(m2);
//拿到结果
        Integer integer = submit.get();
        Integer integer1 = submit1.get();
//控制台输出
        System.out.println(integer+integer1);

    }
//返回类型为Integer
    static class My implements Callable<Integer>{

        @Override
        public Integer call() throws Exception {
            Integer sum = 0;
            for (int i = 1; i <= 50; i++) {
                sum+=i;
            }
            return sum;
        }
    }
    static class My2 implements Callable<Integer>{

        @Override
        public Integer call() throws Exception {
            Integer sum = 0;
            for (int i = 51; i <= 100; i++) {
                sum+=i;
            }
            return sum;
        }
    }
}

13. 手动锁

Lock它是手动锁的父接口,它下面有很多实现类。

lock()方法。

unlock()释放锁资源,放在finally中

synchronized和lock有什么区别

synchronized 可以给类,方法,代码块加锁,而lock只能给代码块加锁

synchronized不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁,而lock需要自己加锁和释放锁,如果使用不当没有unLock() 去释放锁,就会造成死锁,

通过Lock可以知道有没有成功获取锁,而synchronized取无法办到

为保证unLock()必须会使用到

可以用finaly来操作

class TestTicket{
    public static void main(String[] args) {
//四个窗口各卖100张票
        Ticket ticket = new Ticket();
        Thread t1 = new Thread(ticket,"窗口A");
        Thread t2 = new Thread(ticket,"窗口B");
        Thread t3 = new Thread(ticket,"窗口C");
        Thread t4 = new Thread(ticket,"窗口D");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

public class Ticket implements Runnable {
    private int ticket = 100;
    //创建锁对象
    Lock s = new Lock();
    @Override
    public void run() {
        try {
            //加锁
            s.lock();
            while(true){
                if(ticket >0){
                    ticket--;
                    System.out.println(Thread.currentThread().getName()+"卖了一张票,现在还剩下"+ticket);
                }else {
                    break;
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            //在finally代码块中释放锁,这样释放锁的操作一定会运行
            //防止中间有异常操作,导致锁无法释放
        }finally {
            s.unlock();
        }
        }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值