java学习笔记14--多线程编程基础1

多线程编程基础

多进程

一个独立程序的每一次运行称为一个进程,例如:用字处理软件编辑文稿时,同时打开mp3播放程序听音乐,这两个独立的程序在同时运行,称为两个进程

  • 进程要占用相当一部分处理器时间和内存资源
  • 进程具有独立的内存空间
  • 通信很不方便,编程模型比较复杂  

多线程

一个程序中多段代码同时并发执行,称为多线程,线程比进程开销小,协作和数据交换容易

Java是第一个支持内置线程操作的主流编程语言,多数程序设计语言支持多线程要借助于操作系统“原语(primitives)”

Thread类

直接继承了Object类,并实现了Runnable接口。位于java.lang包中封装了线程对象需要的属性和方法

继承Thread类——创建多线程的方法之一,类派生一个子类,并创建子类的对象,子类应该重写Thread类的run方法,写入需要在新线程中执行的语句段。调用start方法来启动新线程,自动进入run方法。

实例1)在新线程中完成计算某个整数的阶乘

class FactorialThread extends Thread {
    private int num;
    public FactorialThread(int num) {
        this.num = num;
    }
    public void run() {
        int i = num;
        int result = 1;
        System.out.println("new thread started");
        while(i > 0) {
            result = result * i;
            i--;
        }
        System.out.println("The factorial of " + num + " is " + result);
        System.out.println("new thread ends");
    }
}

public class javatest {
    public static void main(String args[]) {
        System.out.println("main thread start");
        FactorialThread thread = new FactorialThread(10);
        thread.start();
        System.out.println("main thread ends");

   }
}

运行结果:

main thread start
main thread ends
new thread started
The factorial of 10 is 3628800
new thread ends

结果说明:

main线程已经执行完后,新线程才执行完。main函数调用thread.start()方法启动新线程后并不等待其run方法返回就继续运行,thread.run函数在一边独自运行,不影响原来的main函数的运行

如果启动新线程后希望主线程多持续一会再结束,可在start语句后加上让当前线程(这里当然是main)休息1毫秒的语句:

try {  Thread.sleep(1); }  catch(Exception e){};

常用API方法:

 名称  说明
public Thread()

构造一个新的线程对象,默认名为Thread-n,n是从0开始递增的整数

public Thread(Runnable target)

构造一个新的线程对象,以一个实现Runnable接口的类的对象为参数。默认名为Thread-n,n是从0开始递增的整数

public Thread(String name)

构造一个新的线程对象,并同时指定线程名

public static Thread currentThread()

返回当前正在运行的线程对象

public static void yield()

使当前线程对象暂停,允许别的线程开始运行

public static void sleep(long millis)

使当前线程暂停运行指定毫秒数,但此线程并不失去已获得的锁旗标。

 public void start()  启动线程,JVM将调用此线程的run方法,结果是将同时运行两个线程,当前线程和执行run方法的线程
 public void run()  Thread的子类应该重写此方法,内容应为该线程应执行的任务。
 public final void stop()  停止线程运行,释放该线程占用的对象锁旗标。
 public void interrupt()  中断此线程
 public final void join()  如果此前启动了线程A,调用join方法将等待线程A死亡才能继续执行当前线程
 public final void join(long millis)  如果此前启动了线程A,调用join方法将等待指定毫秒数或线程A死亡才能继续执行当前线程
 public final void setPriority(int newPriority)  设置线程优先级
 public final void setDaemon(Boolean on)  设置是否为后台线程,如果当前运行线程均为后台线程则JVM停止运行。这个方法必须在start()方法前使用
 public void setName(String name)  更改本线程的名称为指定参数
 public final boolean isAlive()  测试线程是否处于活动状态,如果线程被启动并且没有死亡则返回true
 public final void checkAccess()  判断当前线程是否有权力修改调用此方法的线程

实例2):创建3个新线程,每个线程睡眠一段时间(0~6秒),然后结束。

class TestThread extends Thread {
    private int sleeptime;
    public TestThread(String name) {
        super(name);
        sleeptime = (int)(Math.random() * 6000);
    }
    public void run() {
        try {
            System.out.println(getName() + "going to sleep for " +
                    sleeptime);
            Thread.sleep(sleeptime);
        } catch (InterruptedException ex) {

        }
        System.out.println(getName() + " finished");
    }
}

