多线程和网络编程(1)

一、多线程

1、进程和线程

进程:是正在运行的程序
	是系统进行资源分配和调用的独立单位。
	每一个进程都有它自己的内存空间和系统资源。
线程:是进程中的单个顺序控制流,是一条执行路径。
	单线程:一个进程如果只有一条执行路径,则称为单线程程序。
	多线程:一个进程如果有多条执行路径,则成为多线程程序。

2、多线程的实现方式

方式1:继承Thread类

  • 定义一个类MyThread继承Thread类
  • 在MyThread类中重写run()方法
  • 创建MyThread类的对象
  • 启动线程

案例:

/**
 * @Author TeaBowl
 * @Date 2021/3/16 17:27
 * @Version 1.0
 * 定义一个类MyThread继承Thread类
 */
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("i:" + i);
        }
    }
}
/**
 * @Author TeaBowl
 * @Date 2021/3/16 17:24
 * @Version 1.0
 * 方式1:继承Thread类
 */
public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread m1 = new MyThread();
        MyThread m2 = new MyThread();

        m1.start();
        m2.start();
    }
}

控制台:
在这里插入图片描述


小问题:

1.为什么要重写run()方法?

因为run()方法是用来封装被线程执行的代码。

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

run():封装被线程执行的代码,直接调用,相当于普通方法的调用。
start():启动线程,然后油JVM调用此线程的run()方法。

设置和获取线程名称

Thread类中设置和获取线程名称的方法

  • void setName(String name):将此线程的名称更改为等于参数name。
  • String getName():返回线程的名称。
  • 通过构造方法也可以设置线程名称。

案例1:

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread m1 = new MyThread();
        MyThread m2 = new MyThread();

        m1.setName("高铁");
        m2.setName("飞机");

        m1.start();
        m2.start();
    }
}

控制台:
在这里插入图片描述


案例2:

public class MyThread extends Thread {
    //无参构造
    public MyThread(){

    }

    //带参构造
    public MyThread(String name){
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
        //Thread(String name)
        MyThread m1 = new MyThread("高铁");
        MyThread m2 = new MyThread("飞机");

        m1.start();
        m2.start();
    }
}

控制台:
在这里插入图片描述


小问题:如何获取main()方法所在的线程名称?

public class MyThreadDemo {
    public static void main(String[] args) {
        //currentThread():返回正在执行的线程对象的引用,静态方法。
        System.out.println(Thread.currentThread().getName());
    }
}

控制台输出:main


线程调度

线程有两种调度模型

  • 分时调度模型:所有线程轮流使用CPU的使用权,平均分配给每个线程占用CPU的时间片。
  • 优先让优先级高的线程使用CPU,如果线程优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些。

线程优先级:最小1、默认5、最大10。

线程优先级高,并不是指每次都跑在最前面,而是指获得CPU时间片几率高。

java使用的是抢占式调度模型

Thread类中设置和获取线程优先级的方法

  • public final int getPriority():返回此线程的优先级
  • Public final setPriority(int newPriority):更改此线程的优先级

案例一:

public class MyThread extends Thread {
    //无参构造
    public MyThread(){

    }

    //带参构造
    public MyThread(String name){
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
        //Thread(String name)
        MyThread m1 = new MyThread("高铁");
        MyThread m2 = new MyThread("飞机");
        MyThread m3 = new MyThread("汽车");

        //public final int getPriority():返回此线程的优先级
        //线程默认优先级:5
        System.out.println(m1.getPriority());
        System.out.println(m2.getPriority());
        System.out.println(m3.getPriority());

    }
}

控制台:
在这里插入图片描述


案例二:

public class MyThreadDemo {
    public static void main(String[] args) {
        //Thread(String name)
        MyThread m1 = new MyThread("高铁");
        MyThread m2 = new MyThread("飞机");
        MyThread m3 = new MyThread("汽车");

        //Public final setPriority(int newPriority):更改此线程的优先级
        //IllegalArgumentException:非法参数异常(线程优先级不在允许的范围内)
        //线程优先级范围(1-10)
        m1.setPriority(1);
        m2.setPriority(6);
        m3.setPriority(4);

        m1.start();
        m2.start();
        m3.start();
        
        //public final int getPriority():返回此线程的优先级
        //线程默认优先级:5
        System.out.println(m1.getPriority());
        System.out.println(m2.getPriority());
        System.out.println(m3.getPriority());
        
        //线程优先级:最小、默认、最大
        System.out.println(Thread.MIN_PRIORITY);
        System.out.println(Thread.NORM_PRIORITY);
        System.out.println(Thread.MAX_PRIORITY);

    }
}

