并发与并行、进程与线程、同步

并发与并行

在这里插入图片描述

进程

进入到内存的程序叫进程
在这里插入图片描述

线程

线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也称之为多线程程序。在这里插入图片描述

线程的调度方式

* 线程的调度方式:
	1. 分时调度
		所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
	2. 抢占式调度
		优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度
/*
    主线程:执行主(main)方法的线程

    单线程程序:java程序中只有一个线程
    执行从main方法开始,从上到下依次执行
 */
public class Demo01MainThread {
    public static void main(String[] args) {
        Person p1 =new Person("小强");
        p1.run();

        System.out.println(0/0);
        //ArithmeticException: / by zero
/*
小强-->0
	Exception in thread "main" java.lang.ArithmeticException: / by zero
小强-->1
	at com.itheima.demo05.Thread.Demo01MainThread.main(Demo01MainThread.java:14)
小强-->2
小强-->3
小强-->4
小强-->5
小强-->6
小强-->7
小强-->8
小强-->9
小强-->10
小强-->11
小强-->12
小强-->13
小强-->14
小强-->15
小强-->16
小强-->17
小强-->18
小强-->19
*/
//由于是单线程,所以后面的语句就不执行了,这就是单线程的弊端
        Person p2 =new Person("旺财");
        p1.run();
    }
}
public class Person {
    private String name;

    public void run() {
        //定义循环,执行20次
        for (int i = 0; i < 20; i++) {
            System.out.println(name + "-->" + i);
        }
    }

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

在这里插入图片描述

线程的创建方式

创建方式一:创建继承Thread类的线程类,重写父类中的run方法,start线程
* 创建多线程程序的第一种方式:创建Thread类的子类(创建一个类,继承Thread类)
* java.lang.Thread类:是表述线程的类,我们想要实现多线程,就必须继承Thread类

* 实现步骤:
	1.创建一个Thread类的子类
	2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
	3.创建Thread类的子类对象
	4.调用Thread类中的 start方法,开启新的线程,执行run方法
	    void start() 使该线程开始执行;Java虚拟机调用该线程的run方法。
            结果是两个线程并发地运行;当前线程(从调用返回给 start方法)和另一个线程(执行其run方法)
            多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
    Java程序属于抢占式调度,哪个线程的优先级高,哪个线程就优先执行;同一个优先级,随机选择一个执行
public class Demo01Thread {
    public static void main(String[] args) {
        //3.创建Thread类的子类对象
        MyThread mt = new MyThread();
        //4.调用Thread类中的 start方法,开启新的线程,执行run方法
        mt.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("main" + i);
        }
    }
}
//1.创建一个Thread类的子类
public class MyThread extends Thread{
    //2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run" + i);
        }
    }
}

输出: main0 run0 run1 main1 run2 main2 run3 main3 run4 main4 run5 run6 run7 run8 run9 run10 run11 run12 run13 run14 run15 run16 run17 run18 run19 main5 main6 main7 main8 main9 main10 main11 main12 main13 main14 main15 main16 main17 main18 main19 Process finished with exit code 0
两个线程同时进行,谁抢夺到CPU的资源就执行谁在这里插入图片描述
在这里插入图片描述

Thread类中的方法

获取线程名称的方法

* 获取线程的名称:
1. 使用Thread类中的方法getName()
	String getName() 返回该线程的名称。
2. 可以先获取到当前正在执行的线程,使用线程中的方法getName
	static Thread currentThread() 返回对当前正在执行的线程对象的引用
//定义一个Thread类的子类
public class MyThread extends Thread {
    //重写Thread类中的run方法,设置线程任务

    @Override
    public void run() {
        //获取线程的名称
       /* String name = getName();
        System.out.println(name);*/
/*

        Thread t = Thread.currentThread();
//        System.out.println(t);


        String name = t.getName();
        System.out.println(name);
*/
        //利用链式编程代替以上代码
        System.out.println(Thread.currentThread().getName());

    }
}
/*
    线程名称:
        主线程:main
        新线程:Thread-0,Thread-1,Thread-2,Thread-3
 */
public class Demo01GetThreadName {
    public static void main(String[] args) {
        //创建Thread类的子类对象
        MyThread mt = new MyThread();
        //调用start()方法,开启新县城,执行run方法
        mt.start();

        new MyThread().start();
        new MyThread().start();
    }
}

设置线程名称的方法:

/*
    设置线程的名称:(了解)
        1. 使用Thread类中的方法setName(名字)
             void setName(String name) 改变线程名称,使之与参数name相同。
        2. 创建一个带参数的构造方法,参数传递线程的名称;调用父类的代餐构造方法,把线程名称传递给父类,让父类(Thread)给子类线程起一个名字
            Thread(String name)分配新的Thread对象。

 */
