多线程..

并发:只能够让多个任务在逻辑上交替执行的程序设计

并行:指物理上可以同时执行

进程:一个正在运行的程序(包含程序运行的环境、程序上下文)

线程:线程是进程中的一个执行单元

主线程(Java):执行主方法的线程

单线程:就是一条一条代码的执行。

多线程(Java):Java中允许使用多线程,允许并发的执行多个线程

如何实现多线程?

Thread:用来开启多线程
*
* 1.新建一个类继承Thread类
* 2.重写父类的run方法,在run方法里面写自己的逻辑
* 3.创建子类对象
* 4.开启多线程,调用子类对象的start()方法
*  JVM会帮我们调用run()方法
package Demo02_创建线程方式一;

public class Demo01 {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
        System.out.println("程序结束");
    }
}
class MyThread extends Thread{
    //run方法运行在子线程中
    @Override
    public void run() {
        System.out.println("MyThread run()被执行了");
    }
}

cpu执行程序
1.抢:谁抢到谁执行

2.分时:你一下我一下

package Demo02_创建线程方式一;

public class Demo02 {
    /**
     * main方法的线程如果先抢到CPU
     * @param args
     */
    public static void main(String[] args) {
        MyThread2 thread2 = new MyThread2();
        thread2.setDaemon(true);//将线程变为守护线程
        thread2.start();
        //开启多线程
        System.out.println("程序结束");
    }
}
//正常构建的线程都是前台线程
class MyThread2 extends Thread{
    public void run(){
        while (true) {
            System.out.println("MyThread2 的run方法被执行了");
        }
    }
}

线程模型

(java中不分子线程和主线程的)

主线程会等待子线程执行完毕吗?

线程的方法

获取线程名称:setName和getName方法

package Demo02_线程的方法;

public class Demo01 {
    public static void main(String[] args) {
        //获取线程名称
        //获取当前的线程对象
        Thread thread = Thread.currentThread();
        System.out.println("当前线程的名称:"+thread.getName());
        MyThread2 myThread2 = new MyThread2();
        myThread2.setName("子线程1");
        myThread2.start();
    }
}
class MyThread2 extends Thread{
    public void run(){
            System.out.println("MyThread2 的run方法被执行了");
            System.out.println("MyThread2 获取线程名称 :"+getName());
    }
}

睡眠方法:Thread.sleep

package Demo02_线程的方法;

public class Demo02 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("我是执行程序");
        //单位:毫秒
        Thread.sleep(5000);
        System.out.println("我结束了");
    }
}

线程创建方式二:

package Demo04_线程创建方式二;

/**
 * 继承Tread类 创建线程方式的缺点:
 * 1.Java中只支持单继承
 *
 * 通过接口方式创建线程 创建线程方式二:
 * 1.新建类,实现Runnable
 * 2.实现run方法,编写程序逻辑
 * 3.创建Thread对象,并通过构造方法传入MyRunnable的对象
 * 4.开启线程
 */
public class Demo01 {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
        System.out.println("程序输入");
    }
}
class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("我被执行了");
    }
}

模拟买票程序

使用继承的方式:缺点:  1.java只支持单继承   2.票数不共享

package Demo05_多线程创建方式的区别;

public class Demo01 {
    public static void main(String[] args) {
        TicketWindow t1 = new TicketWindow();
        t1.start();
        TicketWindow t2 = new TicketWindow();
        t2.start();
        TicketWindow t3 = new TicketWindow();
        t3.start();
    }
}
class TicketWindow extends Thread{
    //票数
    private int tickets  = 100;

    public void run(){
        while (true){
            //票数大于0就继续卖票
            if(tickets > 0){
                System.out.println("正在售卖第"+tickets--+"张票");
            }
        }
    }
}
实现接口的方式创建线程,模拟卖票程序
package Demo05_多线程创建方式的区别;

/**
 * 实现接口的方式创建线程,模拟卖票程序
 * 1.解决Java单继承问题
 * 2.票数共享了(多个线程共享数据)
 */
public class Demo02 {
    public static void main(String[] args) {
        //多个Thread对象共享了同一个runnable对象
        MyRunnable runnable = new MyRunnable();
        Thread t1 = new Thread(runnable);
        t1.start();
        Thread t2 = new Thread(runnable);
        t2.start();
        Thread t3 = new Thread(runnable);
        t3.start();
    }
}
class MyRunnable implements Runnable{
    private int tickets = 100;

