学习笔记 :多线程

多线程

1. 多线程的第一种启动方式

继承Thread类的方式进行实现

1.自己定义一个类继承Thread。

2.重写run方法。

public class Mythread extends Thread{
    @Override
    public void run(){
        System.out.println("Mythread is running");
    }
}

3.创建子类对象,开启线程。

Mythread t1 = new Mythread();
t1.start();

*** 注意:不得直接调用run()方法,否则将是直接调用方法而未开启线程***

2.多线程的第二种启动方式

实现Runnable接口的方式进行实现

自己定义一个类,实现Runnable接口

重写run方法

public class MyRun implements Runnable
{
    @Override
    public void run()
    {
        System.out.println("Thread is running");
    }
}

创建Thread对象并开启线程

//创建MyRun对象
//表示多线程要执行的任务
MyRun mr = new MyRun();
//创建线程对象
Thread t = new Thread(mr);
t.start();

3.多线程的第三种启动方式

实现Callable接口的方式进行实现

特点:可接受多线程运行结果

自己定义一个类,实现Callable接口

重写call方法

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        //求1到100的和
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

Callabe接口中包含一个泛类,为接受结果的类型。

创建Mycallable的对象和FutureTask的对象,FuterTask对象用于管理多线程运行的结果

public class ThreadTest3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable mc = new MyCallable();
        //创建FuterTask对象,管理多线程运行的结果。
        FutureTask<Integer> ft = new FutureTask<>(mc);
        //创建线程对象
        Thread t1 = new Thread(ft);
        t1.start();
        // 获取线程执行结果
        Integer i = ft.get();//get()方法报异常,直接抛出即可。
        System.out.println("线程执行结果:" + i);
    }
}

三种实现方式对比

总结:需要接受运行结果用实现Callable接口。继承Thread类可以扩展性差,不能再继承其他的类,实现Runnable接口扩展性强,实现接口的同时可以继承其他的类。

线程的优先级

JAVA中,线程的调度方式为:抢占式调度。

抢占式调度:随机性调度。优先级越大,这条线程抢到CPU的概率越大。

MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr,"A");
Thread t2 = new Thread(mr,"B");
System.out.println(t1.getPriority());// 5
System.out.println(t2.getPriority());// 5

优先级默认5,最小1,最大10。

保护线程

MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr,"A");
t1.setDaemon(true);

使用.setDaemon(true)将改线程设置为保护线程

保护线程:也叫备胎线程。当非守护线程执行完毕之后,守护线程没有存在的必要,将陆续结束。(不是立即结束)

例:聊天框中的文件传输线程,当聊天框关闭,文件传输将陆续停止。

礼让线程

public class MyRun implements Runnable
{
    @Override
    public void run()
    {
        System.out.println("Thread is running");
        Thread.yield();
    }
}

Thread.yield() 表示出让CPU的执行权。可以尽量让结果均匀。

插入线程

public class ThreadTset2 {
    public static void main(String[] args) throws InterruptedException {
        //创建MyRun对象
        //表示多线程要执行的任务
        MyRun mr = new MyRun();
        Thread t = new Thread(mr);
        t.start();
        
        t.join();
        for (int i = 0; i < 10; i++) {
            System.out.println("main"+i);
        }
    }
}

.join()方法表示将t线程插入到当前线程(main线程)之前,插入线程运行结束后,再执行当前线程。

线程的生命周期

创建:创建线程对象 start() -> 就绪(不停的抢cpu执行权):有执行资格,无执行权

抢到CPU的执行权 ** -> 运行:有执行资格,有执行权** 执行完毕 -> 死亡: 线程死亡,变成垃圾。

运行状态时,若被其他线程抢走CPU执行权,将回到 就绪状态,重新抢夺CPU执行权。若调用了sleep()或其他阻塞式方法,会进入阻塞等着状态,没有执行资格没有执行权,直到阻塞结束后回到就绪状态。

若遇到sleep()和wait()状态,也会进入阻塞等着状态。

注意! 阻塞方式结束后,无法立即执行下面代码,因就绪状态还需抢夺CPU执行权,抢夺后才能继续运行。

线程安全问题

例如购票时 高并发产生的超卖情况。

public class Mythread extends   Thread{
    static int  ticket = 0;
    static Object obj = new Object();
    @Override
    public void run(){
        while (true){
                if(ticket<100){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    ticket++;
                    System.out.println("线程"+Thread.currentThread().getName()+"卖票,票号为:"+ticket);
                }else{
                    break;
                }
        }
    }
}
public class ThreadDemo5 {
    public static void main(String[] args) {
        Mythread t1 = new Mythread();
        Mythread t2 = new Mythread();
        Mythread t3 = new Mythread();
        t1.setName("线程1");
        t2.setName("线程2");
        t3.setName("线程3");
        t1.start();
        t2.start();
        t3.start();
    }
}

可能出现异常结果:线程2卖票,票号为:101

同步代码块

public class Mythread extends   Thread{
    static int  ticket = 0;
    //锁对象
    static Object obj = new Object();
    @Override
    public void run(){
        while (true){
            //同步代码块
            synchronized (obj){
                if(ticket<100){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    ticket++;
                    System.out.println("线程"+Thread.currentThread().getName()+"卖票,票号为:"+ticket);
                }else{
                    break;
                }

            }
        }
    }
}