public class MyThread extends Thread {

    //创建一个空参数的、一个带参数的构造方法
    public MyThread() {

    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        //获取线程的名称
        System.out.println(Thread.currentThread().getName());
    }
}

public class Demo01SetThreadName {
    public static void main(String[] args) {
        //开启多线程
        MyThread mt = new MyThread();
//        mt.setName("小强"); //设置线程名称
        mt.start();

        //开启多线程方式2
        new MyThread("旺财").start();
    }
}

sleep:

/*
    public static void sleep(long millis):强制该正在执行的线程以指定的毫秒数暂停(暂时停止执行)
    毫秒数结束之后,线程继续执行
 */
public class Demo01Sleep {
    public static void main(String[] args) {
        //模拟秒表
        for (int i = 0; i < 60; i++) {
            System.out.println(i);

            //使用Thread类的sleep方法让程序睡眠1秒钟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
创建线程方式二 :实现Runnable接口
* 创建多线程程序的第二种方式:实现Runnable接口
    java.long.Runnable
        Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run的无参数方法。
    java.lang.Thread类的构造方法
        Thread(Runnable target) 分配新的Thread对象。
        Thread(Runnable target, String name) 分配新的 Thread对象。

* 实现步骤:
    1.创建一个Runnable接口的实现类
    2.在实现类中重写Runnable接口的run方法,设置线程任务
    3.创建一个Runnable接口的实现类对象
    4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
    5.调用Thread类中的start方法,开启新的线程执行run方法
//1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable{

//2.在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}
public class Demo01Runnable {
    public static void main(String[] args) {
        //3.创建一个Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t = new Thread(run);
        //5.调用Thread类中的start方法,开启新的线程执行run方法
        t.start();

        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}
* 实现Runnable接口创建多线程程序的好处:
	1. 避免了单继承的局限性
		一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
		实现了Runnable接口,还可以继承其他的类,实现其他的接口
	2.增强了程序的扩展性,降低了程序的耦合性(解耦)
		实现Runnable接口的方式,把设置线程任务和开启线程进行了分离(解耦)
		实现类中,重写了run方法:用来设置线程任务
		创建Thread类对象,调用star方法:用来开启新线程。
匿名内部类方式实现多线程程序
* 匿名内部类方式实现线程的创建
    匿名:没有名字
    内部类:写在其他类内部的类

* 匿名内部类作用:简化代码
	把子类继承父类,重写父类的方法,创建子类对象合成一步完成
	把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
	
* 匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

* 格式:
	new 父类/接口(){
	    重复父类/接口中的方法
	};
public class Demo01InnerClassThread {
    public static void main(String[] args) {
        //方式一:线程的父类是Thread
        //new MyThread().start();
        new Thread() {
            //重写run方法,设置线程任务
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "程序员1");
                }
            }
        }.start();

        //方式二:线程的接口Runnable
        //Runnable r = new RunnableImpl();//多态
        Runnable r = new Runnable() {
            //重写run方法
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "程序员2");
                }
            }
        };
        new Thread(r).start();

        //简化接口的方式
        new Thread(new Runnable() {
            //重写run方法
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "传程序员呢3");
                }
            }
        }).start();
    }
}

线程安全

在这里插入图片描述

/*
    实现卖票案例
 */
public class RunnableImpl implements Runnable{
    //定义一个多线程共享的票源
    private int ticket = 100;

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复进行
        while (true){
            //先判断票是否存在
            if (ticket > 0){
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //票存在,卖票 ticket--
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
        }
    }
}
/*
    模拟卖票案例
    创建3个线程,同时开启,对共享的票进行出售
 */
public class Demo01Ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

在这里插入图片描述

使用同步代码块解决线程安全问题
/*
    模拟卖票案例
    创建3个线程,同时开启,对共享的票进行出售
 */
public class Demo01Ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();

    }
}
import com.sun.org.apache.xerces.internal.parsers.CachingParserPool;

import java.util.concurrent.SynchronousQueue;

/*
    卖票案例出现了线程安全问题
    卖出了不存在的票和重复的票

    解决线程安全问题的一种方案:使用同步代码块
    格式:
        Synchronized(锁对象){
            可能会出现线程安全问题的代码(访问了共享数据的代码)
        }
    注意:
        1.通过代码块中的锁对象,可以是任意的对象
        2.但是必须保证多个线程使用的锁对象必须是同一个
        3.锁对象作用:
            把同步代码块锁住,只让一个线程在同步代码块中执行
 */
