09 - JavaSE之线程

线程

线程的基本概念

线程是一个程序里面不同的执行路径。

  • 进程与线程的区别
  1. 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换开销大。
  2. 线程可以看作轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程的切换开销小。
  3. 多进程:在操作系统中能同时运行多个程序。
  4. 多线程:在同一应用程序中有多个顺序流同时执行。


线程的创建与启动

  1. Java 的线程是通过 java.lang.Thread 类来实现的。
  2. VM 启动时,会有一个由主方法 main 所定义的线程。
  3. 可以通过创建 Thread 的实例来创建新的线程。
  4. 每个线程都是通过某个特定的 Thread 对象所对应的 run() 方法来完成这个线程要做的任务,方法 run() 成为线程体。
  5. 通过调用 Thread 类的 start() 方法来启动一个线程。
  • 有两种方法创建新的线程:
  1. 第一种(推荐使用):定义线程类实现 Runnable 接口,然后重写 run 方法, 然后以这个线程类创建 Thread 类,然后调用这个 Thread 类的 start() 方法,就可以开始执行这个线程,这个线程具体要执行的内容在 run 方法里面。

    public class Test {

    public static void main(String[] args) {

    MyThread mt = new MyThread();

    Thread th = new Thread(mt);

    th.start();

        for(int i=0; i<100; i++) {
            System.out.println("Main: " + i);
        }
    }

    }

    class MyThread implements Runnable {

    @Override

    public void run() {

    for(int i=0; i<100; i++) {

    System.out.println("MyThread:" + i);

    }

    }

    }

PS: 如果我们没有 new一个 Thread 对象出来,而是直接使用 MyThread 的 run 方法(mt.run()),这就是方法调用,而不是启动线程了,结果就是先后打印语句,而不是并行打印语句了。

  1. 第二种:可以定义一个 Thread 的子类 myThread,并且重写 Thread 的 run 方法(Thread 也实现了Runnable 接口),然后生成子类 myThread 的对象,最后调用子类 myThread 对象的 start() 方法即可。

    public class Test {

    public static void main(String[] args) {

    MyThread mt = new MyThread();

    mt.start();

        for(int i=0; i<100; i++) {
            System.out.println("------ " + i);
        }
    }

    }

    class MyThread extends Thread {

    @Override

    public void run() {

    for(int i=0; i<100; i++) {

    System.out.println("MyThread:" + i);

    }

    }

    }



线程的状态转换



线程控制基本方法

isAlive() // 判断线程是否还活着,即线程是否还未终止
getPriority() // 获得线程的优先级数值
setPriority() // 设置线程的优先级数值
Thread.sleep(...) // 将当前线程指定睡眠时间
join() // 将一个线程合并到某个线程上,成为一个线程执行
yield() // 让出CPU,当前线程进入就绪队列等待调度
wait() // 当前线程进入对象的 wait pool
notify()/notifyAll() // 唤醒对象 wait pool中的一个/所有等待线程
  • sleep 方法

可以调用 Thread 的静态方法 Thread.sleep(long ms) 使得当前线程休眠。(哪个线程调用了Thread.sleep 方法,哪个线程就 sleep)

import java.util.*;
public class Test {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread th = new Thread(mt);
        th.start();

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

        }

        th.interrupt();
    }
}

class MyThread implements Runnable {
    private boolean flag = true;
    @Override
    public void run() {
        while(flag) {
            System.out.println("=== " + new Date() + " ===");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                flag = false;
            }
        }
    }
}
  • join 方法

将一个线程合并到某个线程上,成为一个线程执行。(如下程序就先执行线程 th ,之后才会执行主线程 Main ,而不是并行执行。)

import java.util.*;
public class Test {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread th = new Thread(mt);
        th.start();

        try {
            th.join();
        } catch(InterruptedException e) {

        }

        for(int i=0; i<10; i++) {
            System.out.println("Main Thread......");
        }

        th.interrupt();
    }
}

class MyThread implements Runnable {
    @Override
    public void run() {
        for(int i=0; i<10; i++) {
            System.out.println("=== " + new Date() + " ===");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }
}
  • yield 方法

让出CPU,当前线程进入就绪队列等待调度。

public class Test {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread("mt1");
        MyThread mt2 = new MyThread("mt2");
        mt1.start();
        mt2.start();
    }
}

