文章目录

    • @[toc]
  • 第九篇 Java多线程
    • 一、基本概念
      • 1、程序、进程和线程
      • 2、并行和并发
    • 二、线程的调度
      • 1、线程的调度策略和调度方法
      • 2、线程的优先级
        • (1)线程优先等级
        • (2)获取和设置线程优先级
    • 三、线程的创建和使用
      • 1、方式一:继承于Thread类
        • (1)使用步骤
        • (2)匿名子类的写法
        • (3)Thread类常用方法
        • (4)经典例子:多窗口售票v1.0
      • 2、方式二:实现Runnable接口
        • (1)使用步骤
        • (2)经典例子:多窗口售票v1.1
      • 3、两种创建线程方式的对比
    • 四、线程的生命周期
    • 五、线程的同步
      • 1、多窗口售票的BUG
      • 2、方式一:同步代码块
      • 3、方式二:同步方法
      • 4、线程同步解决窗口售票的BUG
        • (1)经典例子:多窗口售票v2.0.0
        • (2)经典例子:多窗口售票v2.0.1
      • 5、线程死锁问题
      • 6、方式三:Lock锁(JDK 5.0新增)
      • 7、编程练习
    • 六、线程间通信
    • 七、创建线程另外两种方式(JDK 5.0新增)
      • 1、实现Callable接口
      • 2、使用线程池创建

参考资料:《Java线程》、《JDK API 1.6 中文版》
学习视频:b站《尚硅谷Java入门视频教程》(主讲:宋红康老师)

第九篇 Java多线程

一、基本概念

  • 线程是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程
  • 每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新 Thread对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。
  • 当Java虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的main方法)。Java虚拟机会继续执行线程,直到下列任一情况出现时为止:
    ✒️ 调用了Runtime类的exit方法,并且安全管理器允许退出操作发生。
    ✒️ 非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到run方法之外的异常。
  • 创建新执行线程有两种方法。一种方法是将类声明为Thread的子类。该子类应重写Thread类的run方法;另一种方法是声明实现Runnable接口的类。该类然后实现run方法。然后可以分配该类的实例,在创建Thread时作为一个参数来传递并启动。—— 摘自官方文档JDK API 1.6 中文版

1、程序、进程和线程

  • 程序(Program):为完成特定任务或功能所编写的一组指令集合,即一段静态的代码/静态对象
  • 进程(Process):程序执行的结果,是动态的过程,拥有生命周期,作为系统资源分配的单位。系统在运行时会为其分配不同的内存。
  • 线程(Thread):进程的进一步细化,是程序内的一条执行路径。
    ✒️一个Java程序至少有3个线程:main( )主线程、gc( )垃圾资源回收线程、异常处理线程
    ✒️Java线程可分为两类:一是守护线程,二是用户线程(java垃圾回收是一个典型的守护线程)
    ✒️setDaemon( true )可以将一个用户线程变成守护线程,若JVM中都是守护线程,则JVM将退出

2、并行和并发

  • 并行:多个CPU同时执行多个任务
  • 并发:单个CPU执行多个任务(和串口传输类似,实际上不是同一时刻在执行 —— 时间片)

二、线程的调度

关于线程调度的具体实现和原理,详见《Java线程》第六章

1、线程的调度策略和调度方法

  • 时间片+抢占式:**优先级高的线程占用CPU
    ✒️时间片策略:同优先级线程组成先进先出队列
    ✒️抢占式策略:高优先级线程优先占用CPU

PS:高优先级线程优先抢占CPU执行权,只是抢占的概率高,并不意味着高优先级一定比低优先级的线程先执行

2、线程的优先级

(1)线程优先等级

定义在Thread类里的常量 优先级
MIN_PRIORITY 1
NORM_PRIORITY(默认优先级) 5
MAX_PRIORITY 10

(2)获取和设置线程优先级

  • setPriority( int newPriority ):设置线程优先级为newPriority
  • getPriority( ):获取线程当前优先级

三、线程的创建和使用

1、方式一:继承于Thread类

(1)使用步骤

  • 创建一个子类继承于Thread类
  • 重写Thread类的run( )
  • 实例化Thread类的子类对象
  • 由该对象调用start( )启动线程
