java8 第七章-----多线程

7.1、线程基本知识

进程与线程:

进程(Process)是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。

线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行。

在 windows 下查看进程的命令:

  • 查看进程:tasklist
  • 杀死进程:taskkill /pid <PID>

在 linux 下查看进程的命令:

  • 查看进程:ps -fe
  • 查看线程:top -H -p <PID>
  • 杀死进程:kill <PID>

在 java 中查看进程的命令:

  • 查看Java进程:jps
  • 查看Java线程:jstack <PID>
  • 图形化的工具:jconsole

并行与并发:

单核 CPU 下,线程实际还是 串行执行 的。操作系统中有一个组件叫做任务调度器,将 CPU 的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 CPU 在线程间的切换非常快,人类感觉是同时运行的 。总结为一句话就是:微观串行,宏观并行,一般会将这种线程轮流使用 CPU 的做法称为并发(Concurrent)。

多核 CPU下,每个核都可以调度运行线程,这时候线程可以是并行(Parallel)的。

同步与异步:

以调用方角度来讲:

  • 需要等待结果返回后才能继续运行就是同步。
  • 不需要等待结果返回就能继续运行就是异步。

7.2、线程创建方式 

  • 方式一:
Thread thread = new Thread(){
    @Override
    public void run() {
        System.out.println("thread run ...");
    }
};

thread.start();

      简化后:

Thread thread = new Thread(() -> System.out.println("thread run ..."));

thread.start();
  • 方式二:
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("runnable run ...");
    }
});

thread.start();

      简化后:

Thread thread = new Thread(() -> System.out.println("runnable run ..."));

thread.start();
  • 方式三:
Callable<Integer> callable = new Callable() {
    @Override
    public Object call() throws Exception {
        System.out.println("callable run ...");
        return 521;
    }
};
FutureTask futureTask = new FutureTask(callable);
Thread thread = new Thread(futureTask);
thread.start();

      简化后:

Thread thread = new Thread(new FutureTask(() -> {
    System.out.println("callable run ...");
    return 521;
}));

thread.start();

7.3、线程基本方法

注意:标黄色的方法代表是 static 方法,可直接类名调用,无需创建对象。

 

 7.4、线程安全问题

两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?

public class Main {
    static int counter = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter++;
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter--;
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter);
    }
}

以上的结果可能是正数、负数、零。

一个程序运行多个线程本身是没有问题的,问题出现在多个线程访问共享资源:多个线程读共享资源其实也没有问题,问题本质在于多个线程对共享资源读写操作时发生指令交错,就会出现问题。

一段代码块内,如果存在对共享资源的多线程读写操作,称这段代码块为临界区(Critical Section)。

例如,下面代码中的临界区:
 

static int counter = 0;

static void increment()
// 临界区
{
	counter++;
}

static void decrement()
// 临界区
{
	counter--;
}

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件(Race Condition)。

为了避免临界区的竞态条件发生,有多种手段可以达到目的。

  • 阻塞式的解决方案:synchronized,Lock
  • 非阻塞式的解决方案:原子变量

我们使用阻塞式的解决方案:synchronized,来解决上述问题,即俗称的【对象锁】,它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换,以此避免线程安全问题。

虽然 Java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们还是有区别的:

  • 互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
  • 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点

synchronized的语法:
 

synchronized(对象)
{
	临界区
}

synchronized改进后:

public class Main {
    static int counter = 0;
    static final Object room = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (room) {
                    counter++;
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (room) {
                    counter--;
                }
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter);
    }
}

synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。

方法上的 synchronized

class Test{
    public synchronized void test() {
    }
}
等价于
class Test{
    public void test() {
        synchronized(this) {
        }
    }
}
class Test{
    public synchronized static void test() {
    }
}
等价于
class Test{
    public static void test() {
        synchronized(Test.class) {
        }
    }
}

成员变量和静态变量是否线程安全?

  • 如果它们没有共享,则线程安全
  • 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况

       1、   如果只有读操作,则线程安全
        2、  如果有读写操作,则这段代码是临界区,需要考虑线程安全


方法上(内)局部变量是否线程安全?

  • 局部变量是线程安全的
  • 但局部变量引用的对象则未必

        1、 如果该对象没有逃离方法的作用访问,它是线程安全的
        2、 如果该对象逃离方法的作用范围,需要考虑线程安全