class MyThread extends Thread {
    MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        try {
            for (int i = 0; i < 50; i++) {
                System.out.println(getName() + " --- " + i);
                Thread.sleep(100);

                if (i % 10 == 0) {
                    Thread.yield();
                }
            }
        } catch (InterruptedException e) {

        }
    }
}


线程的优先级

  • Java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行。
  • 线程的优先级用数字表示,范围从 1 到 10,一个线程的缺省优先级是 5。

    Thread.MIN_PRIORITY = 1

    Thread.MAX_PRIORITY = 10

    Thread.NORM_PRIORITY = 5

  • 使用下面方法获得或设置线程对象的优先级:

    int getPriority();

    void setPriority(int newPriority);



Java 中的线程临界区

  • 系统中每次只允许一个线程访问的资源叫做临界资源。
  • 对临界资源进行访问的程序代码区域叫做临界区。
  • Java 中通过 synchronized 关键字和对象锁机制对临界区进行管理。
  • Java 中的每个对象都可以作为对象锁使用。


线程同步

我们先看这样一个程序:

public class Test implements Runnable {
    Timer time = new Timer();
    public static void main(String args[]){
        Test test = new Test();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);

        t1.setName("t1");
        t2.setName("t2");

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

    @Override
    public void run() {
        time.add(Thread.currentThread().getName());
    }
}

class Timer {
    private static int num = 0;
    public void add(String name) {
        num ++;
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {

        }
        System.out.println(name + " 是第 " + num + " 个使用timer的线程");
    }
}

我们两个线程同时执行,而且调用同一个方法,相当于访问同一个共享资源 num,执行的结果为:

t2 是第 2 个使用timer的线程
t1 是第 2 个使用timer的线程

这是怎么回事呢?按照猜想,虽然 t1 t2 线程并行执行,但是先开启的 t1 进程,num = 1;在开启的 t2 线程,num = 2,所以,应该是:t1 是第 1 个使用timer的线程;t2 是第 2 个使用timer的线程 才对啊?

其实,这就涉及到线程同步的问题,如果在一个线程访问一个共享对象的时候没有给这个共享资源上锁的话,那么这个线程操作的共享资源可能就是错误的,因为可能别的进程也在访问这个共享资源。

那么,我们就需要在进程访问这个共享资源的时候,将其上锁,上锁的方式有两种:(还是以上面的程序为例:)

// 方式一
class Timer {
    private static int num = 0;
    public void add(String name) {
        synchronized (this) {} {  // 资源上锁
            num ++;
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {

            }
            System.out.println(name + " 是第 " + num + " 个使用timer的线程");
        }
    }
}

synchronized (this) {

// 需要锁定的内容

}

synchronized(this)表示: 锁定当前对象。括号中的语句在一个线程执行的过程中,不会被另一个线程打断。

// 方式二
class Timer {
    private static int num = 0;
    synchronized public void add(String name) { // 资源上锁
        num ++;
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {

        }
        System.out.println(name + " 是第 " + num + " 个使用timer的线程");
    }
}

在 add 函数加上 synchronized 关键字,就表示哪个进程调用了add 函数,那么这个进程在执行这个方法的时候,锁定当前对象。

  • 在 Java 语言中引入了对象互斥锁的概念,保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记保证在任意时刻,智能有一个线程访问该对象。
  • 关键字 synchronized 来与对象的互斥锁联系。当某个对象 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问。


线程死锁

什么是线程死锁?

如果两个或多个线程分别拥有不同的资源, 而同时又需要对方释放资源才能继续运行时,就会发生死锁。简单来说:死锁就是当一个或多个进程都在等待系统资源,而资源本身又被占用时,所产生的一种状态。

public class Test implements Runnable {
    public int flag = 0;

    static Object o1 = new Object();
    static Object o2 = new Object();

    public static void main(String args[]){
        Test test1 = new Test();
        Test test2 = new Test();

        test1.flag = 1;
        test2.flag = 2;

        Thread t1 = new Thread(test1);
        Thread t2 = new Thread(test2);

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

    public void run() {
        System.out.println("flag=" + flag);
        if(1 == flag) {
            synchronized (o1) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                synchronized (o2) {
                    System.out.println("1");
                }
            }
        }

        if(2 == flag) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                synchronized (o1) {
                    System.out.println("2");
                }

            }
        }
    }
}

