黑马程序员——Java基础---多线程

-----------android培训、java培训、java学习型技术博客、期待与您交流!------------一、多线程概述(一)多线程引入程序只有一个执行流程,这样的程序就是单线程程序。假如一个程序有多条执行流程,那么,该程序就是多线程程序。(二)多线程概述进程:正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资
摘要由CSDN通过智能技术生成

-----------android培训java培训、java学习型技术博客、期待与您交流!------------


一、多线程概述

(一)多线程引入

程序只有一个执行流程,这样的程序就是单线程程序。

假如一个程序有多条执行流程,那么,该程序就是多线程程序。

(二)多线程概述

进程:

正在运行的程序,是系统进行资源分配和调用的独立单位。

每一个进程都有它自己的内存空间和系统资源。

线程:

是进程中的单个顺序控制流,是一条执行路径

一个进程如果只有一条执行路径,则称为单线程程序。

一个进程如果有多条执行路径,则称为多线程程序。

进程与线程的关系:

线程是依赖于进程存在的,线程就是一个进程中子程序

1、多进程有什么意义呢?

单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。

对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。

因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。

多进程的作用不是提高执行速度,而是提高CPU的使用率。

2、多线程有什么意义呢?

多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。

而多线程却给了我们一个错觉:让我们认为多个线程是并发执行的。其实不是。

因为多个线程共享同一个进程的资源(堆内存和方法区),但是栈内存是独立的,一个线程一个栈。所以他们仍然是在抢CPU的资源执行。一个时间点上只有能有一个线程执行。而且谁抢到,这个不一定,所以,造成了线程运行的随机性。

3、那么什么又是并发呢?

大家注意两个词汇的区别:并行和并发。

前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。

后者是物理上同时发生,指在某一个时间点同时运行多个程序。

那么,我们能不能实现真正意义上的并发呢,是可以的,多个CPU就可以实现,不过你得知道如何调度和控制它们。

(三)Java程序运行原理

java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。

二、多线程的实现方案1

()步骤

继承Thread

重写run

创建子类对象

调用start()

()代码演示

package thread;

public class ThreadDemo {

public static void main(String[] args) {

CreateThread thtead = new CreateThread();

thtead.start();

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

System.out.println("main--"+i);

}

}

}

class CreateThread extends Thread{

public void run(){

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

System.out.println("run--"+i);

}

}

}

运行结果:

 

注意:每次运行的结果是不一样的,这是由于多线程的随机性,无法控制,这个是由CPU决定的。

三、如何获取和设置线程名称

Thread类的基本获取和设置方法

public final String getName():获取线程名字

public final void setName(String name):设置线程名字

其实通过构造方法也可以给线程起名字,直接在创建时传参

思考:

如何获取main方法所在的线程名称呢?

public static Thread currentThread():这样就可以获取任意方法所在的线程名称

代码演示:

package thread;

public class ThreadGet_Set {

public static void main(String[] args) {

ThreadName tmg = new ThreadName();

ThreadName tms = new ThreadName("线程");

tmg.start();

//获取当前正在执行的线程名字

String nametmg = Thread.currentThread().getName();

System.out.println(nametmg);

//设置线程名字

tms.setName("线程");

tms.start();

}

}

class ThreadName extends Thread{

//构造方法

public ThreadName(){}

public ThreadName(String name) {

super(name);

}

public void run(){

//获取线程名字

System.out.println(getName());

}

}

四、线程调度

假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?

线程有两种调度模型:

分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。 

Java使用的是抢占式调度模型。

设置和获取线程优先级方法

public final int getPriority()

public final void setPriority(int newPriority)

代码演示

package thread;

public class ThreadPriority{

public static void main(String[] args) {

Priority p = new Priority();

Priority p2 = new Priority();

int num = p.getPriority();

System.out.println("默认:"+num);

//设置优先级

p.setPriority(Thread.MIN_PRIORITY);

p2.setPriority(Thread.MAX_PRIORITY);

p.start();

p2.start();

}

}

class Priority extends Thread{

public void run(){

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

System.out.println(getName()+"run--"+i);

}

}

}

五、线程控制

()线程控制方法

1、线程休眠

public static void sleep(long millis)让线程在指定毫秒值内,休眠,时间一到,自动醒来继续执行

代码演示

package thread;

public class ThreadSleep {

public static void main(String[] args) {

new Sleep().start();

}

}

