线程笔记

线程

目录

线程

线程执行方式

实现runnable接口实现多线程的好处:

多线程

并发与并行

进程和线程

线程调度

解决线程安全问题

等待唤醒机制

案例:生产者消费者(吃包子案例)

线程的状态6种

线程池:


线程执行方式

 

常用方法

实现多线程的4中方法:

1、继承Thread类,重写run方法

2、实现runnable接口,重写run方法

3、实现Callable接口,重写call()方法,call方法有返回值

4、通过线程池启动多线程

引用:https://blog.csdn.net/weixin_41891854/article/details/81265772

 

//实现runnable接口
public class RunableIml implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

//使用
public class TestRunnable {
    public static void main(String[] args) {
        RunableIml run = new RunableIml();
        Thread t = new Thread(run);
        t.start();
        for (int i = 0; i < 200; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
     }
}

实现runnable接口实现多线程的好处:

1、避免单继承的局限性,继承Thread类后就不能继承其他的类了,实现runnable接口还可以继承其他的类。

2、实现runnable接口增强了程序的扩展性,降低了程序的耦合性,实现runnable把设置线程任务与开启线程进行了分离。

重写的run方法来设置线程任务,创建线程Thread对象来开启新线程

new Thread时,传递不同的接口实现类完成不同的线程任务

class RunableIml1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}
class RunableIml2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("hello "+i);
        }
    }
}
public class TestRunnable {
    public static void main(String[] args) {

        Thread t1 = new Thread(new RunableIml1());
        t1.start();
        Thread t2 = new Thread(new RunableIml2());
        t2.start();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

匿名内部类实现线程的创建:

好处:简化代码

把子类继承父类,重写父类方法,创建子类对象一步完成

把实现类实现接口,重写接口中的方法,创建实现类对象合成一步完成

匿名内部类的最终产物:子类/实现类对象,这个对象没名字

格式:new 父类/接口{

        重写的方法

}

实现代码:

package testmap;

public class NiMingClass {
    public static void main(String[] args) {
        //使用Thread父类
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+i);
                }
            }
        }.start();

        //使用runnable接口
        Runnable r = new Runnable(){

            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("hello "+i);
                }
            }
        };

        Thread t = new Thread(r);
        t.start();

        //使用runnable接口更加简化
        Thread t2 = new Thread(new Runnable(){

            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("word "+i);
                }
            }
        });
        t2.start();
    }
}

多线程

并发与并行

进程和线程

进程:

Java进程通信的7中方式

线程:

线程调度

主线程:

单线程存在弊端:

多线程:

代码实现

package testmap;
/**
 * 多线程:
 * 实现步骤:
 * 1、创建一个Thread的子类
 * 2、在Thread的子类中重写Thread类中的run方法,设置线程任务
 * 3、创建Thread的子类对象
 * 4、调用Thread类中的start方法,开启新的线程,执行run方法
 *      结果是两个程序并发的执行,当前线程和另一个线程。
 *      多次启动一个线程是非法的。特别是当一个线程已经执行后,不能重新启动
 */
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run "+i);
        }
    }
}
public class MultiThread {

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.run();
        for (int i = 0; i < 20; i++) {
            System.out.println("main "+i);
        }
    }


}

 

线程安全问题

单线程不会出现线程安全问题

多线程不访问共享数据也不会有安全问题

只有多线程访问了共享数据才会出现安全问题

线程不安全的卖票案例:

package testmap;

/**
 * 实现卖票案例
 *
 * 通过代码块中的锁对象,保证多个线程使用的锁是同一个
 * 锁对象的作用:把同步代码块锁住,只让线程在同步代码块中执行
 */

class RunnableImpl implements Runnable{
    private int tickets = 100;

    //创建一个锁对象,锁可以是任意的
    Object obj = new Object();