注意:以上代码中 Object 对象 O1 O2 一定是 static 的,否则不能得到进程死锁。还有一定要有 Thread.sleep 语句。

举例:

public class Test implements Runnable {
    public int  b = 100;

    public synchronized void m1() throws Exception{
        b = 1000;
        Thread.sleep(3000);
        System.out.println("m1:b = " + b);
    }

    public void m2 () {
        System.out.println("m2:b = " + b);
        b = 2;
        System.out.println("m2:b = " + b);
    }

    public void run() {
        try {
            m1();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        Test tt = new Test();
        Thread t = new Thread(tt);
        t.start();

        Thread.sleep(1000);

        tt.m2();

        System.out.println("b = " + tt.b);
    }
}

提问:各个打印 b 的值为多少?

m2:b = 1000

m2:b = 2

b = 2

m1:b = 2

为什么 b 的值可以被修改呢?

因为,如果一个方法加了 synchronized ,而且有一个进程正在访问这个方法,那么只能说明别的进程不可以同时访问这个方法,但是并不妨碍别的进程访问其他的方法,如果其他的方法中有对你需要保护的对象(这里是 b)进行操作的话,也是允许的。

所以,如果要保护一个需要同步的对象的话,对访问这个对象的所有方法考虑加不加 synchronized 。因为一个方法加了锁,只是另一个线程不能访问加了锁的方法,但是可以访问其他的方法,其他的方法可能修改了你需要同步的对象。synchronized 修饰的方法可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法。(所以,上面的 m2 方法也需要加 synchronized 修饰。)



生产者和消费者问题:

/**
 * 这里模拟的是生产者和消费者的问题:这里生产者生产樱花膏,消费者吃樱花膏。然后
 * 还有一个装樱花膏的篮子 basket. 我们需要做的生产者生产的樱花膏按照顺序放入篮子,
 * 吃货消费者按照顺序从顶部依次拿出来吃,所以 basket 的数据结构是栈。
 *
 * 注意:生产者生产的樱花膏要和吃货需要吃的樱花膏数量一致,否则程序会wait
 */

public class Test {
    public final int NUM = 10;
    public static void main(String[] args) {
        BasketStack bs = new BasketStack();
        Producer p = new Producer(bs);
        Consumer c = new Consumer(bs);

        new Thread(p).start();
        new Thread(p).start();
        new Thread(p).start();

        new Thread(c).start();
    }
}

// 樱花膏
class Sakura {
    private int id = 0;

    Sakura (int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return ((Integer)id).toString();
    }
}

// 篮子
class BasketStack {
    int index = 0;
    Sakura[] sakuras = new Sakura[6]; // 一个篮子只能装6个樱花膏

    public synchronized void push(Sakura sa) {
        while(index == (sakuras.length - 1)) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notifyAll();
        sakuras[index] = sa;
        index ++;
    }

    public synchronized Sakura pop() {
        while(index == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        this.notifyAll();
        index--;
        return sakuras[index];
    }
}

// 生产者
class Producer implements Runnable {
    BasketStack bs = null;

    Producer(BasketStack bs) {
        this.bs = bs;
    }