package com.javaThread.java;/* 线程1遍历100内偶数 */
class subThread1 extends Thread{/* 1、创建子类继承于Thread */@Override/* 2、重写run方法 *//* 3、到main里实例化线程1对象 *//* 4、到main里用线程1对象调用start方法 */public void run() {for (int i = 0; i < 100; i++) {if(i%2 == 0) System.out.println(this.getName()+":"+i);}}
}
/* 线程2遍历100内奇数 */
class subThread2 extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i%2 != 0) System.out.println(this.getName()+":"+i);}}
}
public class ThreadDemo1 {public static void main(String[] args) throws InterruptedException {subThread1 t1 = new subThread1();subThread2 t2 = new subThread2();t1.start();t2.start();while (true) {System.out.println("Hello world");Thread.sleep(1000);}}
}

(2)匿名子类的写法

package com.javaThread.java;public class ThreadDemo2 {public static void main(String[] args) {/* 创建Thread类的匿名子类 */new Thread(){@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i%2 == 0) System.out.println(this.getName()+":"+i);}}}.start();/* 创建Thread类的匿名子类 */new Thread(){@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i%2 != 0) System.out.println(this.getName()+":"+i);}}}.start();}
}

(3)Thread类常用方法

  • start( ):启动当前线程,会调用当前线程的run( )方法
  • run( ):创建线程要执行的操作声明在此方法中(类似Linux多线程中传入pthread_create( )中的指针函数)
  • currentThread( ):静态方法,返回当前线程
  • getName( ):获取当前线程名
  • setName( ):设置当前线程名
  • yield( ):释放CPU执行权,重新调度(释放后可能被其他线程抢占,也可能又被自己抢到)
  • join( ):阻塞等待其他线程执行完,再结束阻塞
  • sleep( long millis ):线程阻塞等待一定时间(毫秒级别)
  • isAlive( ):判断线程死活,返回线程状态true/false
  • 其他方法(Deprecated - 已过时):stop( )强制结束

(4)经典例子:多窗口售票v1.0

/* 三个窗口(线程)同时卖票,一共100张票 */
class Windows extends Thread{public Windows(String threadName){super(threadName);}private static int tickets = 100;@Overridepublic void run() {while (tickets > 0){System.out.println(this.getName()+" > NO."+tickets+" has been sold");tickets --;}}
}
public class ThreadDemo3 {public static void main(String[] args) {Windows w1 = new SellTickets("windows_1");Windows w2 = new SellTickets("windows_2");Windows w3 = new SellTickets("windows_3");w1.start();w2.start();w3.start();}
}
  • BUG记录:v1.0中存在线程安全问题(重票或者错票)

2、方式二:实现Runnable接口

  • 说明:在官方API文档中写着创建线程有两种方式,一是继承于Thread类,二是实现Runnable接口。实际上,在JDK 5.0后又新增的另外两种方式,实现Callable接口和使用线程池的方式。(一共就有四种创建线程的方式)

(1)使用步骤

  • 创建一个实现Runnable接口的类(implements)
  • 实现Runnable中的抽象方法run( )
  • 创建实现类的对象
  • 将此对象传到Thread类的构造器中并创建Thread对象
  • 通过Thread对象去调用start( )方法
/* 创建线程方式二:implements Runnable的方式*/
/* 1.创建一个实现Runnable接口的类(implements) */
class Thread1 implements Runnable{/* 2.实现Runnable中的抽象方法run() */@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i%2 == 0) System.out.println(i);}}
}
public class ThreadDemo4 {public static void main(String[] args) {/* 3.创建实现类的对象 */Thread1 t1 = new Thread1();/* 4.将此对象传到Thread类的构造器中并创建Thread对象 */Thread thread1 = new Thread(t1);//Thread thread1 = new Thread(new Thread1());  //3+4/* 5.通过Thread对象去调用start()方法 */thread1.start();}
}

(2)经典例子:多窗口售票v1.1

/* 实现Runnable接口的方式创建的线程 */
class Windows implements Runnable{private int tickets = 100;   //和v1.0不同,这里不用static@Overridepublic void run() {while(tickets > 0){System.out.println(Thread.currentThread().getName()+"> NO."+tickets+" has been sold");tickets --;}}
}
public class ThreadDemo5 {public static void main(String[] args) {Windows windows = new Windows();/* 一个windows可传到三个Thread类的构造器中 */Thread t1 = new Thread(windows);Thread t2 = new Thread(windows);Thread t3 = new Thread(windows);/* 共用一个windows对象,也就共享100张票 */t1.setName("window_1");t2.setName("window_2");t3.setName("window_3");t1.start();t2.start();t3.start();}
}
/* 同样这里也有线程安全的问题,出现了重票的错票的问题,待解决 */

3、两种创建线程方式的对比