7.5、线程并发工具

7.5.1、ReentrantLock

锁介绍

ReentrantLock 相对于 synchronized,它具备如下特点:

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量
  • 与 synchronized 一样,都支持可重入

基本语法:

// 创建锁
ReentrantLock reentrantLock = new ReentrantLock();
// 获取锁
reentrantLock.lock();
try {
    // 临界区
} finally {
    // 释放锁
    reentrantLock.unlock();
}

可重入

可重入是指同一个线程,如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁,如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。

public class Main {
    public static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        method1();
    }

    public static void method1() {
        lock.lock();
        try {
            System.out.println("execute method1");
            method2();
        } finally {
            lock.unlock();
        }
    }

    public static void method2() {
        lock.lock();
        try {
            System.out.println("execute method2");
            method3();
        } finally {
            lock.unlock();
        }
    }

    public static void method3() {
        lock.lock();
        try {
            System.out.println("execute method3");
        } finally {
            lock.unlock();
        }
    }
}
execute method1
execute method2
execute method3

可打断

ReentrantLock 支持可打断,如果是不可中断模式,即使使用了 interrupt 也不会让等待中断。

public class Main {
    public static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("t1启动...");
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("t1等锁的过程中被打断");
                return;
            }
            try {
                System.out.println("t1获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        System.out.println("main获得了锁");
        t1.start();
        try {
            Thread.sleep(1000);
            t1.interrupt();
            System.out.println("main执行打断");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
main获得了锁
t1启动...
main执行打断
t1等锁的过程中被打断
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.company.Main.lambda$main$0(Main.java:12)

锁超时

public class Main {
    public static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("t1启动...");
            if (!lock.tryLock()) {
                System.out.println("t1获取失败,返回");
                return;
            }
            try {
                System.out.println("t1获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        System.out.println("main获得了锁");
        t1.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
main获得了锁
t1启动...
t1获取失败,返回

公平锁

ReentrantLock 默认是不公平的,改为公平锁语法如下,公平锁一般没有必要,会降低并发度。

ReentrantLock lock = new ReentrantLock(true);

多条件

synchronized 中也有条件变量,就是 waitSet 休息室,当条件不满足时进入 waitSet 等待,ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比 synchronized 是那些不满足条件的线程都在一间休息室等消息,而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒。

使用要点:

  • await 前需要获得锁
  • await 执行后,会释放锁,进入 conditionObject 等待
  • await 的线程被唤醒(或打断、或超时)去重新竞争 lock 锁
  • 竞争 lock 锁成功后,从 await 后继续执行
     
public class Main {
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition waitCigaretteQueue = lock.newCondition();
    public static Condition waitbreakfastQueue = lock.newCondition();
    public static volatile boolean hasCigrette = false;
    public static volatile boolean hasBreakfast = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            try {
                lock.lock();
                while (!hasCigrette) {
                    try {
                        waitCigaretteQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("小明等到了它的烟");
            } finally {
                lock.unlock();
            }
        },"小明").start();
        new Thread(() -> {
            try {
                lock.lock();
                while (!hasBreakfast) {
                    try {
                        waitbreakfastQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("小红等到了它的早餐");
            } finally {
                lock.unlock();
            }
        },"小红").start();
        Thread.sleep(1000);
        sendBreakfast();
        Thread.sleep(1000);
        sendCigarette();
    }

    private static void sendCigarette() {
        lock.lock();
        try {
            System.out.println("送烟来了");
            hasCigrette = true;
            waitCigaretteQueue.signal();
        } finally {
            lock.unlock();
        }
    }

    private static void sendBreakfast() {
        lock.lock();
        try {
            System.out.println("送早餐来了");
            hasBreakfast = true;
            waitbreakfastQueue.signal();
        } finally {
            lock.unlock();
        }
    }
}

小测验

线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现?

class AwaitSignal extends ReentrantLock {
    private int loopNumber;// 循环次数

    public AwaitSignal(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    public void start(Condition first) {
        this.lock();
        try {
            first.signal();
        } finally {
            this.unlock();
        }
    }

    public void print(String str, Condition current, Condition next) {
        for (int i = 0; i < loopNumber; i++) {
            this.lock();
            try {
                current.await();
                System.out.print(str);
                next.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                this.unlock();
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        AwaitSignal as = new AwaitSignal(5);
        Condition aWaitSet = as.newCondition();
        Condition bWaitSet = as.newCondition();
        Condition cWaitSet = as.newCondition();
        new Thread(() -> {
            as.print("a", aWaitSet, bWaitSet);
        }).start();
        new Thread(() -> {
            as.print("b", bWaitSet, cWaitSet);
        }).start();
        new Thread(() -> {
            as.print("c", cWaitSet, aWaitSet);
        }).start();
        as.start(aWaitSet);
    }
}
abcabcabcabcabc

7.5.2、ReentrantReadWriteLock

当读操作远远高于写操作时,这时候使用 读写锁 让 读-读 可以并发,提高性能。

加解读锁

rw.readLock().lock();
rw.readLock().unlock();

加解写锁

rw.writeLock().lock();
rw.writeLock().unlock();

7.5.3、StampedLock

该类自 JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用。

加解读锁

long stamp = lock.readLock();
lock.unlockRead(stamp);

加解写锁

long stamp = lock.writeLock();
lock.unlockWrite(stamp);

7.5.4、Semaphore

Semaphore信号量,用来限制能同时访问共享资源的线程上限。

场景:我们现在有6个线程,但是我们每次要求只有2个线程运行,这时候可以使用信号量,来限制能同时访问共享资源的线程上限。

7.5.5、CountDownLatch

CountdownLatch用来进行线程同步协作,等待所有线程完成倒计时。

其中构造参数用来初始化等待计数值,await() 用来等待计数归零,countDown() 用来让计数减一。

7.5.6、CyclicBarrier


CyclicBarrier循环栅栏,用来进行线程同步协作,等待线程满足某个计数。

构造时设置『计数个数』,每个线程执行到某个需要“同步”的时刻调用 await() 方法进行等待,当等待的线程数满足『计数个数』时,继续执行。他可以用于多线程计算数据,最后合并计算结果的场景。CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的。
 

7.5.7、ThreadLocal


ThreadLocal(线程局部变量),在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有一种情况之下,我们需要满足这样一个条件:变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。这种情况,ThreadLocal就比较好的解决了这个问题。


7.6线程组的创建

ThreadGroup是Java提供的一种对线程进行分组管理的手段,可以对所有线程以组为单位进行操作,如设置优先级、守护线程等。

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int x = 0; x < 5; x++) {
            System.out.println(Thread.currentThread().getThreadGroup() + ":" + Thread.currentThread().getName() + ":" + x);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        // ThreadGroup(String name)
        ThreadGroup tg = new ThreadGroup("线程组");
        // Thread(ThreadGroup group, Runnable target, String name)
        Thread t1 = new Thread(tg, new MyRunnable(), "张三");
        Thread t2 = new Thread(tg, new MyRunnable(), "李四");
        // 获取组名并在控制台输出
        System.out.println("t1的线程组名称:" + t1.getThreadGroup().getName());
        System.out.println("t2的线程组名称:" + t2.getThreadGroup().getName());
        // 表示该组线程是后台线程
        tg.setDaemon(true);
        // 表示该组线程的优先级5
        tg.setMaxPriority(5);
        // 启动线程组中的所有线程
        t1.start();
        t2.start();
        // 打印该组活跃线程的信息
        tg.list();
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JAVA线程基本学习, JAVA多线程的特性= 线程池: 本质上是一个对象池, 用来管理线程资源. 在任务执行前, 需要从线程池中拿出线程来执行. 在任务执行完成之后, 需要把线程放回线程池. 线程池好处: 降低资源的消耗, 线程本身是一种资源, 创建和销毁都会消耗CPU内存, 频繁的创建和销毁会浪内存. 提高任务执行的响应速度, 任务执行时, 可以不必等线程创建完成之后在执行,可以直接获取线程执行任务. 提高线程的可管理性, 线程不能无限制的创建, 需要进行统一的分配, 调优和监控. 不适用线程池坏处: 频繁的线程创建和销毁会占用更多的CPU和内积. 频繁的创建和销毁会对gc产生比较大的压力. 线程太多,线程切换带来的开销将不可忽视. 线程太少, 多核CPU得不到充分利用, 浪费资源. 线程池实现原理: 分为三部分: 核心线程池 线程池 队列 拒绝策略 主要流程: 1.判断核心线程池是否已满, 如果不是, 则创建线程执行任务 2.如果核心线程池满了, 判断队列是否满了, 如果队列没满, 将任务放在队列中 3.如果队列满了, 则判断线程池是否已满, 如果没满, 创建线程执行任务 4.如果线程池满了, 按照拒绝策略对任务进行处理 JDK中提供了一个线程池工厂: Executors ,很多工厂方法, 可以创建多种线程池 1.单一线程池 ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor(); 该线程池只有一个线程, 若多个任务被提交到此线程池, 那么会被缓冲到队列,当线程空闲时,按照FIFO的方式进行处理. 2.固定数量线程池 ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5); 和创建单一线程池类似, 不同的是线程池中有多个线程, 可以并行处理任务, 若多个线程任务被提交到此线程池, 会有以下执行过程: 如果线程的数量未达到指定数量, 则创建新线程执行任务. 如果线程池的数量达到了指定数量, 而且此时有线程是空闲的, 则取出空闲线程来执行任务. 如果没有线程是空闲的, 则将任务缓冲到队列, 当线程空闲的时候, 按照FIFO的方式进行处理. FIFO: 先进先出 3.带缓冲的线程池 该线程池中, 核心线程池长度为0, 线程池最大长度为Integer.MAX_VALUE.以SynchronousQueue作为等待队列, 从而每次往队列中插入一个元素, 必须等待另一个线程从这个队列删除一个元素. 定时调度 4.定时调度的线程池 ExecutorService newCachedThreadPool = Executors.newScheduledThreadPool(); ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5); ExecutorService newWorkStealingPool = Executors.newWorkStealingPool() 线程池家族 线程池的顶层接口是Executor, 这个接口定义了一个核心方法executor(Runnable command), 这个方法最后被ThreadPoolExecutor类实现, 这个方法用来传入任务, 并且该类是线程池的核心类, 构造方法如下 : public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueu, ThreadFactory threadFactory,RejectedExecutionHandler handler); 参数意义: CorePoolSize: 核心线程池大小, 如果核心线程池有空闲的位置, 新的任务就会被核心线程池新建一个线程执行, 执行完毕不会销毁线程, 线程会进入缓冲队列等待再次被运行 MaximunPoolSize: 线程池能创建最大的线程数量, 如果核心线程池和缓冲队列都已经满了, 新的任务就会进来创建新的线程来执行, 但是数量不能超过maximunPoolSize, 否则采取拒绝接受任务策略 KeepAliveTime: 非核心线程能够空闲的最长时间, 超过时间, 线程终止, 这个参数默认只有在线程数量超过核心线程池大小时, 才会起作用. Unit: 时间单位, 和keepAliveTime配合使用. WorkQueue: 缓冲队列, 用来存放等待被执行的任务 ThreadFactory: 线程工厂, 用来创建线程, 一般有三个选择 ArrayBlockingQueue LinkedBlockingQueue SynchronousQueue Handler :拒绝处理策略, 线程数量大于最大线程数量就会拒绝处理策略, 四种策略为 ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 Executor接口有一个子接口ExecutorService, ExecutorService的实现类AbstracExecutorService, 而ThreadPoolExecutor正是AbstracExecutorService的子类. ThreadPoolExecutor还有两个常用的方法shutdown和submit,两者都用来关闭线程池, 但是submit有一个结果返回. 线程池任务执行 当执行executor(Runnable command)方法后, 传入一个任务, public void execute(Runnable command) { if (command == null) throw new NullPointerException(); if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { if (runState == RUNNING && workQueue.offer(command)) { if (runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command); } else if (!addIfUnderMaximumPoolSize(command)) reject(command); } } (传入的对象实现了Runnable接口, 这就解释了: 为什么实现多线程的方式中继承Thread类不能使用线程池了) 执行过程: 首先判断任务是否为空, 为空抛出空指针异常, 否则执行下一个判断: 当前线程数量是否小于核心线程池线程数量, 是,则执行addIfUbderCorePollSize(command)方法, 在核心线程池中创建新的线程, 并且执行这个任务 总结: 如果当前线程池中的数目小于corePollSize, 则每来一个任务, 就会创建一个新的线程去执行这个任务 如果当前线程池中的线程数目>=corePollSize, 则每来一个任务, 会尝试将其添加到缓冲队列中, 如果添加成功, 则该任务会等待空闲线程将其取出去执行, 如果添加失败(一般是以为任务队列已经满了), 则会尝试创建新的线程去执行这个任务. 如果当前线程池中的线程数目达到maximunPoolSize, 则会采取任务拒绝策略进行处理. 如果线程池中的线程数量大于corePoolSize时, 如果某线程空闲时间超过keepAliveTime, 线程将被终止, 直至线程池中的线程数目小于等于corePoolSize; 如果允许核心池中的线程设置存活时间, 那么核心池中的线程空闲时间超过keepAliveTime, 线程也会被终止.
第8章 多线程编程 第1页 本章概述 本章的学习目标 主要内容 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第1页。 本章概述 前面我们所开发的程序大多是单线程的,即一个程序只有一条从头到尾的执行路线。然而,现实世界中的很多事务都是有多种途径同时运作的,例如:服务器可能需要同时处理多个客户机的请求,这就需要有多个线程同时在工作。多线程编程使得系统资源并不是由某个执行体独占,而是由多个执行单元共同拥有,轮换使用。正确使用多线程可以消除系统的瓶颈问题,提高整个应用系统的性能。本章将详细介绍Java语言的多线程技术。通过本章的学习,读者应该理解线程和进程的区别,掌握Java多线程编程技术,了解线程的同步和线程间通信等内容。 第2页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第2页。 本章的学习目标 了解进程和线程的基本概念和区别 掌握创建线程的两种方法 掌握线程同步的概念和方法 了解线程的优先级 掌握线程间通信的方法 第3页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第3页。 第4页 主要内容 8.1 Java线程模型 8.2 创建线程 8.3 同步与线程间通信 8.4 获取线程状态 8.5 本章小结 8.6 思考和练习 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第4页。 8.1 Java线程模型 Java多线程编程(multithreaded programming)提供了内置支持。多线程程序包含同时运行的两个或多个部分。这种程序的每一部分被称为一个线程,并且每个线程定义了单独的执行路径。因此,多线程是特殊形式的多任务处理。 第5页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第5页。 进程 进程本质上是正在执行的程序。在UNIX操作系统中,每个应用程序的执行都在操作系统内核中登记一个进程标志,操作系统根据分配的标志对应用程序的执行进行调度和系统资源分配。每个进程都有自己的内存单元,进程之间是互相独立的,一个进程一般不允许访问其他进程的内存空间,因此,进程间通信非常困难。 基于进程的多任务处理就是允许计算机同时运行两个或更多个程序的特性。例如,基于进程的多任务处理可以在运行Java编译器的同时使用文本编辑器或浏览网站。在基于进程的多任务处理中,程序是调度程序能够调度的最小代码单元。 第6页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第6页。 线程 线程是比进程更小的执行单位。如果将进程概念一分为二,则进程中的系统资源,可以看成是一个静态的对象;而程序代码的执行位置,可以看成一个动态对象,这个动态的部分就是线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存,线程之间的通信比较容易解决,从而极大地提高了程序的运行效率。 在基于线程的多任务环境中,最小的可调度代码单元是线程,这意味着单个程序可以同时执行两个或更多个任务。例如,文本编辑器可以在打印的同时格式化文本,只要这两个动作是通过两个独立的线程执行即可。 第7页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第7页。 进程和线程的区别 进程和线程的区别可以总结为如下几点: 一个程序至少有一个进程,一个进程至少有一个线程,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。 线程在执行过程中与进程也是有区别的,每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口;但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程的执行控制。 一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。 第8页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第8页。 Java中的线程 Java运行时系统在许多方面依赖于线程,并且所有类库在设计时都考虑了多线程。事实上,Java通过利用线程使得整个环境能够异步执行。 在过去几年,多核系统已经变得很普遍了。当然,单核系统仍然在广泛使用。Java多线程系统在这两种类型的系统中都可以工作。在单核系统中,并发执行的线程共享CPU,每个线程得到一片CPU时钟周期。所以,在单核系统中,两个或更多个线程不是真正同时运行的,但是空闲时间被利用了。然而,在多核系统中,两个或多个线程可能是真正同步执行的。 第9页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第9页。 线程的状态 线程有多种状态:线程可以处于运行(running)状态,只要获得CPU时间就准备运行。运行的线程可以被挂起(suspended),这会临时停止线程的活动。挂起的线程可以被恢复(resumed),从而允许线程从停止处恢复执行。当等待

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值