控制台:
在这里插入图片描述


结论:线程优先级高,并不是指每次都跑在最前面,而是指获得CPU时间片几率高。


线程控制

在这里插入图片描述


sleep案例:

/**
 * @Author TeaBowl
 * @Date 2021/3/18 12:32
 * @Version 1.0
 */
public class ThreadSleep extends Thread{
    //无参构造
    public ThreadSleep(){

    }

    //带参构造
    public ThreadSleep(String name){
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName() + ":" + i);
            try {
                //每执行一次输出,线程休眠一秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
/**
 * @Author TeaBowl
 * @Date 2021/3/18 12:33
 * @Version 1.0
 * static void sleep(long millis):使当前正在执行的线程停留(暂停执行)指定的毫秒数
 */
public class ThreadSleepDemo {
    public static void main(String[] args) {
        ThreadSleep t1 = new ThreadSleep("曹操");
        ThreadSleep t2 = new ThreadSleep("刘备");
        ThreadSleep t3 = new ThreadSleep("孙权");

        //3个线程同时执行,每秒休眠一次
        t1.start();
        t2.start();
        t3.start();
    }
}

控制台:

在这里插入图片描述


join案例:

public class ThreadSleep extends Thread{
    //无参构造
    public ThreadSleep(){

    }

    //带参构造
    public ThreadSleep(String name){
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName() + ":" + i);
            /*try {
                //每执行一次输出,线程休眠一秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
        }
    }
}
/**
 * @Author TeaBowl
 * @Date 2021/3/18 12:33
 * @Version 1.0
 * void join():等待当前线程死亡
 */
public class ThreadSleepDemo {
    public static void main(String[] args) {
        ThreadSleep t1 = new ThreadSleep("曹操");
        ThreadSleep t2 = new ThreadSleep("刘备");
        ThreadSleep t3 = new ThreadSleep("孙权");

        t1.start();
        try {
            //t1线程死亡之后,其他线程才有可能执行
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
        t3.start();
    }
}

控制台:

在这里插入图片描述


setDeamon案例:

/**
 * @Author TeaBowl
 * @Date 2021/3/18 12:32
 * @Version 1.0
 */
public class ThreadSleep extends Thread{
    //无参构造
    public ThreadSleep(){

    }

    //带参构造
    public ThreadSleep(String name){
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}
/**
 * @Author TeaBowl
 * @Date 2021/3/18 12:33
 * @Version 1.0
 * void setDaemon(boolean on):将此线程标记为守护线程,当运行的线程都是守护线程时,java虚拟机将退出
 */
public class ThreadSleepDemo {
    public static void main(String[] args) {
        ThreadSleep t1 = new ThreadSleep("关羽");
        ThreadSleep t2 = new ThreadSleep("张飞");

        //设置主线程为刘备
        //currentThread():返回正在执行的线程对象的引用,静态方法。
        Thread.currentThread().setName("刘备");

        //设置关羽和张飞为刘备的守护线程
        // 如果刘备挂了,守护线程很快也得挂
        t1.setDaemon(true);
        t2.setDaemon(true);

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

        //输出线程刘备
        for (int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

控制台:

在这里插入图片描述


线程生命周期

在这里插入图片描述

方式2:实现Runnable接口

  • 定义一个类MyRunnable实现Runnable接口。
  • 在MyRunnable类中重写run()方法。
  • 创建MyRunnable类的对象。
  • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数。
  • 启动线程。

案例:

/**
 * @Author TeaBowl
 * @Date 2021/3/18 16:36
 * @Version 1.0
 */
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}
/**
 * @Author TeaBowl
 * @Date 2021/3/18 16:38
 * @Version 1.0
 */
public class MyRunnableDemo {
    public static void main(String[] args) {

        MyRunnable myRunnable = new MyRunnable();

        //创建Thread类的对象,把MyRunnable对象作为构造方法的参数。
        //Thread(Runnable r)
        //Thread(Runnable r,String name):给线程设置名称
        Thread t1 = new Thread(myRunnable,"李白");
        Thread t2 = new Thread(myRunnable,"苏烈");

        //启动线程
        t1.start();
        t2.start();

    }
}

控制台:

在这里插入图片描述

相比继承Thread类,实现Runnable接口的好处

  • 避免了java单继承的局限性
  • 适合多个相同程序代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想。

线程同步

案例:卖票

在这里插入图片描述

/**
 * @Author TeaBowl
 * @Date 2021/3/24 14:30
 * @Version 1.0
 * 要执行的任务
 */
public class SellTicket implements Runnable{

    //是设置票数为100
    private int tickets = 100;

    @Override
    public void run() {
        //循环执行卖票
        while (true){
            //票数>0时,就卖票
            if (tickets>0){
                //模拟出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
                //出售之后,总票数-1
                tickets--;
            }
        }
    }
}
/**
 * @Author TeaBowl
 * @Date 2021/3/24 14:26
 * @Version 1.0
 */
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();
    }
}

控制台:

在这里插入图片描述

在这里插入图片描述

如图,出现了几个问题。

在这里插入图片描述

1.重复卖票
2.漏卖(卖出第100张接着却是卖第97张)
3.负数票(第-1张)

分析:

在这里插入图片描述
在这里插入图片描述

卖票案例数据安全问题的解决。

在这里插入图片描述

同步代码块。

在这里插入图片描述

重复代码块加锁(同一把锁)。

@Override
    public void run() {
        //循环执行卖票
        while (true){
            //重复代码块加锁(同一把锁)
            synchronized (obj){
                //票数>0时,就卖票
                if (tickets>0){
                    //模拟出票时间
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
                    //出售之后,总票数-1
                    tickets--;
                }
            }
        }
    }

控制台输出正常。

窗口1正在出售第100张票
窗口1正在出售第99张票
窗口1正在出售第98张票
窗口1正在出售第97张票
……
窗口1正在出售第5张票
窗口1正在出售第4张票
窗口1正在出售第3张票
窗口1正在出售第2张票
窗口1正在出售第1张票
同步代码块锁的分析

在这里插入图片描述


同步方法

在这里插入图片描述

案例

/**
 * @Author TeaBowl
 * @Date 2021/3/24 14:30
 * @Version 1.0
 */
public class SellTicket implements Runnable {
    private static int tickets = 100;
    //private Object obj = new Object();
    private int x = 0;

    @Override
    public void run() {
        while (true) {
            if (x % 2 == 0) {
                synchronized (SellTicket.class) {
                    //票数>0时,就卖票
                    if (tickets > 0) {
                        //模拟出票时间
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                        //出售之后,总票数-1
                        tickets--;
                    }
                }
            } else {
                sellTiket();
            }
            x++;
        }
    }

    //同步方法
    private static synchronized void sellTiket() {
        if (tickets > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
            tickets--;
        }
    }
}
/**
 * @Author TeaBowl
 * @Date 2021/3/24 14:26
 * @Version 1.0
 */
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();
    }
}

控制台输出正常:

窗口1正在出售第100张票
窗口1正在出售第99张票
窗口1正在出售第98张票
窗口1正在出售第97张票
……
窗口1正在出售第5张票
窗口1正在出售第4张票
窗口1正在出售第3张票
窗口1正在出售第2张票
窗口1正在出售第1张票
线程安全的类

在这里插入图片描述

线程不安全的集合类转为线程安全的集合类
/**
 * @Author TeaBowl
 * @Date 2021/3/24 16:45
 * @Version 1.0
 */
public class ThreadDemo {
    public static void main(String[] args) {
        //static <T> List<T> synchronizedList(List<T> list):返回由指定列表支持的同步(线程安全)列表
        List<String> list = Collections.synchronizedList(new ArrayList<>());
    }
}

Lock锁

在这里插入图片描述

案例

/**
 * @Author TeaBowl
 * @Date 2021/3/24 14:30
 * @Version 1.0
 */
public class SellTicket implements Runnable {
    private static int tickets = 100;
    //Lock是个接口,使用它的无参构造方法ReentrantLock()创建Lock锁对象
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try{
                lock.lock();
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            }finally {
                lock.unlock();
            }
        }
    }
}
/**
 * @Author TeaBowl
 * @Date 2021/3/24 14:26
 * @Version 1.0
 */
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();
    }
}

