从零开始的"E"世界(J2SE)

来源:互联网 发布:最后的堡垒 知乎 编辑:程序博客网 时间:2024/05/06 09:56

JavaNote8

    • JavaNote8
      • 多线程
        • 1 并发和并行
        • 2 进程和线程
        • 3 多线程优势
        • 4 创建和启动线程
        • 5 线程同步
        • 5 线程通信
        • 6 线程的六大状态
        • 6 经典案例 生产者和消费者

1. 多线程

1.1 并发和并行

并行:指两个或多个事件在同一时刻点发生;
并发:指两个或多个事件在同一时间段内发生。


1.2 进程和线程

进程:进程是指一个内存中运行中的应用程序。每个进程都有自己独立的一块内存空间.
线程线程是指进程中的一个执行任务(控制单元),一个进程可以同时并发运行多个线程.

多进程:操作系统中同时运行的多个程序。
多线程:在同一个进程中同时运行的多个任务。

进程与线程的区别:
进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
线程:堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响的,又称为轻型进程或进程元。

注意:Java程序的进程(Java的一个程序运行在系统中)里至少包含主线程和垃圾回收线程(后台线程)。


1.3 多线程优势

  • 进程之前不能共享内存,而线程之间共享内存(堆内存)则很简单。
  • 系统创建进程时需要为该进程重新分配系统资源,创建线程则代价小很多,因此实现多任务并发时,多线程效率更高.
  • Java语言本身内置多线程功能的支持,而不是单纯第作为底层系统的调度方式,从而简化了多线程编程.

1. 4 创建和启动线程

创建和启动线程,有两种方式:
方式1:继承Thread类;
方式2:实现Runnable接口;

线程类(java.lang.Thread): Thread类和Thread的子类才能称之为线程类.

方式1:继承Thread类:
步骤:
1:定义一个类A**继承于java.lang.Thread类**.
2:在A类中覆盖Thread类中的run方法.
3:我们在run方法中编写需要执行的操作—->run方法里的,线程执行体.
4:在main方法(线程)中,创建线程对象,并启动线程.
创建线程类对象: A类 a = new A类();
调用线程对象的start方法: a.start();//启动一个线程

//播放音乐的线程类class MusicThread extends Thread{    public void run() {        for(int i = 0; i < 50; i++){            System.out.println("播放音乐" + i);        }    }}//方式1:继承Thread类public class ExtendsThreadDemo {    public static void main(String[] args) {        //主线程:运行游戏        for(int i = 0; i < 50; i++){            System.out.println("打游戏" + i);            if(i == 10){                //创建线程对象,并启动线程                MusicThread t = new MusicThread();                t.start();//不能调用run方法            }        }    }}

注意:千万不要调用run方法,如果调用run方法好比是对象调用方法,依然还是只有一个线程,并没有开启新的线程.


方式2:实现Runnable接口;
步骤:
1):定义一个类A实现于java.lang.Runnable接口,注意A类不是线程类.
2:在A类中覆盖Runnable接口中的run方法.
3:我们在run方法中编写需要执行的操作—->run方法里的,线程执行体.
4:在main方法(线程)中,创建线程对象,并启动线程.
创建线程类对象: Thread t = new Thread(new A());
调用线程对象的start方法: t.start();

//播放音乐的实现类class MusicRunnableImple implements java.lang.Runnable{    public void run() {        for(int i = 0; i < 50; i++){            System.out.println("播放音乐" + i);        }    }}//方式2:实现Runnable接口public class ImplementsRunnableDemo {    public static void main(String[] args) {        //主线程:运行游戏                for(int i = 0; i < 50; i++){                    System.out.println("打游戏" + i);                    if(i == 10){                        //创建线程对象,并启动线程                        Thread t = new Thread(new MusicRunnableImple());                        t.start();                    }                }    }}

1. 5 线程同步

多线程并发访问同一个资源对象的时候,可能出现线程不安全的问题.
解决方案:
方式1:同步代码块
方式2:同步方法
方式3:锁机制(Lock)


同步代码块

//同步代码块语法:synchronized(同步锁){     //需要同步操作的代码}

同步锁:
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制.
同步监听对象/同步锁/同步监听器/互斥锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
Java程序运行使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为同步监听对象.
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着.


同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着.