public class javatest {
    public static void main(String args[]) {
        TestThread thr1 = new TestThread("thread1");
        TestThread thr2 = new TestThread("thread2");
        TestThread thr3 = new TestThread("thread3");
        System.out.println("staring threads");
        thr1.start();
        thr2.start();
        thr3.start();
        System.out.println("Thread started, main ends");
   }
}

运行结果:

staring threads
thread1going to sleep for 2925
Thread started, main ends
thread3going to sleep for 1222
thread2going to sleep for 5007
thread3 finished
thread1 finished
thread2 finished

Runnable接口

Thread类实现了Runnable接口,只有一个run()方法,更便于多个线程共享资源。Java不支持多继承,如果
已经继承了某个基类,便需要实现Runnable接口来生成多线程以实现runnable的对象为参数建立新的线程,start方法启动线程就会运行
run()方法

实例3)使用Runnable接口实现实例1的功能:

class FactorialThread implements Runnable {
    private int num;
    public FactorialThread(int num) {
        this.num = num;
    }
    public void run() {
        int i = num;
        int result = 1;
        while(i > 0) {
            result = result * i;
            i--;
        }
        System.out.println("The factorial of " + num + " is " + result);
        System.out.println("new thread ends");
    }
}

public class javatest {
    public static void main(String args[]) {
        System.out.println("main thread starts");
        FactorialThread t = new FactorialThread(10);
        new Thread(t).start();
        System.out.println("new thread started main thread ends");
   }
}

实例4)使用Runnable接口实现实例2的功能:

class TestThread implements Runnable {
    private int sleepTime;
    public TestThread() {
        sleepTime = (int)(Math.random() * 6000);
    }
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() +
                    " going to sleep for " + sleepTime);
            Thread.sleep(sleepTime);
        } catch(InterruptedException ex) {

        };
        System.out.println(Thread.currentThread().getName() + " finished");
    }
}
public class javatest {
    public static void main(String args[]) {
        TestThread thread1 = new TestThread();
        TestThread thread2 = new TestThread();
        TestThread thread3 = new TestThread();
        System.out.println("Starting threads");

        new Thread(thread1, "Thread1").start();
        new Thread(thread2, "Thread2").start();
        new Thread(thread3, "Thread3").start();
        System.out.println("Threads started, main ends");
   }
}

线程间的数据共享

用同一个实现了Runnable接口的对象作为参数创建多个线程

多个线程共享同一对象中的相同的数据

修改实例4,只用一个Runnable类型的对象为参数创建3个新线程:

class TestThread implements Runnable {
    private int sleepTime;
    public TestThread() {
        sleepTime = (int)(Math.random() * 6000);
    }
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() +
                    " going to sleep for " + sleepTime);
            Thread.sleep(sleepTime);
        } catch(InterruptedException ex) {

        };
        System.out.println(Thread.currentThread().getName() + " finished");
    }
}
public class javatest {
    public static void main(String args[]) {
        TestThread threadobj = new TestThread();
        System.out.println("Starting threads");
        new Thread(threadobj, "Thread1").start();
        new Thread(threadobj, "Thread2").start();
        new Thread(threadobj, "Thread3").start();
        System.out.println("Threads started, main ends");
   }
}

说明:因为是用一个Runnable类型对象创建的3个新线程,这三个线程就共享了这个对象的私有成员sleepTime,在本次运行中,三个线程都休眠了966毫秒

独立的同时运行的线程有时需要共享一些数据并且考虑到彼此的状态和动作

举个例子:用三个线程模拟三个售票口,总共出售200张票

class SellTicks implements Runnable {
    private int tickets = 200;
    public void run() {
        while(tickets > 0) {
            System.out.println(Thread.currentThread().getName() +
                    " is selling ticket " + tickets--);
        }
    }
}
public class javatest {
    public static void main(String args[]) {
        SellTicks t = new SellTicks();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
   }
}

说明:

在这个例子中,创建了3个线程,每个线程调用的是同一个SellTickets对象中的run()方法,访问的是同一个对象中的变量(tickets)