public class RunnableImpl implements Runnable {
    //定义一个多线程共享的票源
    private int ticket = 100;

    //创建一个锁对象
    Object obj = new Object();

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复进行
        while (true) {
            //同步代码块
            synchronized (obj) {
                //先判断票是否存在
                if (ticket > 0) {
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                    ticket--;
                }
            }
        }
    }
}
同步方法
/*
    模拟卖票案例
    创建3个线程,同时开启,对共享的票进行出售
 */
public class Demo01Ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();

    }
}
/*
    卖票案例出现了线程安全问题
    卖出了不存在的票和重复的票

    解决线程安全问题的二种方案:使用同步方法
    使用步骤:
        1.把访问了共享数据的代码抽取出来放到一个方法中
        2.在方法上添加synchronized返回值类型 方法名(参数列表){
            可能会出现线程安全问题的代码(访问了共享数据的代码)
        }

 */
public class RunnableImpl implements Runnable {
    //定义一个多线程共享的票源
    private int ticket = 100;

    //创建一个锁对象
    Object obj = new Object();

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复进行
        while (true) {
            //同步代码块
            payTicked();
            }
        }

        /*
            定义一个同步方法
            同步方法也会把方法内部的代码锁住
            只让一个进程执行
            同步方法的锁对象是谁
            就是实现类对象 new RunnableImpl
            也就是this
         */
        public synchronized void payTicked(){
            if (ticket > 0) {
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //票存在,卖票 ticket--
                System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                ticket--;

        }

    }
}
lock锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/*
    卖票案例出现了线程安全问题
    卖出了不存在的票和重复的票

    解决线程安全问题的三种方案:使用Lock锁
    java.util.concurrent.locks.Lock接口
    Lock 实现提供了比使用synchronized方法和语句可获得更广泛的锁定操作。
    Lock接口中的方法:
        void lock() 获取锁
        void unlock() 释放锁
    java.util.concurrent.locks.ReentrantLock implements Lock接口

* 使用步骤:
        1.在成员位置创建一个ReentrantLock对象
        2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
        3.在可能会出现安全问题的代码前调用Lock接口中的方法unLock释放锁

 */
public class RunnableImpl implements Runnable {
    //定义一个多线程共享的票源
    private int ticket = 100;

    //1. 在成员位置创建一个ReentrantLock对象
    Lock l = new ReentrantLock();

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复进行
        while (true) {
            //2. 在可能出现安全问题的代码前调用Lock接口中的方法lock获取锁
            l.lock();

    /*
        定义一个同步方法
        同步方法也会把方法内部的代码锁住
        只让一个进程执行
        同步方法的锁对象是谁
        就是实现类对象 new RunnableImpl
        也就是this
     */

            if (ticket > 0) {
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //                    票存在,卖票 ticket--
                System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                ticket--;

            }
            //3.在可能会出现安全问题的代码前调用Lock接口中的方法unLock释放锁
            l.unlock();
        }
    }
}
package com.itheima.demo09.Lock;

/*
    模拟卖票案例
    创建3个线程,同时开启,对共享的票进行出售
 */
public class Demo01Ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}
等待与唤醒案例的实现
/*
    等待唤醒案例:线程之间的通信
        创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WATTING状态(无限等待)
        创建一个老板线程(生产者):花5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子

    注意:
        顾客和老板线程必须使用同步代码包裹起来,保证等待和唤醒只有一个在执行
        同步使用的锁对象必须是唯一的
        只有锁对象才能调用wait和notify方法

    Object类中的方法
    void wait()
        在其他线程调用此对象的 notify() 方法域 notifyAll() 方法前,导致当前线程等待,
    void notify()
        唤醒再次对象监视器上等待的单个线程。
        会继续执行wait方法之后的代码
 */
public class DEmo01WaitAndNotify {
    public static void main(String[] args) {
        //创建锁对象,保证唯一
        Object obj = new Object();
        //创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
                //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                synchronized (obj){
                    System.out.println("告知老板要的包子的种类和数量");
                    //调用wait方法,放弃cpu的执行,进入到WAITTING状态(无限等待)
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }


                }
            }
        }.start();

        //创建老板线程(生产者)
        new Thread(){
            @Override
            public void run() {
                //花5秒做包子
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //唤醒之后执行的代码
                System.out.println("包子已经做好了,开吃。");

                //保证等待和唤醒的线程只有一个执行,需要使用同步技术
                synchronized (obj){
                    System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
                    //做好包子之后,调用notify方法,唤醒顾客吃包子
                    obj.notify();
                }
            }
        }.start();
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值