    @Override
    public void run() {
        for(int i=0; i<10; i++) {
            Sakura sa = new Sakura(i);
            bs.push(sa);
            System.out.println("生产者:" + sa.toString());
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 吃货
class Consumer implements Runnable {
    BasketStack bs = null;

    Consumer(BasketStack bs) {
        this.bs = bs;
    }

    @Override
    public void run() {
        for(int i=0; i<30; i++) {
            System.out.println("吃货:" + bs.pop().toString());

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

wait和sleep的区别:

1.wait之后 锁就不归我所有,别的线程可以访问锁定对象;sleep后 锁还是我的,别的线程不可以访问锁定对象。

2.调用 wait方法的时候必须先锁定对象,否则谈何wait。

2.wait是Object的方法,sleep是Thread的方法。



总结(关键字联想)

  • 线程/进程的概念
  • 创建和启动线程的方式
  • sleep
  • join
  • yield
  • synchronized
  • wait
  • notify/notifyAll

原文地址:https://www.cnblogs.com/lvonve/p/8323661.html

时间: 2024-10-26 12:31:23

09 - JavaSE之线程的相关文章

javaSE之线程联合

首先定义 : 一个线程A在占有CPU资源期间 ,可以让其他线程调用join()和本线程联合. 嗯哈,像书本这个列子: 如: B.join(); 我们称A在运行期间联合了B, 如果线程A在占有CPU资源期间一旦联合B线程,那么A线程将立刻 中断执行,一直等到它联合的线程B执行完毕,A线程再重新排队等待CPU资源,以便恢复执行, 如果A准备联合的B线程已经结束,那么B.join(),不会产生任何效果. 1 package dialog_color; 2 3 public class Example1

JavaSE中线程与并行API框架学习笔记——线程为什么会不安全?

前言:休整一个多月之后,终于开始投简历了.这段时间休息了一阵子,又病了几天,真正用来复习准备的时间其实并不多.说实话,心里不是非常有底气. 这可能是学生时代遗留的思维惯性--总想着做好万全准备才去做事.当然,在学校里考试之前当然要把所有内容学一遍和复习一遍.但是,到了社会里做事,很多时候都是边做边学.应聘如此,工作如此,很多的挑战都是如此.没办法,硬着头皮上吧. 3.5 线程的分组管理 在实际的开发过程当中,可能会有多个线程同时存在,这对批量处理有了需求.这就有点像用迅雷下载电视剧,假设你在同时

JavaSE中线程与并行API框架学习笔记1——线程是什么?

前言:虽然工作了三年,但是几乎没有使用到多线程之类的内容.这其实是工作与学习的矛盾.我们在公司上班,很多时候都只是在处理业务代码,很少接触底层技术. 可是你不可能一辈子都写业务代码,而且跳槽之后新单位很可能有更高的技术要求.除了干巴巴地翻书,我们可以通过两个方式来解决这个问题:一是做业余项目,例如在github上传自己的demo,可以实际使用:二是把自己的学习心得写成博客,跟同行们互相交流. 3.1 线程的初窥门径 我们在之前的文章里提到的程序其实都是单线程程序,也就说启动的程序从main()程

python 自动化之路 day 09 进程、线程、协程篇

本节内容 操作系统发展史介绍 进程.与线程区别 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者消费者模型 Queue队列 开发一个线程池 进程 语法 进程间通讯 进程池

Java多线程并发09——如何实现线程间与线程内数据共享

本文将为各位带来 Java 阻塞队列相关只是.关注我的公众号「Java面典」了解更多 Java 相关知识点. 线程间数据共享 Java 里面进行多线程通信的主要方式就是共享内存的方式,共享内存主要的关注点有两个:可见性和有序性原子性.Java 内存模型(JMM)解决了可见性和有序性的问题,而锁解决了原子性的问题,理想情况下我们希望做到"同步"和"互斥".有以下常规实现方法: 将数据抽象成一个类 将数据抽象成一个类,并将对这个数据的操作作为这个类的方法,这么设计可以和

《Cracking the Coding Interview》——第16章:线程与锁——题目1

2014-04-27 19:09 题目:线程和进程有什么区别? 解法:理论题,操作系统教材上应该有很详细的解释.我回忆了一下,写了如下几点. 代码: 1 // 16.1 What is the difference between process and thread? 2 Answer: 3 Process: 4 1. Basic element of resource allocation in the operating system. 5 2. Possesses independent

黑马程序员–Java之多线程09

黑马程序员–Java之多线程09 一.线程和进程 在Java中,并发机制非常重要,程序员可以在程序中执行多个线程,每一个线程完成一个功能,并与其他线程并发执行,这种机制被称为多线程.多线程就是指一个应用程序中有多条并发执行的线索,每条线索都被称作一个线程,它们会交替执行,彼此间可以进行通信.多线程是非常复杂的机制,在每个操作系统中的运行方式也存在差异,window操作系统是多任务操作系统,它以进程为单位.一个进程是一个包含有自身地址的程序,每个独立执行的程序都称为进程,也就是正在执行的程序.系统

Python_进程、线程及协程

一.Python进程 IO密集型----多线程 计算密集型----多进程 1.单进程 from multiprocessing import Process def foo(i): print('你好哈',i) if __name__ == '__main__': #if __name__ == '__main__':只可做测试调用,不能用于生产,windows不支持,linux中可不用添加if __name__ == '__main__' for i in range(10): t = Pro

SchuledExecutorService 使用controller控制线程关闭

1:SchuledExecutorService  使用controller控制线程关闭 package com.li.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; impo