如果是通过创建Thread类的子类来模拟售票过程,再创建3个新线程,则每个线程都会有各自的方法和变量,虽然方法是相同的,但变量却是各有200张票,因而结果将会是各卖出200张票,和原意就不符了

多线程的同步控制

有时线程之间彼此不独立、需要同步

线程间的互斥

同时运行的几个线程需要共享一个(些)数据

共享的数据,在某一时刻只允许一个线程对其进行操作

“生产者/消费者” 问题

假设有一个线程负责往数据区写数据,另一个线程从同一数据区中读数据,两个线程可以并行执行,如果数据区已满,生产者要等消费者取走一些数据后才能再写。当数据区空时,消费者要等生产者写入一些数据后再取

举个例子:

用两个线程模拟存票、售票过程

假定开始售票处并没有票,一个线程往里存票,另外一个线程则往出卖票,我们新建一个票类对象,让存票和售票线程都访问它。本例采用两个线程共享同一个数据对象来实现对同一份数据的操作

class Tickets {
    int number = 0;    //票号
    int size;    //总票数
    boolean available = false;    //表示目前是否有票可售
    public Tickets(int size) {    //构造函数,传入总票参数
        this.size = size;
    }
}
class Producer extends Thread {
    Tickets t = null;
    public Producer(Tickets t) {
        this.t = t;
    }
    public void run() {
        while(t.number < t.size) {
            System.out.println("Producer puts ticket" + (++t.number));
            t.available = true;
        }
    }
}
class Consumer extends Thread {
    Tickets t = null;
    int i = 0;
    public Consumer(Tickets t) {
        this.t = t;
    }
    public void run() {
        while(i < t.size) {
            if(t.available == true && i <= t.number)
                System.out.println("Consumer buys ticket" + (++i));
            if(i == t.number)
                t.available = false;
        }
    }
}
public class TicketSell {
    public static void main(String[] args) {
        Tickets t = new Tickets(10);
        new Consumer(t).start();
        new Producer(t).start();
    }
}

运行结果:

Producer puts ticket1
Producer puts ticket2
Consumer buys ticket1
Producer puts ticket3
Consumer buys ticket2
Producer puts ticket4
Producer puts ticket5
Producer puts ticket6
Producer puts ticket7
Producer puts ticket8
Producer puts ticket9
Producer puts ticket10
Consumer buys ticket3
Consumer buys ticket4
Consumer buys ticket5
Consumer buys ticket6
Consumer buys ticket7
Consumer buys ticket8
Consumer buys ticket9
Consumer buys ticket10

通过让两个线程操纵同一个票类对象,实现了数据共享的目的,

线程同步(Synchronization)

互斥:许多线程在同一个共享数据上操作而互不干扰,同一时刻只能有一个线程访问该共享数据。因此有些方法或程序段在同一时刻只能被一个线程执行,称之为监视区

协作:多个线程可以有条件地同时操作共享数据。执行监视区代码的线程在条件满足的情况下可以允许其它线程进入监视区

synchronized ——线程同步关键字

用于指定需要同步的代码段或方法,也就是监视区

可实现与一个锁旗标的交互。例如:

synchronized(对象){ 代码段 }

synchronized的功能是:首先判断对象的锁旗标是否在,如果在就获得锁旗标,然后就可以执行紧随其后的代码段;如果对象的锁旗标不在(已被其他线程拿走),就进入等待状态,直到获得锁旗标

当被synchronized限定的代码段执行完,就释放锁旗标

互斥:存票线程和售票线程应保持互斥关系。即售票线程执行时不进入存票线程、存票线程执行时不进入售票线程

Java 使用监视器机制

–每个对象只有一个“锁旗标” ,利用多线程对“锁旗标”的争夺实现线程间的互斥

–当线程A获得了一个对象的锁旗标后,线程B必须等待线程A完成规定的操作、并释放出锁旗标后,才能获得该对象的锁旗标,并执行线程B中的操作

将需要互斥的语句段放入synchronized(object){}语句框中,且两处的object是相同的

修改上面的代码:

class Producer extends Thread {
    Tickets t = null;
    public Producer(Tickets t) {
        this.t = t;
    }
    public void run() {
        while(t.number < t.size) {
            synchronized (t) {    //申请对象t的锁旗标
                System.out.println("Producer puts ticket" + (++t.number));
                t.available = true;
            } //释放对象t的锁旗标
        }
    }
}
class Consumer extends Thread {
    Tickets t = null;
    int i = 0;
    public Consumer(Tickets t) {
        this.t = t;
    }
    public void run() {
        while(i < t.size) {
            synchronized (t) {    ////申请对象t的锁旗标
                if(t.available == true && i <= t.number)
                    System.out.println("Consumer buys ticket" + (++i));
                if(i == t.number)
                    t.available = false;
            }
        }    //释放对象t的锁旗标
    }
}

运行结果:

Producer puts ticket1
Producer puts ticket2
Producer puts ticket3
Producer puts ticket4
Producer puts ticket5
Producer puts ticket6
Producer puts ticket7
Producer puts ticket8
Producer puts ticket9
Producer puts ticket10
Consumer buys ticket1
Consumer buys ticket2
Consumer buys ticket3
Consumer buys ticket4
Consumer buys ticket5
Consumer buys ticket6
Consumer buys ticket7
Consumer buys ticket8
Consumer buys ticket9
Consumer buys ticket10

说明:

1、存票程序段和售票程序段为获得同一对象的锁旗标而实现互斥操作

2、当线程执行到synchronized的时候,检查传入的实参对象,并申请得到该对象的锁旗标。如果得不到,那么线程就被放到一个与该对象锁旗标相对应的等待线程池中。直到该对象的锁旗标被归还,池中的等待线程才能重新去获得锁旗标,然后继续执行下去

3、除了可以对指定的代码段进行同步控制之外,还可以定义整个方法在同步控制下执行,只要在方法定义前加上synchronized关键字即可

把上面的程序再修改一下:

class Tickets {
    int number = 0;    //票号
    int size;    //总票数
    int i = 0;    //售票序号
    boolean available = false;    //表示是否有票可售
    public Tickets(int size) {    //构造函数,传入总票参数
        this.size = size;
    }
    public synchronized void put() {    //同步方法,实现存票的功能
        System.out.println("Producer puts ticket" + (++number));
        available = true;
    }
    public synchronized void sell() {    //同步方法,实现售票的功能
        if(available == true && i <= number)
            System.out.println("Consumer buys ticket" + (++i));
        if(i == number)
            available = false;
    }
}
class Producer extends Thread {
    Tickets t = null;
    public Producer(Tickets t) {
        this.t = t;
    }
    public void run() {
        //如果存票数小于限定总量,则不断存入票
        while(t.number < t.size)
            t.put();
    }
}
class Consumer extends Thread {
    Tickets t = null;
    int i = 0;
    public Consumer(Tickets t) {
        this.t = t;
    }
    public void run() {
        //如果售票数小于限定总量,则不断售票
        while(i < t.size)
            t.sell();
    }
}

线程之间的通信

为了更有效地协调不同线程的工作,需要在线程间建立沟通渠道,通过线程间的“对话”来解决线程间的同步问题

java.lang.Object 类的一些方法为线程间的通讯提供了有效手段

wait() 
如果当前状态不适合本线程执行,正在执行同步代码(synchronized)的某个线程A调用该方法(在对象x上),该线程暂停执行而进入对象x的等待
池,并释放已获得的对象x的锁旗标。线程A要一直等到其他线程在对象x上调用notify或notifyAll方法,才能够在重新获得对象x的锁旗标后继
续执行(从wait语句后继续执行)

notify()和notifyAll()方法:

notify() 随机唤醒一个等待的线程,本线程继续执行

线程被唤醒以后,还要等发出唤醒消息者释放监视器,这期间关键数据仍可能被改变

被唤醒的线程开始执行时,一定要判断当前状态是否适合自己运行

notifyAll() 唤醒所有等待的线程,本线程继续执行

修改上面的例子,使每存入一张票,就售一张票,售出后,再存入。 代码如下:

package javatest;

import com.sun.org.apache.xalan.internal.xslt.Process;