  • 开发中,优先考虑用实现Runnable接口的方式创建线程,原因如下:
    ✒️实现的方式没有类的单继承性的局限性
    ✒️实现的方式更适合处理多线程操作共享数据的情况
  • 联系:实际上,Thread类中也实现的Runnable接口
  • 相同点:两种方式都需要重写run( )方法,将线程执行的逻辑声明在run( )中

四、线程的生命周期

  • 其中,suspend( )、resume( )都是Deprecated - 已过时的,因为该方法可能会产生死锁。如果某一线程suspend挂起时持有锁,则在该线程重新开始之前任何线程都不能访问该资源。如果重新开始此线程的另一线程调用resume之前锁定该监视器,则会发生死锁。这类死锁通常会证明自己是“冻结”的进程。

五、线程的同步

1、多窗口售票的BUG

  • 重票、错票的产生:当某一个线程在操作tickets时,由于中间的时间差(可能极小却不可忽略)或者线程状态变化等问题,操作尚未完成就有其他线程也操作tickets。
    (PS:例如当某一个线程拿到tickets=100,还未到tickets--就有其他线程参与进来,这时也拿到了tickets=100。最终出现票号为99的重票;同样的当某一个线程操作完tickets--到等于0,还未结束循环时,另一个线程参与进来,拿到tickets=0就会出现票号为-1的错票)
  • 在Java中,通过以下两种同步方式来解决线程安全问题

2、方式一:同步代码块

synchronized(同步监视器){//需要被同步的代码……  ……  ……
}
  • 操作共享数据的代码,即为需要被同步的代码
  • 共享数据:多个线程共同操作的变量。如:窗口售票中的tickets
  • 同步监视器(锁):任何一个类的对象都可以充当同步监视器。要求多个线程必须共用同一把锁
    ✒️实现Runnable接口的方式创建线程,可以考虑用this充当同步监视器
    ✒️继承Thread类的方式创建线程,可以考虑用当前类作同步监视器,总之需要保证锁是唯一的
  • 局限性:同步虽然解决的线程安全问题,但是在操作同步代码时只能有一个线程参与,其余等待,相当于此过程是单线程的。

3、方式二:同步方法

  • 同步方法仍然涉及到同步监视器,只是不需要显式的声明
  • 静态同步方法,同步监视器是:this
  • 非静态同步方法,同步监视器是:当前类

4、线程同步解决窗口售票的BUG

(1)经典例子:多窗口售票v2.0.0

/* 实现Runnable接口的方式创建的线程 */
/* 同步代码块解决线程安全问题 */
class Windows implements Runnable{private int tickets = 100;Object obj = new Object();@Overridepublic void run() {while (true) {synchronized (obj) { //可传this,此时的this是windows的唯一对象/* 为了出现重票和错票的概率变大,这里加入sleep() */try{Thread.sleep(50);}catch (InterruptedException e) {e.printStackTrace();}if(tickets > 0) {System.out.println(Thread.currentThread().getName() + "> NO." + tickets + " has been sold");tickets --;}else{break;}}}}
}
public class ThreadDemo5 {public static void main(String[] args) {Windows windows = new Windows();Thread t1 = new Thread(windows);Thread t2 = new Thread(windows);Thread t3 = new Thread(windows);t1.setName("window_1");t2.setName("window_2");t3.setName("window_3");t1.start();t2.start();t3.start();}
}

(2)经典例子:多窗口售票v2.0.1

/* 继承Thread类的方式创建的线程 */
/* 同步方法解决线程安全问题 */
class Windows extends Thread {public Windows(String threadName) {super(threadName);}private static int tickets = 100;private static boolean flag = true;//标志位,控制循环@Overridepublic void run() {while(flag) {sellTickets();  //调用同步方法}}/* 同步方法实现,注意是静态的同步方法 */private static synchronized void sellTickets() {/* sleep提高错票重票的概率 */try{Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}if (tickets > 0) {System.out.println(Thread.currentThread().getName() + " > NO." + tickets + " has been sold");tickets--;}else{flag = false;}}
}public class ThreadDemo6 {public static void main(String[] args) {Windows w1 = new Windows("windows_1");Windows w2 = new Windows("windows_2");Windows w3 = new Windows("windows_3");w1.start();w2.start();w3.start();}
}

5、线程死锁问题

