JAVA 多线程

1.相关概念:

1.并发和并行:

        并行:多个CPU执行多个任务

        并发:单个CPU执行多个任务

2.程序:具有一些特定功能的静态的代码段

3.进程:程序的动态运行过程

        特点:

                动态性:动态执行过程

                独立性:进程之间户型独立

                并发性:多个进程可以交替执行

4.线程:是进程的一部分

        一个进程至少包含一个线程

        现成本质是进程的一条执行路径

计算机执行 并发的!

程序 都支持多线程!

2.多线程的实现方式(3种)

方法名说明
void run()在线程开启后,此方法将被调用执行
void start()使此线程开始执行,Java虚拟机会调用run方法()

一:继承Thread*****

创建一个类 ,让他继承Thread类(不是抽象类)   Thread类里实现了Runnable接口 ,此接口只有一个run方法。并且被Thread重写了,所以,自己创建的类要 手动重写 run方法

自己创建的类  并且重写run方法

public class MyThread extends Thread{
    @Override
    public void run() {
//        super.run();
        for (int i = 0; i < 10; i++) {
            System.out.println("MYThread" + i);
        }
    }
}

测试类开启多线程

public class Demo {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
//        myThread1.run();这里只是调用重写后的方法, 所以不会启动多线程
//        启动线程
        myThread1.start();//启动该线程
//        myThread1.start();
        MyThread thread = new MyThread();
        thread.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("main" + i);
        }
    }
}

注意:

        run()方法和start()方法的区别?

                run():封装线程执行的代码,直接调用,相当于普通方法的调用

                start():启动线程;然后由JVM调用此线程的run()方法

二:接口方式创建多线程*****

Thread构造方法

方法名说明
Thread(Runnable target)分配一个新的Thread对象
Thread(Runnable target, String name)分配一个新的Thread对象

 这里实现接口,我们直接创建一个类实现Runnable接口  ,重写里边的run方法

​
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("MyRunable" + i);
        }
    }
}

 测试类:

public class Demo {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
//        mr.run();
//        不能 start()
//        Thread thread = new Thread(mr);
        Thread thread = new Thread(mr,"线程1");//可以通过有参构造直接给该线程取名字
        thread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main" + i);
        }
    }
}

三:实现Callable接口  用的很少 ,编写太复杂

方法名说明
V call()计算结果,如果无法计算结果,则抛出一个异常
FutureTask(Callable<V> callable)创建一个 FutureTask,一旦运行就执行给定的 Callable
V get()如有必要,等待计算完成,然后获取其结果

/*
* 里边要传入泛型  要有返回值 ,类型是传递的泛型
* */
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
//        return null;
        for (int i = 0; i < 10; i++) {
            System.out.println("表白第" + (i+1) + "次");
        }
        return "答应你啦!";
    }
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable mc = new MyCallable();

//        传递
        FutureTask ft = new FutureTask(mc);
        Thread thread = new Thread(ft);
//        System.out.println(1);
//        System.out.println(ft.get());阻塞
//        System.out.println(2);
        thread.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("main" + i);
        }
//        返回值
//
        System.out.println(1);
        System.out.println(ft.get());
        System.out.println(2);
    }
}

一般都要用第一种继承和实现Runnable接口

四:创建多线程简单方式: 

用lanbda表达式在主方法,直接创建

public class Demo {
    public static void main(String[] args) {
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("子线程1=》"+i);
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("子线程2=》"+i);
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("子线程3=》"+i);
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("子线程4=》"+i);
            }
        }).start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main=>" + i);
        }
    }
}

2.设置和获取线程名称

方法名说明
void setName(String name)将此线程的名称更改为等于参数name
String getName()返回此线程的名称
Thread currentThread()返回对当前正在执行的线程对象的引用

 也可以通过 构造函数  直接给名字

注意:Thread currentThread 可以直接通过类名直接调用

 3.线程休眠状态

方法名说明
static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的毫秒数
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        /*System.out.println("睡觉前");
        Thread.sleep(3000);
        System.out.println("睡醒了");*/

        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);

        t1.start();
        t2.start();
    }
}

 4.进程优先级    了解

方法名说明
final int getPriority()返回此线程的优先级
final void setPriority(int newPriority)更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10
/*
* 线程的优先级
*   【1-10】 之间
*  默认是5
*
* 优先级高,不代表一定获得CPU时间(执行权)
*
* */
public class Demo {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "=" + i);
            }
        },"子线程B");
        thread.setPriority(1);//设置优先权

//        System.out.println(thread.getPriority());//得到优先级
        Thread thread1 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "=" + i);
            }
        },"子线程A");

        thread1.setPriority(10);
        thread.start();
        thread1.start();

    }
}

5.守护线程

方法名说明
void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
public class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName("女神");
        t2.setName("备胎");

        //把第二个线程设置为守护线程
        //当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
        t2.setDaemon(true);

        t1.start();
        t2.start();
    }
}

  //把第二个线程设置为守护线程
        //当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.

不一定会立即停止

 6.线程安全问题

安全问题出现的条件

  • 是多线程环境

  • 有共享数据

  • 有多条语句操作共享数据、

解决方案:

        1.同步代码块

        2.同步方法

        3.Lock锁

1.同步代码块

synchronized(任意对象) { 
    多条语句操作共享数据的代码 
}

synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

同步的好处和弊端

  • 好处:解决了多线程的数据安全问题

  • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