    /**
     * 卖票
     */
    @Override
    public void run() {


        while(true){
//            synchronized (obj) {
                if (tickets > 0) {
                    try {
                        //提高线程不安全,卖票出错的可能性
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖票: " + tickets);
                    tickets--;
                }
//            }
        }

    }
}

/**
 * 模拟卖票,创建三个线程,同时卖票
 */
public class Tickets {
    public static void main(String[] args) {
        RunnableImpl run = new RunnableImpl();
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        Thread t3 = new Thread(run);

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

 

改进后对的卖票

1、解决线程安全问题:同步代码块

  /**
     * 卖票
     */
    @Override
    public void run() {
        while(true){
            synchronized (obj) {
                if (tickets > 0) {
                    try {
                        //提高线程不安全,卖票出错的可能性
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖票: " + tickets);
                    tickets--;
                }
            }
        }
    }

同步代码块原理:

2、解决线程安全问题:使用同步方法:

package testmap;

/**
 * 实现卖票案例
 *使用同步方法保证线程安全
 * 把访问了共享数据的代码抽取出来,放到同步的方法里
 * 使用同步关键字修饰方法
 */

class RunnableImpl2 implements Runnable{
    private int tickets = 100;

    public synchronized void payTicket(){
        while(true){
            if (tickets > 0) {
                try {
                    //提高线程不安全,卖票出错的可能性
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖票: " + tickets);
                tickets--;
            }
        }
    }
    /**
     * 卖票
     */
    @Override
    public void run() {
       payTicket();
    }
}

/**
 * 模拟卖票,创建三个线程,同时卖票
 */
public class Tickets2 {
    public static void main(String[] args) {
        RunnableImpl2 run = new RunnableImpl2();
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        Thread t3 = new Thread(run);

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

3、解决线程安全问题:使用Lock锁

package testmap;

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

/**
 * 实现卖票案例
 * 使用Lock锁来保证线程安全
 */

class RunnableImpl3 implements Runnable{
    private int tickets = 100;

    Lock lock = new  ReentrantLock();
    /**
     * 卖票
     */
    @Override
    public void run() {
        while(true){
            lock.lock();
            if (tickets > 0) {
                try {
                    //提高线程不安全,卖票出错的可能性
                    Thread.sleep(10); 
                    System.out.println(Thread.currentThread().getName() + "正在卖票: " + tickets);
                    tickets--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        }
    }
}

/**
 * 模拟卖票,创建三个线程,同时卖票
 */
public class Tickets3 {
    public static void main(String[] args) {
        RunnableImpl3 run = new RunnableImpl3();
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        Thread t3 = new Thread(run);

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

 线程间的通信

java实现线程间通信的四种方式

1、synchronized同步

2、while轮询

3、wait/notify机制

4、管道通信

等待唤醒机制

waiting状态不会浪费CPU也不会竞争锁,等待的线程需要notify来唤醒它

案例:生产者消费者(吃包子案例)

代码:包子类

package baozi;

/**
 * 包子类
 */
public class Baozi {
    public String pi;
    public String xian;
    boolean flag;
}

包子铺

package baozi;

/**
 *包子铺
 */
public class BaoZiPu extends Thread {
    private Baozi baozi;
    public BaoZiPu(Baozi baozi) {
        this.baozi = baozi;
    }

    @Override
    public void run() {
        int count = 0;
        while(true){
            synchronized (baozi){
                if(baozi.flag){
                    //有包子就等待
                    try {
                        baozi.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //被唤醒后开始生产包子
                if(count%2==0){
                    baozi.pi = "薄皮";
                    baozi.xian = "三鲜馅";
                }else{
                    baozi.pi = "冰皮";
                    baozi.xian = "牛肉馅";
                }
                count++;
                System.out.println("包子铺正在生产"+baozi.pi+baozi.xian+"的包子");
                //3秒生产包子
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //修改包子的状态为有
                baozi.flag = true;
                //唤醒吃货进程
                baozi.notify();
                //
                System.out.println("包子铺已经生产好了"+baozi.pi+baozi.xian+"的包子吃货可以吃了");
            }
        }
    }
}

吃货类

package baozi;

/**
 * 吃货
 */
public class ChiHuo extends Thread{
    private Baozi baozi;

    public ChiHuo(Baozi baozi){
        this.baozi = baozi;
    }

    @Override
    public void run() {
        //while死循环,让吃货一直吃包子、
        while(true){
            synchronized (baozi){
                if(!baozi.flag){
                    //没包子,吃货等待
                    try{
                        baozi.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //被唤醒之后,开始吃包子
                System.out.println("吃货正在吃"+baozi.pi+baozi.xian+"包子");
                baozi.flag = false;

                //唤醒包子铺线程
                baozi.notify();
                System.out.println("吃货吃完了"+baozi.pi+baozi.xian+"包子,包子铺开始生产");

            }
        }
    }
}

测试类

package baozi;

public class TestBaozi {

    public static void main(String[] args) {
        Baozi baozi = new Baozi();
        new BaoZiPu(baozi).start();
        new ChiHuo(baozi).start();

    }
}

线程的状态:6种

线程的状态转换图

1、新建

2、可运行

3、结束

4、无限等待

锁.wait(),不带时间参数

5、计时等待:

锁.sleep()计时等待期间,与锁无关,锁资源不被释放,等待时间后只需要抢夺cpu。

锁.wait(时间参数),带时间参数,会释放锁资源,案例如下

package notify_wait;

public class WaitDaiCanDemo2 {
    public static void main(String[] args) {
        Object obj = new Object();
        new Thread(){
            @Override
            public void run() {
                synchronized (obj){
                    try {
                        System.out.println("线程1 开始 wait(1000)");
                        obj.wait(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
        
        new Thread(){
            @Override
            public void run() {
                synchronized (obj) {
                    System.out.println("线程2开始执行");
                    System.out.println(obj);
                }
            }
        }.start();
    }
}

6、计时等待有两种方式

1、sleep(毫秒值):毫秒值结束就会自动醒来。

2、wait(毫秒值):如果,wait在毫秒值结束之前一直没被唤醒,那么他就会自动醒来

案例代码:

package notify_wait;

public class WaitDaiCan {
    public static void main(String[] args) {
        //创建锁对象
        Object obj = new Object();
        //创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
               while (true){
                   synchronized (obj){
                       System.out.println("顾客告知老板要的包子种类和数量");
                       //调用wait进入无限等待状态
                       try {
                           obj.wait(1000);
                           //线程被唤醒后,会继续执行wait之后的代码

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

    }
}

6、锁阻塞:

Blocked状态:一个正在阻塞等待锁对象的线程处于这一状态。

唤醒wait()线程的两种方式:

1、notify():随机唤醒一个正在等待这个锁的线程

2、notifyAll():唤醒所有正在等待这个锁的线程。

 

还是吃包子的案例:

package notify_wait;

public class WaitAndNotify {
    public static void main(String[] args) {
        //创建锁对象
        Object obj = new Object();
        //创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
               while (true){
                   synchronized (obj){
                       System.out.println("顾客告知老板要的包子种类和数量");
                       //调用wait进入无限等待状态
                       try {
                           obj.wait();
                           //线程被唤醒后,会继续执行wait之后的代码

                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                       //线程被唤醒后,会继续执行wait之后的代码
                       System.out.println("顾客开始吃包子");
                   }
               }
            }
        }.start();

        //创建一个老板线程(生产者)
        new Thread(){
            @Override
            public void run() {
               while(true){
                   System.out.println("老板开始做包子,需要5秒");
                   try {
                       Thread.sleep(5000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   synchronized (obj){
                       //老板花5s做包子

                       obj.notify();
                       System.out.println("老板做好了包子,告知顾客");
                   }
               }

            }
        }.start();
    }
}

线程池:

概念

合理利用线程池的三个好处:

 

/**
 * 线程池的使用步骤
 * 1、使用线程池的工厂类Executor里面提供的newFixedThreadPool生产一个指定线程数量的线程池
 * 2、创建一个雷,实现Runnable接口,重写run方法
 * 3、调用ExecutorService中的submit方法,传递线程(实现类),开启线程,执行run方法
 * 4、调用ExecutorService中的shutdown方法,销毁线程池(不建议,线程池就是为了重复使用)
 */

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 创建线程实现类,实现Runnable接口,重写run方法,设置线程任务
 */
class RunnableImplement implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"创建了一个新的线程");
    }
}
public class ThreadPoll {
    public static void main(String[] args) {
        //使用线程工厂类Ex而粗铜仁市里边提供的静态方法,创建指定线程个数的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        executorService.submit(new RunnableImplement());
        executorService.submit(new RunnableImplement());
        executorService.submit(new RunnableImplement());
        executorService.submit(new RunnableImplement());
        executorService.submit(new RunnableImplement());
        executorService.submit(new RunnableImplement());
        executorService.submit(new RunnableImplement());
        executorService.submit(new RunnableImplement());
        executorService.shutdown();
    }
}

线程池用完会归还,会使用多次

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值