  • 死锁:不同的线程分别占用对方的同步资源,都在等待对方释放自己所需的同步资源,造成了线程的死锁。
    ✒️出现死锁后不会报错和抛异常,所有线程处在阻塞状态,无法继续。
    ✒️避免嵌套同步、尽量减少同步资源的定义以避免出现死锁
/* 死锁的简单例子 */
public class ThreadDemo6 {public static void main(String[] args) {StringBuffer s1 = new StringBuffer();StringBuffer s2 = new StringBuffer();/* 线程1 - 匿名类*/new Thread(){@Overridepublic void run() {synchronized (s1){s1.append("a");s2.append("1");/* sleep增加死锁概率 */try{Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s2){s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}}.start();/* 当线程1先执行,拿到s1阻塞,此时线程2可能刚好拿了s2也阻塞,* 接着就出现线程1拿着s1等s2,线程2拿着s2等s1 *//* 线程2 */new Thread(new Runnable() {@Overridepublic void run() {synchronized (s2){s1.append("c");s2.append("3");/* sleep增加死锁概率 */try{Thread.sleep(200);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (s1){s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}}}).start();}
}

6、方式三:Lock锁(JDK 5.0新增)

  • 问:synchronized和Lock的异同?
    答:虽然synchronized和Lock都能解决线程安全的问题,但是使用synchronized的方式在执行完相应的同步代码后,即使发生异常,也会自动释放占用的锁(同步监视器)。而用Lock的方式则需要手动的启动(lock( ) -上锁),手动的结束同步(unlock( ) - 解锁),在发生异常时若没有unlock( ),很有可能导致死锁,所以unlock( )需要写在finally中以保证异常情况下锁也能够被释放;且synchronized是非公平锁,Lock可以选择公平或非公平锁。(默认非公平)
  • 用法见下例代码:
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;class Window implements Runnable{private int tickets = 100;/* 实例化ReentrantLock对象 */ReentrantLock l = new ReentrantLock();@Overridepublic void run() {while(true){/* 给同步代码上锁 */l.lock();try{if(tickets > 0){try{Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+">\tNO."+tickets+" has been sold");tickets --;}else break;}finally {/* 手动解锁 */l.unlock();}}}
    }public class LockTest{public static void main(String[] args) {Window w = new Window();Thread t1 = new Thread(w);Thread t2 = new Thread(w);Thread t3 = new Thread(w);t1.setName("window 1");t1.start();t2.setName("window 2");t2.start();t3.setName("window 3");t3.start();}
    }

7、编程练习

银行有一个账户,有两个储户分别向同一个账户存3000元,每次存1000,存5次。每次存完打印账户余额。
问题:该程序是否有安全问题,如果有,如何解决?
【提示】
​ 1,明确哪些代码是多线程运行代码,须写入run()方法
​ 2,明确什么是共享数据。
​ 3,明确多线程运行代码中哪些语句是操作共享数据的。
【拓展问题】能否实现储户交替存钱的操作?

import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;class Account{  //账号private double balance = 0; //当前余额/* 存钱并显示当前余额的方法,操作共享数据需要同步 */public synchronized void saveMoney(double money){if(money < 0) return;balance = balance + money;String customerName = Thread.currentThread().getName();System.out.println(customerName+"> "+"Deposit "+money+"¥ succeed.Your current balance is "+balance+"¥");}
}
class Customer extends Thread{  //客户——线程private Account acc;private ReentrantLock lock = new ReentrantLock();/* 提供构造器传入账户,可实现多客户共用一个账户 */public Customer(Account account) {this.acc = account;}/* 重写run方法,根据题目 */@Overridepublic void run() {for(int i=0;i<5;i++){/* 提高出现存钱问题概率,加入睡眠 */try{Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}acc.saveMoney(1000);}}
}
public class AccountTest{public static void main(String[] args) {Account acc = new Account();Customer c1 = new Customer(acc);Customer c2 = new Customer(acc);Customer c3 = new Customer(acc);c1.setName("Customer 1");c2.setName("Customer 2");c1.start();c2.start();}
}

六、线程间通信