    @Override
    public void run() {
        while (true){
            if(tickets > 0){
                System.out.println("在卖第"+tickets+"张卖票");
            }
        }
    }
}

创建线程的代码写法

package Demo06_创建线程代码写法;
/**
 * 创建线程方式1
 *      继承
 * 创建线程方式2
 *      实现
 */
public class Demo01 {
    public static void main(String[] args) {
        //匿名内部类(接口)
        Thread t1 = new Thread(){
            @Override
            public void run() {
                System.out.println("我是线程1");
            }
        };
        t1.start();
        
        //匿名内部类(接口)
        Runnable r = new Runnable(){
            @Override
            public void run() {
                System.out.println("我是线程2");
            }
        };
        Thread t2 = new Thread(r);
        t2.start();

        //写法3
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是线程3");
            }
        }).start();
    }
}

线程安全问题

什么是线程安全问题?

代码在运行过程中,如果一个共享的变量被其他线程修改,那么对于当前线程来说就发生了线程安全问题。

单线程不会发生线程安全问题。如果变量只被访问,不修改,不会发生线程安全问题。

解决线程安全问题:让可能发生线程安全的代码在某个时刻只允许一个线程访问。

即,线程同步

package demo07_线程同步;

/**
 * 实现接口的方式创建线程,模拟卖票程序
 * 1.解决Java单继承问题
 * 2.票数共享了(多个线程共享数据(票数))
 * 线程同步
 *      同步代码块
 *      synchronized (被锁对象){
 *             //可能出现线程安全的代码
 *         }
 */
public class Demo01 {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
         Thread t1 = new Thread(runnable);
        t1.start();
        Thread t2 = new Thread(runnable);
        t2.start();
        Thread t3 = new Thread(runnable);
        t3.start();
    }
}
class MyRunnable implements Runnable{
    //票数
    private int tickets  = 100;

    //锁对象可以是任意类型的对象
    //但是多个线程的锁对象必须是同一个
    Object obj = new Object();

    public void run(){
        while (true){
            synchronized (obj){
                //票数大于0就继续卖票
                if(tickets > 0){
                    try {
                        //让问题更容易显现
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("正在售卖第"+tickets--+"张票");
                }else{
                    break;
                }

            }
        }
    }
}

同步方法优化卖票程序

测试类:

package demo08_同步方法;

/**
 * 整个方法的代码都会出现线程安全问题,那么可以使用同步方法
 *
 * 同步方法
 * public synchronized void run(){}
 */
public class Demo {
    public static void main(String[] args) {
        //多个Thread对象共享了同一个MyRunnable对象。
        MyRunnable runnable = new MyRunnable();
        Thread t1 = new Thread(runnable);
        t1.start();
        Thread t2 = new Thread(runnable);
        t2.start();
        Thread t3 = new Thread(runnable);
        t3.start();
    }
}
class MyRunnable implements Runnable {
    TicketsPool pool = new TicketsPool();

    public void run(){
        while (true){
            int ticket = pool.getTicket();
            if(ticket != 0){
                System.out.println("正在售卖第"+ticket+"张票");
            }else{
                break;
            }
        }
    }
}

 TicketsPool类

package demo08_同步方法;

public class TicketsPool {
    //票数
    private int tickets = 100;

    //获取票的方法
    public synchronized int getTicket()  {
        try {
            if(tickets > 0){
                Thread.sleep(1);
                return tickets--;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 0;
    }
}

同步代码块

synchronized (锁对象){
    //可能出现线程安全问题的代码
}
//释放锁:同步代码块执行完毕

同步方法

public synchronized void run(){
}
//同步方法的锁对象:this
//释放锁:同步方法执行完毕

Look锁

程序员掌控上锁和解锁的时机

package demo09_lock对象;

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

/**
 * lock锁
 */
public class Demo01 {
    public static void main(String[] args) {
        //多个Thread对象共享了同一个MyRunnable对象
        MyRunnable runnable = new MyRunnable();
        Thread t1 = new Thread(runnable);
        t1.start();
        Thread t2 = new Thread(runnable);
        t2.start();
        Thread t3 = new Thread(runnable);
        t3.start();
    }
}
class MyRunnable implements Runnable{
    //票数
    private int tickets  = 100;
    //创建Lock对象
    Lock lock = new ReentrantLock();
    public void run(){
        while (true){
            //上锁
            lock.lock();
            try{
                if(tickets > 0){
                    try {
                        //让问题更容易显现
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("正在售卖第"+tickets--+"张票");
                }else{
                    break;
                }
            }finally {
                //无论如何都会释放锁
                lock.unlock();
            }
        }
    }
}

线程安全问题回顾

StringBuffer:线程同步的,线程安全的,效率低
StringBuilder:线程不同步,线程不安全,效率高
 ArrayList:线程不同步

 使用多线程操作StringBuilder
 使用多个线程,调用同一个StringBuilder的append方法,添加数据
 每个线程添加100个字符
 总共300个字符

 使用StringBuilder和ArrayList,因为这两线程不同步
 可能导致的问题:字符丢失(不一定会丢失反正可能会有一些奇奇怪怪的问题)
package demo10_线程安全问题回顾;

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 10; i++) {
            //线程1
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 100; j++) {
                        sb.append("A");
                    }
                    System.out.println("添加完成:"+sb.length());
                }
            }).start();
        }
        Thread.sleep(5000);
        System.out.println(sb.length());
    }
}