控制台:

窗口1正在出售第100张票
窗口1正在出售第99张票
窗口1正在出售第98张票
窗口1正在出售第97张票
……
窗口1正在出售第5张票
窗口1正在出售第4张票
窗口1正在出售第3张票
窗口1正在出售第2张票
窗口1正在出售第1张票

生产者和消费者模式

在这里插入图片描述

在这里插入图片描述
生产者和消费者案例:
在这里插入图片描述

生产者类:

/**
 * @Author TeaBowl
 * @Date 2021/3/24 20:22
 * @Version 1.0
 * 生产者类:送奶工
 */
public class Producer implements Runnable {
    //定义成员变量:牛奶箱
    private Box box;

    //定义带参构造方法
    public Producer(Box box) {
        this.box = box;
    }

    //生产者执行的操作:生产5瓶牛奶
    //i要传给奶箱类Box里面的milk
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            box.put(i);
        }
    }
}

消费者类:

/**
 * @Author TeaBowl
 * @Date 2021/3/24 20:23
 * @Version 1.0
 * 消费者类:用户
 */
public class Customer implements Runnable {
    //创建成员变量:牛奶箱
    private Box box;

    //创建带参构造方法
    public Customer(Box box) {
        this.box = box;
    }

    //消费者执行的操作
    @Override
    public void run() {
        while (true){
            box.get();
        }
    }
}