  • wait( ):调用此方法当前线程进入阻塞状态,并释放同步监视器
  • notify( )、notifyAll( ):调用此方法会唤醒正在处于wait的线程,多个线程则唤醒优先级高的线程
    (PS:以上只能在同步代码块或者同步方法中使用-sychronized,且其调用者必须是同步监视器,否则会出现IllegalMonitorStateException)
  • 【拓展问题】实现两个储户交替存钱的操作?
import java.util.concurrent.locks.ReentrantLock;class Account {  //账号public double balance = 0; //当前余额/* 存钱并显示当前余额的方法,操作共享数据需要同步 */static ReentrantLock l = new ReentrantLock();public synchronized void saveMoney(double money) {if (money < 0) {System.out.println("error");return;}else {notify();balance = balance + money;String customerName = Thread.currentThread().getName();System.out.println(customerName + "> " + "Deposit " + money + "$ succeed.Your current balance is " + balance + "$");try {wait(500);} catch (InterruptedException e) {e.printStackTrace();}}}
}class Customer extends Thread {  //客户——线程private Account acc;/* 提供构造器传入账户,可实现多客户共用一个账户 */public Customer(Account account) {this.acc = account;}/* 重写run方法,根据题目 */@Overridepublic void run() {for (int i = 0; i < 5; i++) {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}acc.saveMoney(1000);}}
}public class AccountTest {public static void main(String[] args) {Account acc = new Account();Customer c1 = new Customer(acc);Customer c2 = new Customer(acc);c1.setName("Customer 1");c2.setName("Customer 2");c1.start();c2.start();}
}

七、创建线程另外两种方式(JDK 5.0新增)

1、实现Callable接口

  • 与Runnable相比,Callable的功能更加强大:
    ✒️可以有返回值
    ✒️可以抛出异常
    ✒️支持泛型
    ✒️可借助FutureTask类,比如获取返回结果
  • 使用步骤:
    ✒️创建一个实现Callable的实现类
    ✒️重写call方法,将线程执行的操作声明在call( )中
    ✒️new一个Callable接口实现类的对象传到FutureTask构造器中
    ✒️new一个FutureTask对象传到Thread类的构造器中
    ✒️使用Thread类对象调用start( )启动线程
  • 下面使用实现Callable接口的方式创建线程,依然是上面"储户存钱"的问题
  package com.ThreadDemos.java;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;class Account{public double balance = 0;}/* 1.创建一个实现Callable的实现类 */class Customer implements Callable {static Account acc = new Account();/* 2.重写call方法,将线程执行的操作声明在call()中 */@Overridepublic Object call() throws Exception {String threadName = Thread.currentThread().getName();for (int i = 0; i < 3; i++) {synchronized (acc) {Thread.sleep(1000);acc.balance += 1000;System.out.println(threadName + "转入金额:"+1000+"$");}}return acc.balance;}}public class CallableTest {public static void main(String[] args) {Customer customer1 = new Customer();Customer customer2 = new Customer();/* 3.new一个Callable接口实现类的对象传到FutureTask构造器中 */FutureTask futureTask1 = new FutureTask(customer1);FutureTask futureTask2 = new FutureTask(customer2);/* 4.new一个FutureTask对象传到Thread类的构造器中 */Thread t1 = new Thread(futureTask1);Thread t2 = new Thread(futureTask2);/* 5.使用Thread类对象调用start()启动线程 */t1.setName("客户1");t2.setName("客户2");t1.start();t2.start();try {if((double)futureTask1.get() > (double)futureTask2.get()){System.out.println("当前账户余额是:"+futureTask1.get()+"$");}else {System.out.println("当前账户余额是:"+futureTask2.get()+"$");}} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}}

2、使用线程池创建

  • 概述:提前创建好多个线程组成线程池,使用时从线程池中直接获取,使用完重新放回池中。可以避免频繁创建、销毁的操作,实现重复利用;对于创建和销毁、使用线程量大的资源(如:高并发线程),能提高响应速度、降低资源销毁。
  • 优点:①减少了创建线程的时间,提高响应速度;②重复利用线程池中的线程,降低了资源消耗;③便于线程管理
  • 使用步骤:
    ✒️提供指定线程数量的线程池
    ✒️提供实现Runnable接口或者Callable接口实现类对象
    ✒️关闭线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;public class ThreadPollTest {public static void main(String[] args) {/* 1.提供指定线程数量的线程池 */ExecutorService service = Executors.newFixedThreadPool(10);/* 2.设置线程池的属性[可选] */ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) service;threadPoolExecutor.setCorePoolSize(10); //设置核心池大小threadPoolExecutor.setMaximumPoolSize(10);  //设置最大线程数/* 3.提供实现Runnable接口或者Callable接口实现类对象 */service.submit(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i%2 == 0) System.out.print(i+" ");}}});/* 4.关闭线程池 */service.shutdown();}
}

第九篇 Java多线程相关推荐

  1. 【面试 多线程】【第九篇】多线程的问题

    1.多线程有什么用 发挥多核CPU优势,防止阻塞,更快的处理数据 =============================================================== 2.多 ...