使用synchronized锁锁定代码块。

sychronized(锁对象),锁对象任意, 甚至是Object类,但是要保证锁对象唯一

等待唤醒机制

wait() 当先线程等待,直到被其他线程唤醒

notify()随机唤醒单个线程

notifyAll()唤醒所有线程

public class Desk {
    public static int foodFlag = 0;//桌子上食物状态:0无,1有
    public static int count = 10;//上菜最多10次
    public static Object lock = new Object();//锁
}

中间件:桌子

public class Foodie extends Thread{
    @Override
    public void run(){
        while(true){
            synchronized (Desk.lock){
                if(Desk.count == 0)
                {
                    break;
                }else {
                    if(Desk.foodFlag == 0){
                            try {
                                Desk.lock.wait();//由锁调用wait方法,使当前线程和锁绑定,wait会释放锁!
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                    }else{
                        Desk.count--;
                        System.out.println("Foodie is eating"+"还能吃"+Desk.count+"次");
                        Desk.lock.notifyAll();//唤醒等待的厨师
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }

}

消费者:食客

public class Cook extends Thread{

    @Override
    public void run() {
        while(true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    if(Desk.foodFlag == 1){//判断桌子上是否有一份食物
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else{
                        System.out.println("厨师做了一碗饭");
                        Desk.foodFlag = 1;//上菜一份
                        Desk.lock.notify();
                    }
                }
            }
        }
    }
}

生产者:厨师

public class ThreadDemo {
    public static void main(String[] args) {
        Cook c1 = new Cook();
        Foodie f1 = new Foodie();

        c1.setName("Chef");
        f1.setName("Foodie");

        c1.start();
        f1.start();
    }
}

阻塞队列实现等待唤醒机制

阻塞队列实现类:

  • ArrayBlockingQueue : 底层是数组,有界,创建时需要指定大小
  • LinkedBlockingQueue : 底层是链表,无界,但不是真正的无界,最大为int最大值

生产者

public class Cook extends Thread{
     ArrayBlockingQueue<String> queue;
     public Cook(ArrayBlockingQueue<String> queue) {
            this.queue = queue;
     }

     @Override
    public void run() {
         while(true){
             try {
                 queue.put("吮指原味鸡");//此处不用加锁,因为put()方法中已经有锁,再加锁会出现锁的嵌套,容易死锁
                 System.out.println("上菜一份");
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     }
}

消费者

public class Foodie extends Thread{
    ArrayBlockingQueue<String> queue;
    public Foodie(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while(true){
            try {
                String food = queue.take();//此处不用加锁,因为put()方法内已经有锁,再加锁会出现锁的嵌套,容易死锁
                System.out.println("获取到食物:"+food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
import java.util.concurrent.ArrayBlockingQueue;

public class ThreadDemo {
    public static void main(String[] args) {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);//指定队列大小1,存储String类型数据

        //创建线程对象,并把队列传递过去
        Cook c = new Cook(queue);
        Foodie f = new Foodie(queue);

        c.start();
        f.start();
    }
}

可能出现重复打印语句的情况,因为打印语句在锁外面,打印重复不影响数据操作。

线程池

public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池

public static ExecutorService newFixedThreadPool(int nThreads) 创建一个有上限的线程池

public class MyThreadPoolDemo {
    public static void main(String[] args) {
//        ExecutorService pool1 = Executors.newCachedThreadPool();//获取线程池对象
        ExecutorService pool1 = Executors.newFixedThreadPool(3);//获取线程池对象
        //提交任务
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());

        pool1.shutdown();//关闭线程池

    }
}
public class MyRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 1; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"  is running "+ i);
        }

    }

}
  1. 创建一个池子,池子中是空的
  2. 提交任务时,池子创建新线程对象,任务执行完毕,线程归还给池子,下回再提交任务的时候,不需要创建新线程,直接复用已有的线程
  3. 若提交任务时,池子中无空闲线程,也无法创建新的线程,任务将排序等待

任务拒绝策略

ThreadPoolExecutor.AbortPolicy : 丢弃任务抛出异常(默认)

ThreadPoolExecutor.DiscardPolicy : 丢弃任务,但是不抛出异常(不推荐)

ThreadPoolExecutor.DiscardOldestPolicy : 抛弃队列中等待时间最久的任务,然后把当前任务加入队列

ThreadPoolExecutor.CallerRunsPolicy : 调用任务的run()方法,绕过线程池直接执行

自定义线程池

线程池大小:

  • CPU密集型运算:项目运算较多,读取本地数据较少。 最大线程池大小为最大并行数+1
  • IO密集型运算:项目读取本地数据或数据库数据较多,运算较少。(大部分项目为这个类型) 最大线程池大小为 最大并行数 * 期望利用率 * (CPU运行时间+等待时间)/CPU运行时间
  • 例: 4核8线的CPU中,最大并行数为8 期望利用率100,运行时间1s,等待时间1s。公式计算: 8 * 100% * 2 = 16 则线程池合适大小为16
public class threadpoolDemo {
    public static void main(String[] args) {
        //核心线程数量,最大线程数,空闲线程最大存活时间,时间单位,任务队列,创建线程工厂,任务拒绝策略
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,
                4,
                1,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                new ThreadPoolExecutor.AbortPolicy());
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值