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

并发与并行

在这里插入图片描述

进程

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

线程

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

线程的调度方式

* 线程的调度方式:
	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
    评论
进程线程是操作系统中进行任务调度和资源分配的两个基本单位,它们在**资源分配、独立性以及调度切换**等方面有所区别。以下是具体分析: 1. **资源分配** - **进程**:进程是操作系统资源分配的基础单元,拥有独立的内存空间。每个进程至少有一个执行线程进程间的资源是相互独立的,拥有自己的地址空间。 - **线程**:线程是CPU调度的基础单元,可以共享属于其父进程的资源,如堆和方法区。线程相较于进程,系统在创建或切换时的负担更小,因此常被视为轻量级进程。 2. **独立性** - **进程**:能够独立运行,并在发生错误时互不影响,因此具备良好的容错性。 - **线程**:虽然共享相同进程的资源,但每个线程还是有自己独立的程序计数器、虚拟机栈和本地方法栈,使得线程在一定程度上也可独立执行。 3. **调度切换** - **进程**:由于进程间的独立性,切换时需要独立的资源空间,所以开销较大。 - **线程**:由于共享同一进程资源,切换成本较低,可实现更高效的上下文切换。 4. **系统开销** - **进程**:进程间资源的独立性导致其创建和撤销的系统开销较大。 - **线程**:因为共享进程资源,创建和撤销的系统开销较小。 5. **并发性能** - **进程**:可以在多核处理器上并行运行,提高应用的吞吐量。 - **线程**:由于共享进程资源,并行处理多个线程进程更加高效,尤其在IO密集型的应用中体现得更为明显。 6. **编程复杂性** - **进程**:进程间的通信(IPC)较为复杂,需要操作系统提供相应的机制进行支持。 - **线程**:线程间的通信相对简单,可以通过直接读写进程数据段的方式实现。 7. **架构设计** - **进程**:在设计上,进程模型更适合于那些需要独立运行、耦合度低的任务。 - **线程**:而线程模型则适用于紧密协作的任务,例如web服务器处理并发请求的情况。 8. **执行控制流** - **进程**:一个进程至少包含一个执行控制流,可以同时处理多个任务。 - **线程**:线程进程中的执行控制流,负责当前进程中的程序执行。 针对上述分析,提出以下几点建议: - 考虑系统的性能需求,是否需要高并发处理。 - 根据应用的特性选择适合的并行处理模式(进程线程)。 - 注意线程安全问题,合理使用同步机制以避免竞争条件。 - 考虑资源消耗和系统开销,以优化资源使用。 - 评估系统的可伸缩性,确保在高负载下也能保持良好的性能。 总的来说,多线程技术尤其适合IO密集型或者需要高并发处理的应用,而多进程则更适合计算密集型或者需要隔离和安全性的场景。在选择使用哪种方式时,开发者需要根据实际的应用需求和预期的系统负载来做出决策。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值