class Tickets {
    int number = 0;    //票号
    int size;    //总票数
    int i = 0;    //售票序号
    boolean available = false;    //表示是否有票可售
    public Tickets(int size) {    //构造函数,传入总票参数
        this.size = size;
    }
    public synchronized void put() {
        if(available) {    //如果还有存票待售,则存票线程等待
            try {
                wait();
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
        System.out.println("Producer puts ticket" + (++number));
        available = true;
        notify();    //存票后唤醒售票线程开始售票
    }
    public synchronized void sell() {
        if(!available) {    //如果没有存票,则售票线程等待
            try {
                wait();
            } catch(Exception e) {

            }
        }
        System.out.println("Consumer buys ticket" + (number));
        available = false;
        notify();
        if(number == size)
            number = size + 1;    //在售完最后一张票后,
                        //设置一个结束标志,number>size表示售票结束
    }
}
class Producer extends Thread {
    Tickets t = null;
    public Producer(Tickets t) {
        this.t = t;
    }
    public void run() {
        //如果存票数小于限定总量,则不断存入票
        while(t.number < t.size)
            t.put();
    }
}
class Consumer extends Thread {
    Tickets t = null;
    int i = 0;
    public Consumer(Tickets t) {
        this.t = t;
    }
    public void run() {
        //如果售票数小于限定总量,则不断售票
        while(i < t.size)
            t.sell();
    }
}
public class TicketSell {
    public static void main(String[] args) {
        Tickets t = new Tickets(10);
        new Consumer(t).start();
        new Producer(t).start();
    }
}

运行结果:

Producer puts ticket1
Consumer buys ticket1
Producer puts ticket2
Consumer buys ticket2
Producer puts ticket3
Consumer buys ticket3
Producer puts ticket4
Consumer buys ticket4
Producer puts ticket5
Consumer buys ticket5
Producer puts ticket6
Consumer buys ticket6
Producer puts ticket7
Consumer buys ticket7
Producer puts ticket8
Consumer buys ticket8
Producer puts ticket9
Consumer buys ticket9
Producer puts ticket10
Consumer buys ticket10

程序说明:

当Consumer线程售出票后,available值变为false,当Producer线程放入票后,available值变为true

只有available为true时,Consumer线程才能售票,否则就必须等待Producer线程放入新的票后的通知

只有available为false时,Producer线程才能放票,否则必须等待Consumer线程售出票后的通知

后台线程

也叫守护线程,通常是为了辅助其它线程而运行的线程

它不妨碍程序终止

一个进程中只要还有一个前台线程在运行,这个进程就不会结束;如果一个进程中的所有前台线程都已经结束,那么无论是否还有未结束的后台线程,这个进程都会结束

“垃圾回收”便是一个后台线程

如果对某个线程对象在启动(调用start方法)之前调用了setDaemon(true)方法,这个线程就变成了后台线程

时间: 2024-09-13 17:48:39

java学习笔记14--多线程编程基础1的相关文章

java学习笔记15--多线程编程基础2

线程的生命周期 1.线程的生命周期 线程从产生到消亡的过程 一个线程在任何时刻都处于某种线程状态(thread state)  线程生命周期状态图 诞生状态 线程刚刚被创建 就绪状态 线程的 start 方法已被执行 线程已准备好运行 运行状态 处理机分配给了线程,线程正在运行 阻塞状态(Blocked) 在线程发出输入/输出请求且必须等待其返回 遇到用synchronized标记的方法而未获得其监视器暂时不能进入执行时 休眠状态(Sleeping) 执行sleep方法而进入休眠 死亡状态 线程

Java学习笔记,适用于零基础的小伙伴

1. 成员变量与局部变量的区别     1.作用域:         成员变量的作用域是整个类中都可以访问:         局部变量是的作用域是定义它的{}内, {}中的语句执行完它就被回收:     2.初始值:         局部变量使用前必须手动赋值, 系统不会赋默认值:成员变量系统会赋默认初始值.     3.所在内存区域:         局部变量在方法运行的栈内存里,在栈里,会自动释放:         成员变量在对象里,对象在堆里,成员变量也在堆里,不使用的对象会被垃圾回收机制

浅谈.NET下的多线程和并行计算(九)Winform中多线程编程基础 下

在之前的文章中我们介绍过两种Timer和BackgroundWorker组件,在上文中我们提到过,强烈建议在UI 线程上操作控件,否则很容易产生人品问题.可以想到,上次介绍的两个Timer基于ThreadPool,回调方 法运行于不同于UI线程的新线程上,在这个方法中操作控件需要进行 Invoke或BeginInvoke.其实,还有 第三种System.Windows.Forms.Timer,它可以让回调事件在UI线程上执行,我们来做一个实验比较一下 System.Windows.Forms.T

浅谈.NET下的多线程和并行计算(八)Winform中多线程编程基础 上

首先我们创建一个Winform的应用程序,在上面添加一个多行文本框和一个按钮控件,按钮的事件如下 : Thread.Sleep(1000); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; i++) sb.Append("test"); string s = sb.ToString(); textBox1.Text = s; 首先我们可以把这个操作理解为一个非常耗时的操作,它至少占用1秒的时间.

javascript学习笔记_浅谈基础语法,类型,变量_基础知识

基础语法.类型.变量 非数字值的判断方法:(因为Infinity和NaN他们不等于任何值,包括自身) 1.用x != x ,当x为NaN时才返回true; 2.用isNaN(x) ,当x为NaN或非数字值时,返回true; 3.用isFinity(x),在x不是NaN.Infinity.-Infinity时返回true; 虽然(字符串.数字.布尔值)不是对象,他们的属性是只读的,但也可以像操作对象一样来引用他们的属性和方法,原理: javascript构造一个(String.Number.Boo

Java多线程编程基础之线程和多线程

[写在前面] 随着计算机技术的发展,编程模型也越来越复杂多样化.但多线程编程模型 是目前计算机系统架构的最终模型.随着CPU主频的不断攀升,X86架构的硬件已 经成为瓶,在这种架构的CPU主频最高为4G.事实上目前3.6G主频的CPU已经接近了顶峰. 如果不能从根本上更新当前CPU的架构(在很长一段时间内还不太可能),那么 继续提高CPU性能的方法就是超线程CPU模式.那么,作业系统.应用程序要发挥 CPU的最大性能,就是要改变到以多线程编程模型为主的并行处理系统和并发式 应用程序. 所以,掌握

java 学习笔记(入门篇)_java的基础语法_java

前言 学习完了第一个java程序,之后就来系统的学习java.先从基础语法开始,这个语法你也可以理解为英语或是汉语里面的语法,只不过大家各有各的特点和区别.学习编程其实也是一个编程语言的学习过程.我们在学习英语的时候都说,要想学习好英语一定要动口说,那么在学习编程的时候你一定要动手写.编程无非就是一个写代码的过程,首要就是你心中要有基础,没有基础怎么去写呢,就像一篇好文章,没有好文笔是写不出来的.好文笔不仅靠积累,更要靠创造,编程亦是如此.java是一个面向对象的语言,在写代码的过程中,接触最多

Python 爬虫学习笔记之多线程爬虫_python

XPath 的安装以及使用 1 . XPath 的介绍 刚学过正则表达式,用的正顺手,现在就把正则表达式替换掉,使用 XPath,有人表示这太坑爹了,早知道刚上来就学习 XPath 多省事 啊.其实我个人认为学习一下正则表达式是大有益处的,之所以换成 XPath ,我个人认为是因为它定位更准确,使用更加便捷.可能有的人对 XPath 和正则表达式的区别不太清楚,举个例子来说吧,用正则表达式提取我们的内容,就好比说一个人想去天安门,地址的描述是左边有一个圆形建筑,右边是一个方形建筑,你去找吧,而使

Java多线程编程基础之线程对象

在进入java平台的线程对象之前,基于基础篇(一)的一些问题,我先插入两个基本概念. [线程的并发与并行] 在单CPU系统中,系统调度在某一时刻只能让一个线程运行,虽然这种调试机制有多种形式(大多数是时间片轮巡为主),但无论如何,要通过不断切换需要运行的线程让其运行的方式就叫并发(concurrent).而在多CPU系统中,可以让两个以上的线程同时运行,这种可以同时让两个以上线程同时运行的方式叫做并行(parallel). 在上面包括以后的所有论述中,请各位朋友谅解,我无法用最准确的词语来定义储