奶箱类:

/**
 * @Author TeaBowl
 * @Date 2021/3/24 20:16
 * @Version 1.0
 * 奶箱类
 */
public class Box {
    //定义变量:用来表示第几瓶牛奶
    private int milk;
    //定义成员变量,表示牛奶箱的状态
    private Boolean state = false;

    //生产者操作
    public synchronized void put(int milk) {
        //如果有牛奶,等待消费
        if (state){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果没有牛奶,就生产牛奶
        this.milk = milk;
        System.out.println("送奶工将第" + this.milk + "瓶牛奶放入牛奶箱");

        //生产完之后,更改奶箱状态
        state = true;
        
        //唤醒其他等待的线程
        notifyAll();
    }

    //消费者操作
    public synchronized void get() {
        //如果没有牛奶,等待生产
        if (!state){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果有牛奶,消费牛奶
        System.out.println("用户拿到第" + this.milk + "瓶牛奶");

        //消费完之后,更改奶箱状态
        state = false;
        
        //唤醒其他等待的线程
        notifyAll();
    }
}

测试类:

/**
 * @Author TeaBowl
 * @Date 2021/3/24 20:15
 * @Version 1.0
 * 生产者和消费者案例,测试类
 */
public class BoxDemo {
    public static void main(String[] args) {
        //创建奶箱对象
        Box box = new Box();
        //创建生产者对象,参数:牛奶箱
        Producer producer = new Producer(box);
        //创建消费者对象,参数:牛奶箱
        Customer customer = new Customer(box);

        //创建生产者线程
        Thread t1 = new Thread(producer);
        //创建消费者线程
        Thread t2 = new Thread(customer);

        //启动线程
        t1.start();
        t2.start();
    }
}

控制台:

送奶工将第1瓶牛奶放入牛奶箱
用户拿到第1瓶牛奶
送奶工将第2瓶牛奶放入牛奶箱
用户拿到第2瓶牛奶
送奶工将第3瓶牛奶放入牛奶箱
用户拿到第3瓶牛奶
送奶工将第4瓶牛奶放入牛奶箱
用户拿到第4瓶牛奶
送奶工将第5瓶牛奶放入牛奶箱
用户拿到第5瓶牛奶
posted @ 2021-03-16 17:42  茶碗儿  阅读(122)  评论(0)    收藏  举报
点击这里跳转到-->【茶碗儿CSDN】