public class SellTicket implements Runnable {
    private int tickets = 100;
    private Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj) { // 对可能有安全问题的代码加锁,多个线程必须使用同一把锁
                //t1进来后,就会把这段代码给锁起来
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                        //t1休息100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //窗口1正在出售第100张票
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--; //tickets = 99;
                }
            }
            //t1出来了,这段代码的锁就被释放了
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();

        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

 2.同步方法

同步方法:就是把synchronized关键字加到方法上

修饰符 synchronized 返回值类型 方法名(方法参数) { 
	方法体;
}

3.Lock锁   (手动创建释放)

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

- ReentrantLock构造方法
          ReentrantLock()         | 创建一个ReentrantLock的实例 |
          void lock()                   | 获得锁  |
          void unlock()               |  释放锁  |


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Ticket implements Runnable{
    private int count = 100;//100张票
//    private Object obj = new Object();
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            System.out.print("");
//            加锁
            lock.lock();//手动加锁
                if(count > 0){
                    count--;
//                    try {
//                        Thread.sleep(10);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
                    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + count + "张");
                }else {
                    lock.unlock();    //手动释放
                    break;
                }
        }
    }
}

 7.死锁问题

 死锁:
*    多个线程互相持有对方想要的锁标记而处于僵持的一种状态
*
*   产生原因:
*       1.抢占式
*       2.锁的嵌套
*       3.锁不释放,对方无法抢夺,处于循环等待过程
*
* 解决:
*   不要写锁嵌套

8.生产者 消费者****

所谓生产者消费者问题,实际上主要是包含了两类线程:

        一类是生产者线程用于生产数据

        一类是消费者线程用于消费数据

方法名说明
void wait()导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify()唤醒正在等待对象监视器的单个线程
void notifyAll()唤醒正在等待对象监视器的所有线程
/*
* 创建两个线程
*   吃货线程
*   生成线程
* 交替打印
* */
public class Demo {
    public static void main(String[] args) {
        Object obj = new Object();
        int count = 1000;
//        吃货线程
        new Thread(()->{
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (obj) {
                for (int i = 1; i <= count; i++) {
                    obj.notifyAll();
                    System.out.println(Thread.currentThread().getName() + "第" + i + "包子");

                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"吃了").start();
//        生成线程
        new Thread(()->{
            synchronized (obj) {
                for (int i = 1; i <= count; i++) {
                    obj.notifyAll();
                    System.out.println(Thread.currentThread().getName() + "第" + i + "个包子");

                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"生产").start();
    }
}

 9.线程池****

线程池:
*   好处:
        1.帮助我们维护了一些线程的创建和管理
*         节省了创建 线程和销毁的资源开销
*       2.线程的重复利用:
*         线程执行完任务后,不是销毁,而是归还线程池,以备下次任务使用
public class Demo {
    public static void main(String[] args) {
//        创建一个线程池
        ExecutorService pool = Executors.newCachedThreadPool();
//        show1(pool);
//        自动创建 多个线程  进行打印程序
        for (int i = 0; i < 500; i++) {
//            提交  Lanbdm 表达式
            pool.submit(()->{
                System.out.println(Thread.currentThread().getName() + "执行");
            });
        }
    }

    
    
    public static void show1(ExecutorService pool) {
        //        线程池的常用方法  自己定义的类 为了创建多个线程 
        MyRunnable myRunnable = new MyRunnable();
//        提交
        pool.submit(myRunnable);
        
        pool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 正在执行!");
                System.out.println(Thread.currentThread().getName() + " 执行完毕!");
            }
        });

        pool.submit(()->{
            System.out.println(Thread.currentThread().getName() + " 正在执行!");
            System.out.println(Thread.currentThread().getName() + " 执行完毕!");
        });
    }
}

线程池  固定开启线程个数

public class Demo {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 10; i++) {
            pool.submit(()->{
                System.out.println(Thread.currentThread().getName() + ":执行");
            });
        }
//        pool.shutdown();不建议
    }
}

自定义线程参数

七个参数  五个必回,两个有默认值

      1.核心线程数  3

      2.最大线程数 5
      3.空闲线程数  空闲时间  2,   销毁
      4.空闲线程数空闲时间单位   TimeUnit.SECONDS,

       5.阻塞队列        new ArrayBlockingQueue<>(10)

/*
* 1.来了任务后,交给核心线程去处理
* 2.当任务超过了核心线程数:将任务放入阻塞队列
* 3.核心线程在忙,阻塞队列满了  此时启动临时线程
*
*最多十五个  阻塞队列加上最大线程数
*
* */

/*
* 1.来了任务后,交给核心线程去处理
* 2.当任务超过了核心线程数:将任务放入阻塞队列
* 3.核心线程在忙,阻塞队列满了  此时启动临时线程
*
*最多十五个  阻塞队列加上最大线程数
*
* */
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Demo {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(3,
                5,2,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));
//        1核心线程数  3

//        2最大线程数 5
//       3空闲线程数  空闲时间  2,   销毁
//        4空闲线程数空闲时间单位   TimeUnit.SECONDS,
//      5.阻塞队列
//        new ArrayBlockingQueue<>(10)

//       如果是 16 会有 RejectedExecutionException拒绝执行异常
        for (int i = 0; i < 16; i++) {
            pool.submit(()->{
                System.out.println(Thread.currentThread().getName());
            });
        }

    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

華同学.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值