  2. 300 行代码带你搞懂 Java 多线程!

    线程 线程的概念,百度是这样解释的: 线程(英语:Thread)是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可 ...

  3. java多线程同步 多窗口卖票实例_java多线程之火车售票系统模拟实例

    1.前言 为了学习多线程共享与通信,我们模拟一个火车售票系统,假设有10张火车票,三个窗口(也就是三个线程)同时进行售票. 2.非同步代码 package com.tl.skyLine.thread; ...

  4. Java总结篇系列:Java多线程(三)

    2019独角兽企业重金招聘Python工程师标准>>> 本文主要接着前面多线程的两篇文章总结Java多线程中的线程安全问题. 一.一个典型的Java线程安全例子 public cla ...

  5. Java总结篇系列:Java多线程(二)

    本文承接上一篇文章<Java总结篇系列:Java多线程(一)>. 四.Java多线程的阻塞状态与线程控制 上文已经提到Java阻塞的几种具体类型.下面分别看下引起Java线程阻塞的主要方法 ...

  6. Java多线程编程实战指南+设计模式篇pdf

    下载地址:网盘下载 随着CPU 多核时代的到来,多线程编程在充分利用计算资源.提高软件服务质量方面扮演了越来越重要的角色.而 解决多线程编程中频繁出现的普遍问题可以借鉴设计模式所提供的现成解决方案.然 ...

  7. java多线程编程_Java多线程编程实战指南+设计模式篇.pdf

    Java多线程编程实战指南+设计模式篇.pdf 对Java架构技术感兴趣的工程师朋友们可以关注我,转发此文后私信我"Java"获取更多Java编程PDF资料(附送视频精讲) 关注我 ...

  8. java多线程论文_Java5 多线程之入门篇-论文

    Java5 多线程之入门篇 Java5 多线程之入门篇 首先回顾一下JDK1.5之前的线程相关的知识: 1 线程的入门. 什么是线程,线程就是程序执行的线索,Java是面向对象的语言什么类来表示这样一 ...

  9. Java核心类库篇7——多线程

    Java核心类库篇7--多线程 1.程序.进程和线程 程序 - 数据结构 + 算法,主要指存放在硬盘上的可执行文件 进程 - 主要指运行在内存中的可执行文件 线程就是进程内部的程序流 操作系统内部支持 ...

最新文章

  1. springBoot+maven的打包和部署在Tomcat
  2. 2016年11月2日——jQuery源码学习笔记
  3. 如何删除第一张单页_如何用PowerBI导入网页数据
  4. SQLServer 2000 生成数据源的SQL脚本
  5. 在Ubuntu服务器上打开第二个控制台会话
  6. Ubuntu16.04安装WPS
  7. utf-8转换gbk代码_将代码转换为现金-如何以Web开发人员的身份赚钱并讲述故事。...
  8. Air Data System
  9. php伪数组转换为数组,JavaScript伪数组用法实例
  10. php优化上百次foreach,php – 优化数千个项目的foreach
  11. mysql工作中遇到的问题_MySQL工作中遇到的问题记录
  12. Nginx 简单命令
  13. java英文笔试题_java英文面试笔试题
  14. plsql使用存储过程添加数据
  15. 解决My97DatePicker时间插件的input框,触发onchange时间要失去焦点时才触发
  16. 超女复活赛,明星大补考
  17. 动词ing形式的5种用法_动词ing形式的5种用法
  18. android 4.4 设置谷歌拼音输入法为默认输入法,android4.4修改出厂默认输入法
  19. 坚果云 android 操作历史,坚果云怎样将文件恢复到某一个历史版本?两招轻松搞定!...
  20. 利用css3实现3d立体特效--正方体

热门文章

  1. 豌豆荚研发管理经验分享-软件项目管理及绩效考核方法
  2. Android开发中容易被忽视的一些注意事项
  3. ChatGPT | Word文档如何更好地提取表格内容给ChatGPT
  4. 姐姐告诉我提眉的危害是什么,提眉术后多久可以恢复,纳尼,头大了
  5. 浅析LET无线移动网络为什么RTT抖动大为什么数据包乱序多
  6. 解决公众号开发 安卓正常 iOS调微信SDK失败 签名错误
  7. 技嘉B85M-D3V刷入NVME模块支持NVME SSD启动
  8. 前端高效开发和调试工具整理
  9. 转载:一碗牛肉面的思考
  10. 2023年IT岗位最火热的就业方向有哪些?网络安全必占一席