class Sleep extends Thread{

public void run(){

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

//调用方法

try{

Thread.sleep(2000);

}catch(InterruptedException ex){}

System.out.println("run--"+i);

}

}

}

2、线程加入

public final void join()

3、线程礼让

public static void yield()

4、后台线程

public final void setDaemon(boolean on)

代码演示

package thread;

/*

 * Thread 类方法setDaemon(boolean b)

 * 将线程设置为后台线程,传递是true,设置为后台线程

 * 守护线程

 * 程序中所有的线程都是守护线程的时候,JVM就自动退出了

 * setDaemon()方法必须在start()方法之前

 */

public class ThreadSetDaemon {

public static void main(String[] args) {

SetDaemon sd = new SetDaemon();

sd.setDaemon(true);

sd.start();

}

}

class SetDaemon extends Thread{

public void run(){

while (true) {

System.out.println("run--");

}

}

}

5、中断线程

public final void stop()

public void interrupt()

六、线程的生命周期图

七、多线程的实现方案2

实现Runnable接口

如何获取线程名称

如何给线程设置名称

实现接口方式的好处

可以避免由于Java单继承带来的局限性。

适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。

代码演示

package thread;

/*

 *  创建新线程第二种方式,实现接口方式

 *  对售票案例,进行改进

 *  延迟处理,出现错误的票数

 *  同步技术,保证线程安全性,同步代码块

 *  

 *  速度降低,数据安全

 */