同步细节问题

如果线程1访问了append方法,那么线程2还能访问delete方法吗?

        不能,因为synchronized方法使用了java类的内置锁,即锁住的是方法所属对象本身,同一个锁某个时刻只能被一个执行线程所获取,因此其他线程都得等待锁的释放。

死锁

package demo11_死锁;

/**
 * 死锁:程序卡住,不会往下继续执行,也不会停止
 */
public class Demo01 extends Thread{
    int flag = 1;
    //锁对象
    static Object obj1 = new Object();
    static Object obj2 = new Object();
    public static void main(String[] args) {
        Demo01 d1 = new Demo01();
        d1.flag = 1;
        Demo01 d2 = new Demo01();
        d2.flag = 2;

        d1.start();
        d2.start();
    }
    public void run(){
        System.out.println("我被执行了");
        if(flag == 1){
            synchronized (obj1){
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //如果obj2被锁住了,这里会继续等待
                synchronized (obj2){
                    System.out.println("执行了线程1");
                }
            }
        }

        if(flag == 2){
            synchronized (obj2){

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (obj1){
                    System.out.println("执行了线程2");
                }
            }
        }
    }
}

线程通信

包子铺
        进货     setxxx
        卖出     getxxx
生产包子的线程
     不断(while(true))生产包子,添加到包子铺
消费包子的线程
     从包子铺中不断(while(true))取包子

协调生产包子和消费包子,包子没有生产好,那么消费者会取不到包子

如果没有包子,让消费者等待,等包子生产好再通知消费者

包子铺类

package demo12_线程通信;

import java.util.ArrayList;
/**
 * 包子铺
 *     进货   setxxx
 *     卖出   getxxx
 */
public class BreadStore {
    //存储包子
    ArrayList<String> breads = new ArrayList<>();
    /**
     * 设置包子
     */
    public synchronized void setBreads(String bread) {
        breads.add(bread);
        //添加包子后,唤醒等待的线程
        notifyAll();
    }
    /**
     * 获取包子
     */
    public synchronized String getBreads(){
        if(breads.isEmpty()){
            try {
                //如果包子没有好,让调用者等待
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        String bread = breads.get(0);
        breads.remove(bread);
        return bread;
    }
}

 get类

package demo12_线程通信;

/**
 * 获取包子
 */
public class GetThread extends Thread{
    BreadStore bs;
    public GetThread(BreadStore bs){
        this.bs = bs;
    }

    @Override
    public void run() {
        while (true){
            System.out.println("获取到的包子:"+bs.getBreads());
        }
    }
}

 set类

package demo12_线程通信;

/**
 * 不断生产包子
 */
public class SetThread extends Thread{
    BreadStore bs;
    public SetThread(BreadStore bs){
        this.bs = bs;
    }
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            bs.setBreads("a");
        }
    }
}

测视类

package demo12_线程通信;

/**
 * 测试类
 */
public class Demo {
    public static void main(String[] args) {
        BreadStore bs = new BreadStore();
        //启动线程
        SetThread set = new SetThread(bs);
        set.start();
        GetThread get = new GetThread(bs);
        get.start();

        //如果生产包子比较慢
        //有时候取到空的包子
    }
}

线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制

  1. 等待唤醒机制所涉及到的方法:
    1. wait():等待,将正在执行的线程释放其执行资格执行权,并存储到线程池中
    2. notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的
    3. notifyAll():唤醒全部:可以将线程池中的所有wait()线程都唤醒。

以上方法都定义在Object类中

其实,所谓唤醒的意思就是让线程具备执行资格。必须注意的是,这些方法都是在同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。

因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值