同步方法synchronized public void  doWork(){     ///TODO}

同步锁是谁:
对于非static方法,同步锁就是this.
对于static方法,我们使用当前方法所在类的字节码对象.

synchronized的好与坏:
好处:保证了多线程并发访问时的同步操作,避免线程的安全性问题.
缺点:使用synchronized的方法/代码块的性能比不用要低一些.
建议:尽量减小synchronized的作用域.


同步锁(Lock):
Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象.

class X {   private final ReentrantLock lock = new ReentrantLock();   // ...   public void m() { //m方法是需要同步操作的方法     lock.lock();  // block until condition holds     try {       // ... method body     } finally {       lock.unlock()     }   } }

1. 5 线程通信

1.线程通信-wait和notify方法
注意:线程通信:不是传输数据,而是控制线程状态.

java.lang.Object类提供类两类用于操作线程通信的方法.
wait():执行该方法的线程对象释放同步锁,JVM把该线程存放到等待池中,等待其他的线程唤醒该线程.
notify():执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待.
notifyAll():执行该方法的线程唤醒在等待池中等待的所有的线程,把线程转到锁池中等待.
注意:上述方法只能被同步监听锁对象来调用,否则报错IllegalMonitorStateException


线程通信-使用Lock和Condition接口:
从Java5开始,可以:
1):使用Lock机制取代synchronized 代码块和synchronized 方法.
2):使用Condition接口对象的await,signal,signalAll方法取代Object类中的wait,notify,notifyAll方法.


1. 6 线程的六大状态

线程的6大状态

  • 1:新建状态(new):使用new创建一个线程对象,仅仅在堆中分配内存空间,在调用start方法之前.新建状态下,线程压根就没有启动,仅仅只是存在一个线程对象而已.
    Thread t = new Thread();//此时t就属于新建状态

当新建状态下的线程对象调用了start方法,此时从新建状态进入可运行状态.线程对象的start方法只能调用一次,否则报错:IllegalThreadStateException.

  • 2:可运行状态(runnable):分成两种状态,ready和running。分别表示就绪状态和运行状态。

    • 就绪状态:线程对象调用start方法之后,等待JVM的调度(此时该线程并没有运行).
    • 运行状态:线程对象获得JVM调度,如果存在多个CPU,那么允许多个线程并行运行.
      这里写图片描述
  • 3:阻塞状态(blocked):正在运行的线程因为某些原因放弃CPU,暂时停止运行,就会进入阻塞状态.
    此时JVM不会给线程分配CPU,直到线程重新进入就绪状态,才有机会转到运行状态.
    阻塞状态只能先进入就绪状态,不能直接进入运行状态.
    阻塞状态的两种情况:

    • 1:当A线程处于运行过程时,试图获取同步锁时,却被B线程获取.此时JVM把当前A线程存到对象的锁池中,A线程进入阻塞状态.
    • 2):当线程处于运行过程时,发出了IO请求时,此时进入阻塞状态.
  • 4:等待状态(waiting)(等待状态只能被其他线程唤醒):此时使用的无参数的wait方法,

    • 当线程处于运行过程时,调用了wait()方法,此时JVM把当前线程存在对象等待池中.
  • 5:计时等待状态(timed waiting)(使用了带参数的wait方法或者sleep方法)

    • 1:当线程处于运行过程时,调用了wait(long time)方法,此时JVM把当前线程存在对象等待池中.
    • 2:当前线程执行了sleep(long time)方法.
  • 6:终止状态(terminated):通常称为死亡状态,表示线程终止.

    • 1:正常执行完run方法而退出(正常死亡).
    • 2:遇到异常而退出(出现异常之后,程序就会中断)(意外死亡).

注意:线程一旦终止,就不能再重启启动,否则报错(IllegalThreadStateException).


1. 6 经典案例 生产者和消费者

//消费者public class Consumer extends Thread {    private Product p;    public Consumer(Product p) {        this.p = p;    }    public void run() {        for (int i = 0; i < 50; i++) {            p.consume();        }    }}//生产者public class Producer extends Thread {    private Product p = null;    public Producer(Product p) {        this.p = p;    }    public void run() {        for (int i = 0; i < 50; i++) {            if (i % 2 == 0) {                p.produce("红旗", "中国");            } else {                p.produce("三菱", "日本");            }        }    }}//产品public class Product {    private String productName;//产品名称    private String country;//生产国家    private Lock lock = new ReentrantLock();    private Condition con_pro = lock.newCondition();//生产的监视器    private Condition con_con = lock.newCondition();//消费的监视器    private boolean isEmpty = true;//判断产品是否为空    //生产    public void produce(String productName, String country) {        lock.lock();        try {            while (!isEmpty) {            //如果为false(产品不为空),生产的监视器等待                con_pro.await();            }            this.productName = productName;            Thread.sleep(30);            this.country = country;            isEmpty = false;            con_con.signalAll();        } catch (Exception e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    //消费    public void consume() {        lock.lock();        try {            while (isEmpty) {            //如果为true(产品为空),消费的监视器等待                con_con.await();            }            Thread.sleep(30);            System.out.println(this.productName + "--->" + this.country);            isEmpty = true;            con_pro.signalAll();        } catch (Exception e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }}

最后附上Java学习网址

原创粉丝点击