class RunnableTicket implements Runnable {

private int tickets = 100;

private Object obj = new Object();

public void run() {

while (true) {

synchronized (obj) {

if (tickets > 0) {

try {

Thread.sleep(10);

catch (Exception ex) {

}

System.out.println(Thread.currentThread().getName()

出售第" + tickets--);

}

}

}

}

}

public class ThreadDemo {

public static void main(String[] args) {

RunnableTicket r = new RunnableTicket();

Thread t0 = new Thread(r);

Thread t1 = new Thread(r);

Thread t2 = new Thread(r);

t0.start();

t1.start();

t2.start();

}

}

八、两种方式的区别和线程的几种状态

1、两种创建方式的区别

继承Thread:线程代码存放在Thread子类run方法中。

实现Runnable:线程代码存放在接口子类run方法中。      

2、几种状态

被创建:等待启动,调用start启动。

运行状态:具有执行资格和执行权。

临时状态(阻塞):有执行资格,但是没有执行权。

冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。

消忙状态:stop()方法,或者run方法结束。

注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常。

3图解:

   

九、线程安全问题

1、导致安全问题的出现的原因:

当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。

简单的说就两点:

a、多个线程访问出现延迟。

b、线程随机性    。

注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

2、解决办法——同步

对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

在java中对于多线程的安全问题提供了专业的解决方式——synchronized(同步)

这里也有两种解决方式,一种是同步代码块,还有就是同步函数。都是利用关键字synchronized来实现。

a、同步代码块

用法:

synchronized(对象)

{需要被同步的代码}

同步可以解决安全问题的根本原因就在那个对象上。其中对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。

示例:

package thread;

/*

 *   模拟储户同时在多个窗口存钱

 *     账户余额0

 *     2个窗口同时存钱,每个窗口存3,每次存储100

 *     1 2 3 4 5 6

 *     

 *   存钱功能,银行的还是储户的,储户调用银行方法存钱,参数传递

 *   

 *    saveMoney 方法所有的代码,都是线程操作共享数据

 *    同步代码块,结构比较负责,能不能同步整个方法

 *    同步关键字写在方法上,返回值类型前

 *    

 *    非静态同步方法的锁,this本类对象引用

 *    静态方法同步的锁,也是一个对象,本类名字.class

 *    每个数据类型,JVM赋予静态属性,属性名字class

 *    属性获取到class文件对象

 */

class Bank{

private static int sum = 0 ;

public static synchronized  void saveMoney(int money){

//synchronized(Bank.class){

 sum = sum + money;

 System.out.println(sum);

//}

}

}

//定义储户类,同时调用银行的存钱功能

class Customer implements Runnable{

private Bank b = new Bank();

public void run(){

for(int x = 0 ; x <  3 ; x++){

    b.saveMoney(100);

}

}

}

public class ThreadDemo{

public static void main(String[] args) {

Customer c = new Customer();

Thread t0 = new Thread(c);

Thread t1 = new Thread(c);

t0.start();

t1.start();

}

}

3、同步的前提

        a,必须要有两个或者两个以上的线程。

        b,必须是多个线程使用同一个锁。

4、同步的利弊

        好处:解决了多线程的安全问题。

        弊端:多个线程需要判断锁,较为消耗资源。

5、如何寻找多线程中的安全问题

        a,明确哪些代码是多线程运行代码。

        b,明确共享数据。

        c,明确多线程运行代码中哪些语句是操作共享数据的。

十、静态函数的同步方式

        如果同步函数被静态修饰后,使用的锁是什么呢?

        通过验证,发现不在是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如:

        类名.class 该对象的类型是Class

这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class

经典示例:

package thread;

/* 

 加同步的单例设计模式————懒汉式 

 */

class Single {

private static Single s = null;

private Single() {

}

public static void getInstance() {

if (s == null) {

synchronized (Single.class) {

if (s == null)

s = new Single();

}

}

return s;

}

}

十一、死锁

        当同步中嵌套同步时,就有可能出现死锁现象。

示例:

package thread;

/* 

 写一个死锁程序 

 */

//定义一个类来实现Runnable,并复写run方法  

class LockTest implements Runnable {

private boolean flag;

LockTest(boolean flag) {

this.flag = flag;

}

public void run() {

if (flag) {

while (true) {

synchronized (LockClass.locka)// a

{

System.out.println(Thread.currentThread().getName()

"------if_locka");

synchronized (LockClass.lockb)// b

{

System.out.println(Thread.currentThread().getName()

"------if_lockb");

}

}

}

else {

while (true) {

synchronized (LockClass.lockb)// b

{

System.out.println(Thread.currentThread().getName()

"------else_lockb");

synchronized (LockClass.locka)// a

{

System.out.println(Thread.currentThread().getName()

"------else_locka");

}

}

}

}

}

}

// 定义两个锁

class LockClass {

static Object locka = new Object();

static Object lockb = new Object();

}

class DeadLock {

public static void main(String[] args) {

// 创建2个进程,并启动

new Thread(new LockTest(true)).start();

new Thread(new LockTest(false)).start();

}

}

结果:程序卡住,不能继续执行

       

十二、线程间通信

        其实就是多个线程在操作同一个资源,但是操作的动作不同。

1、使用同步操作同一资源的示例:

package thread;

/* 

 有一个资源 

 一个线程往里存东西,如果里边没有的话 

 一个线程往里取东西,如果里面有得话 

 */

//资源  

class Resource {

private String name;

private String sex;

private boolean flag = false;

public synchronized void setInput(String name, String sex) {

if (flag) {

try {

wait();

catch (Exception e) {

}// 如果有资源时,等待资源取出

}

this.name = name;

this.sex = sex;

flag = true;// 表示有资源

notify();// 唤醒等待

}

public synchronized void getOutput() {

if (!flag) {

try {

wait();

catch (Exception e) {

}// 如果木有资源,等待存入资源

}

System.out.println("name=" + name + "---sex=" + sex);// 这里用打印表示取出

flag = false;// 资源已取出

notify();// 唤醒等待

}

}

// 存线程

class Input implements Runnable {

private Resource r;

Input(Resource r) {

this.r = r;

}

public void run()// 复写run方法

{

int x = 0;

while (true) {

if (x == 0)// 交替打印张三和王羲之

{

r.setInput("张三"".....man");

else {

r.setInput("王羲之""..woman");

}

x = (x + 1) % 2;// 控制交替打印

}

}

}

// 取线程

class Output implements Runnable {

private Resource r;

Output(Resource r) {

this.r = r;

}

public void run()// 复写run方法

{

while (true) {

r.getOutput();

}

}

}

class ResourceDemo2 {

public static void main(String[] args) {

Resource r = new Resource();// 表示操作的是同一个资源

new Thread(new Input(r)).start();// 开启存线程

new Thread(new Output(r)).start();// 开启取线程

}

}

结果:部分截图

 

 几个小问题:

        1wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?

                a,这些方法存在与同步中。

                b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。

                c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。

        2wait(),sleep()有什么区别?

              wait():释放cpu执行权,释放锁。

              sleep():释放cpu执行权,不释放锁。

        3为甚么要定义notifyAll?

        因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。

2、JDK1.5中提供了多线程升级解决方案。

        将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。

升级解决方案的示例:

package thread;

/* 

 生产者生产商品,供消费者使用 

 有两个或者多个生产者,生产一次就等待消费一次 

 有两个或者多个消费者,等待生产者生产一次就消费掉 

 */

import java.util.concurrent.locks.*;

class Resource {

private String name;

private int count = 1;

private boolean flag = false;

// 多态

private Lock lock = new ReentrantLock();

// 创建两Condition对象,分别来控制等待或唤醒本方和对方线程

Condition condition_pro = lock.newCondition();

Condition condition_con = lock.newCondition();

// p1p2共享此方法

public void setProducer(String name) throws InterruptedException {

lock.lock();// 

try {

while (flag)

// 重复判断标识,确认是否生产

condition_pro.await();// 本方等待

this.name = name + "......" + count++;// 生产

System.out.println(Thread.currentThread().getName() + "...生产..."

this.name);// 打印生产

flag = true;// 控制生产\消费标识

condition_con.signal();// 唤醒对方

finally {

lock.unlock();// 解锁,这个动作一定执行

}

}

// c1c2共享此方法

public void getConsumer() throws InterruptedException {

lock.lock();

try {

while (!flag)

// 重复判断标识,确认是否可以消费

condition_con.await();

System.out.println(Thread.currentThread().getName() + ".消费."

this.name);// 打印消费

flag = false;// 控制生产\消费标识

condition_pro.signal();

finally {

lock.unlock();

}

}

}

// 生产者线程

class Producer implements Runnable {

private Resource res;

Producer(Resource res) {

this.res = res;

}

// 复写run方法

public void run() {

while (true) {

try {

res.setProducer("商品");

catch (InterruptedException e) {

}

}

}

}

// 消费者线程

class Consumer implements Runnable {

private Resource res;

Consumer(Resource res) {

this.res = res;

}

// 复写run

public void run() {

while (true) {

try {

res.getConsumer();

catch (InterruptedException e) {

}

}

}

}

class ProducerConsumer {

public static void main(String[] args) {

Resource res = new Resource();

new Thread(new Producer(res)).start();// 第一个生产线程 p1

new Thread(new Consumer(res)).start();// 第一个消费线程 c1

new Thread(new Producer(res)).start();// 第二个生产线程 p2

new Thread(new Consumer(res)).start();// 第二个消费线程 c2

}

}

运行结果:部分截图

       

十三、停止线程

在JDK 1.5版本之前,有stop停止线程的方法,但升级之后,此方法已经过时。

那么现在我们该如果停止线程呢?

只有一种办法,那就是让run方法结束。

1、开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。

如:run方法中有如下代码,设置一个flag标记

public void run()  

{  

    while(flag)  

    {     

        System.out.println(Thread.currentThread().getName()+"....run");  

    }  

那么只要在主函数或者其他线程中,在该线程执行一段时间后,将标记flag赋值false,该run方法就会结束,线程也就停止了。

2、上面的1方法可以解决一般情况,但是有一种特殊情况:就是当线程处于冻结状态。就不会读取到标记。那么线程就不会结束。

当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法interrupt();

如:

package thread;

class StopThread implements Runnable {

private boolean flag = true;

public void run() {

while (flag) {

System.out.println(Thread.currentThread().getName() + "....run");

}

}

public void changeFlag() {

flag = false;

}

}

class StopThreadDemo {

public static void main(String[] args) {

StopThread st = new StopThread();

Thread t1 = new Thread(st);

Thread t2 = new Thread(st);

t1.start();

t2.start();

int num = 0;

while (true) {

if (num++ == 60) {

t1.interrupt();// 清除冻结状态

t2.interrupt();

st.changeFlag();// 改变循环标记

break;

}

System.out.println(Thread.currentThread().getName() + "......."

+ num);

}

System.out.println("over");

}

}

运行结果:部分截图

      

十四、什么时候写多线程?

当某些代码需要同时被执行时,就用单独的线程进行封装。

示例:

package thread;

class ThreadTest {

public static void main(String[] args) {

// 一条线程

new Thread() {

public void run() {

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

System.out.println(Thread.currentThread().toString()

"....." + x);

}

}

}.start();

// 又是一条线程

Runnable r = new Runnable() {

public void run() {

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

System.out.println(Thread.currentThread().getName()

"....." + x);

}

}

};

new Thread(r).start();

// 可以看作主线程

for (int x = 0; x < 1000; x++) {

System.out.println("Hello World!");

}

}

}

 

-----------android培训java培训、java学习型技术博客、期待与您交流!------------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值