JavaSE学习 第十六章 线程

1.什么是线程

线程是一个程序内部的顺序控制流。

线程和进程:

每个进程都有独立的代码和数据空间(进程上下文),进程切换的开销大。

线程: 轻量的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。

多进程: 在操作系统中能同时运行多个任务(程序)

多线程:在同一应用程序中有多个顺序流同时执行

2.线程的概念模型

1. 虚拟的CPU,由java.lang.Thread类封装和虚拟;

2. CPU所执行的代码,传递给Thread类对象;

3. CPU所处理的数据,传递给Thread类对象。

1

Java的线程是通过java.lang.Thread类来实现的。

每个线程都是通过某个特定Thread对象所对应的,方法run( )来完成其操作的,方法run( )称为线程体。

3.创建线程的两种方式的比较

一. 使用Runnable接口创建线程:

可以将CPU(Tread 类)、代码和数据(Runnable接口 run 方法)分开,形成清晰的模型;线程体run()方法所在的类还可以从其他类继承一些有用的属性或方法;

并有利于保持程序风格的一致性。

public class TestTread1{
    public static void main(String args[]) {
        Runner1 r = new Runner1();
        Thread t = new Thread(r);
        t.start();
    }
}


class Runner1 implements Runnable {
    public void run() {
        for(int i=0; i<30; i++) {
            System.out.println("No. " + i);
        }
    }
}

二. 直接继承Thread类创建线程:

Thread子类无法再从其他类继承,编写简单,run()方法的当前对象就是线程对象,可直接操纵。

public class TestTread2{
    public static void main(String args[]){
        Thread t = new Runner3();
        t.start();
    }
}


class Runner3 extends Thread {
    public void run() {
        for(int i=0; i<30; i++) {
            System.out.println("No. " + i);
        }
    }
}

4.多线程

Java 中 引 入 线 程 机 制 的 目 的 在 于 实 现 多 线 程 (Multi-Thread),多线程之间可以共享代码和数据。

public class TestTread3{
    public static void main(String args[]) {
        Runner2 r = new Runner2();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
    }
}


class Runner2 implements Runnable {
    public void run() {
        for(int i=0; i<10; i++) {
            String s = Thread.currentThread().getName();
            System.out.println(s + ": " + i);
        }
    }
}

结果:

Thread-0: 0
Thread-0: 1
Thread-0: 2
Thread-0: 3
Thread-0: 4
Thread-0: 5
Thread-0: 6
Thread-0: 7
Thread-0: 8
Thread-0: 9
Thread-1: 0
Thread-1: 1
Thread-1: 2
Thread-1: 3
Thread-1: 4
Thread-1: 5
Thread-1: 6
Thread-1: 7
Thread-1: 8
Thread-1: 9

2

5.后台线程

相关基本概念:

后台处理(Background Processing):让某些程序的运行让步给其他的优先级高的程序

后台线程(Background Thread / Daemon Thread):以后台的方式(往往是服务线程)运行的线程,例如定时器线程

用户线程(User Thread):完成用户指定的任务的线程

主线程(Main Thread):它就是一种主线程

子线程(Sub Thread):在线程中创建的线程就是子线程

Thread类提供的相关方法:

public final boolean isDaemon():判断某个线程是不是一个后台线程

public final void setDaemon(Boolean on):设置某个线程为一个后台线程

举例:

package v512.chap16;
public class TestDaemonThread {    
    public static void main(String args[]){
        Thread t1 = new MyRunner(10);
        t1.setName("用户线程t1");
        t1.start();
        
        Thread t2 = new MyRunner(50);
        t2.setDaemon(true);
        t2.setName("后台线程t2");
        t2.start();
        
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName() + ": " + i);    
        }
        System.out.println("主线程结束!");
    }
}

class MyRunner extends Thread {
    private int n;
    public MyRunner(int n){
        this.n = n;    
    }
    public void run() {
        for(int i=0; i<n; i++) {    
                System.out.println(this.getName() + ": " + i);
        }
        System.out.println(this.getName() + "结束!");
    }
}

结果:

用户线程t1: 0
用户线程t1: 1
用户线程t1: 2
用户线程t1: 3
用户线程t1: 4
用户线程t1: 5
用户线程t1: 6
用户线程t1: 7
用户线程t1: 8
用户线程t1: 9
用户线程t1结束!
main: 0
main: 1
main: 2
main: 3
main: 4
main: 5
main: 6
main: 7
main: 8
main: 9
主线程结束!
后台线程t2: 0
后台线程t2: 1
后台线程t2: 2
后台线程t2: 3
后台线程t2: 4
后台线程t2: 5
后台线程t2: 6
后台线程t2: 7
后台线程t2: 8
后台线程t2: 9
后台线程t2: 10
后台线程t2: 11
后台线程t2: 12
后台线程t2: 13
后台线程t2: 14
后台线程t2: 15
后台线程t2: 16
后台线程t2: 17
后台线程t2: 18
后台线程t2: 19
后台线程t2: 20
后台线程t2: 21
后台线程t2: 22
后台线程t2: 23
后台线程t2: 24
后台线程t2: 25
后台线程t2: 26
后台线程t2: 27
后台线程t2: 28
后台线程t2: 29
后台线程t2: 30
后台线程t2: 31
后台线程t2: 32
后台线程t2: 33
后台线程t2: 34
后台线程t2: 35
后台线程t2: 36
后台线程t2: 37
后台线程t2: 38
后台线程t2: 39
后台线程t2: 40
后台线程t2: 41
后台线程t2: 42
后台线程t2: 43
后台线程t2: 44
后台线程t2: 45
后台线程t2: 46
后台线程t2: 47
后台线程t2: 48
后台线程t2: 49
后台线程t2结束!

6.GUI线程

GUI程序运行过程中系统会自动创建若干GUI线程

常见GUI线程:

AWT-Windows线程:(windows 系统中)后台线程

AWT-EventQueue-n线程

AWT-Shutdown线程:负责关闭窗体中的抽象窗口工具

DestroyJavaVM线程:不是GUI线程,它是当main线程结束之后系统自动创建的,用于等到所有的线程都死掉了之后销毁jvm

实例:

package v512.chap16;
import java.awt.*;
import java.awt.event.*;
public class TestGUIThread {    
    public static void main(String args[]) throws Exception{
        Frame f = new Frame();
        Button b = new Button("Press Me!");
        MyMonitor mm = new MyMonitor();
        b.addActionListener(mm);
        f.addWindowListener(mm);    
        f.add(b,"Center");    
        f.setSize(100,60);
        f.setVisible(true);
        MyThreadViewer.view();
    }
}

class MyMonitor extends WindowAdapter implements ActionListener{
    public void actionPerformed(ActionEvent e){
        MyThreadViewer.view();    
    }
}

class MyThreadViewer{
    public static void view(){
        Thread current = Thread.currentThread();
        System.out.println("当前线程名称: " + current.getName());
        int total = Thread.activeCount();
        System.out.println("活动线程总数: " + total + "个");
        Thread[] threads = new Thread[total];        
        current.enumerate(threads);
        for(Thread t : threads){
            String role = t.isDaemon()?"后台线程 ":"用户线程 ";
            System.out.println("   -" + role + t.getName());    
        }    
        System.out.println("----------------------------------");
    }    
}

运行之后,在点击一下 button

输出结果:

当前线程名称: main
活动线程总数: 4个
   -用户线程 main
   -用户线程 AWT-Shutdown
   -后台线程 AWT-Windows
   -用户线程 AWT-EventQueue-0
----------------------------------
当前线程名称: AWT-EventQueue-0
活动线程总数: 4个
   -用户线程 AWT-Shutdown
   -后台线程 AWT-Windows
   -用户线程 AWT-EventQueue-0
   -用户线程 DestroyJavaVM
----------------------------------

7.线程的生命周期

新建状态

就绪状态:此时不一定马上就开始运行                                                      

运行状态:运行状态的线程可能会发生问题(赚到了阻塞状态)

阻塞状态

终止状态

3

8.线程的优先级

线程的优先级用数字来表示,范围从1到10。主线程的缺省优先级是5,子线程的优先级默认与其父线程相同。

注意:程序中不能依赖线程的优先级来让线程按照我们的要求来执行!

Thread类提供的相关方法:

public final int getPriority();

public final void setPriority(int newPriority);

相关静态整型常量:

Thread.MIN_PRIORITY = 1

Thread.MAX_PRIORITY = 10

Thread.NORM_PRIORITY = 5

用法举例:

package v512.chap16;
public class TestPriority {    
    public static void main(String args[]){
        System.out.println("线程名\t优先级");
        Thread current = Thread.currentThread();
        System.out.print(current.getName() + "\t");
        System.out.println(current.getPriority());        
        Thread t1 = new Runner();
        Thread t2 = new Runner();
        Thread t3 = new Runner();
        t1.setName("First");        
        t2.setName("Second");        
        t3.setName("Third");        
        t2.setPriority(Thread.MAX_PRIORITY);
        t3.setPriority(8);
           t1.start();
        t2.start();
           t3.start();
    }
}

class Runner extends Thread {
    public void run() {
        System.out.print(this.getName() + "\t");
        System.out.println(this.getPriority());        
    }
}

输出结果:

线程名    优先级
main    5
First    5
Second    10
Third    8

9.线程的串行化

在多线程程序中,如果在一个线程运行的过程中要用到另一个线程的运行结果,则可进行线程的串型化处理。

Thread类提供的相关方法:

public final void join()

public final void join(long millis)  //等待毫秒数

public final void join(long millis,int nanos)

测试 主线程必须要等到子线程运行完成之后才可以运行:

package v512.chap16;
public class TestJoin {    
    public static void main(String args[]){
        MyRunner2 r = new MyRunner2();
        Thread t = new Thread(r);
        t.start();
        try{
            t.join();
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        for(int i=0;i<20;i++){
            System.out.println("主线程:" + i);
        }
    }
}

class MyRunner2 implements Runnable {
    public void run() {
        for(int i=0;i<20;i++) {
            System.out.println("SubThread: " + i);
        }
    }
}

输出结果:

SubThread: 0
SubThread: 1
SubThread: 2
SubThread: 3
SubThread: 4
SubThread: 5
SubThread: 6
SubThread: 7
SubThread: 8
SubThread: 9
SubThread: 10
SubThread: 11
SubThread: 12
SubThread: 13
SubThread: 14
SubThread: 15
SubThread: 16
SubThread: 17
SubThread: 18
SubThread: 19
主线程:0
主线程:1
主线程:2
主线程:3
主线程:4
主线程:5
主线程:6
主线程:7
主线程:8
主线程:9
主线程:10
主线程:11
主线程:12
主线程:13
主线程:14
主线程:15
主线程:16
主线程:17
主线程:18
主线程:19

10.线程休眠

线程休眠——暂停执行当前运行中的线程,使之进入阻塞状态,待经过指定的“延迟时间”后再醒来并转入到就绪状态。

Thread类提供的相关方法:

public static void sleep(long millis)

public static void sleep(long millis, int nanos)

用法举例: 数字时钟

package v512.chap16;
import java.util.*;
import javax.swing.*;

public class DigitalClock{
    public static void main(String[] args){
        JFrame jf = new JFrame("Clock");
        JLabel clock = new JLabel("Clock");
        clock.setHorizontalAlignment(JLabel.CENTER);
        jf.add(clock,"Center");
        jf.setSize(140,80);
        jf.setLocation(500,300);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setVisible(true);
        
        while(true){
            clock.setText(getTime());
            try{
                Thread.sleep(1000); //this.sleep(1000);    
            }catch(InterruptedException e){
                e.printStackTrace();    
            }    
        }    
    }
    public static String getTime(){
        Calendar c = new GregorianCalendar();
        String time = c.get(Calendar.YEAR) + "-" +
                      (c.get(Calendar.MONTH) + 1) + "-" +
                      c.get(Calendar.DATE)  + "  " ;
        int h = c.get(Calendar.HOUR_OF_DAY);
        int m = c.get(Calendar.MINUTE);
        int s = c.get(Calendar.SECOND);
        String ph = h<10 ? "0":"";
        String pm = m<10 ? "0":"";
        String ps = s<10 ? "0":"";        
        time += ph + h + ":" + pm + m + ":" + ps + s; 
        return time;
    }    
}

显示效果:

4

11.线程让步

线程让步——让运行中的线程主动放弃当前获得的 CPU 处理机会,但不是使该线程阻塞,而是使之转入就绪状态。

Thread类提供的相关方法:

public static void yield()

用法举例:

package v512.chap16;
import java.util.Date;

public class TestYield{
    public static void main(String[] args){
        Thread t1 = new MyThread(false);
        Thread t2 = new MyThread(true);
        Thread t3 = new MyThread(false);
        t1.start();
        t2.start();
        t3.start();
    }    
}

class MyThread extends Thread{
    private boolean flag;
    public MyThread(boolean flag){
        this.flag = flag;
    }
    public void setFlag(boolean flag){
        this.flag = flag;
    }
    public void run(){
        long start = new Date().getTime();
        for(int i=0;i<500;i++){
            if(flag)
                Thread.yield();
            System.out.print(this.getName() + ": " + i + "\t");
        }
        long end = new Date().getTime();
        System.out.println("\n" + this.getName() + "执行时间: " + (end - start) + "毫秒");
    }
}

输出结果太多了,不便贴出,可以查看一下执行时间

Thread-0执行时间: 67毫秒

Thread-2执行时间: 84毫秒

Thread-1执行时间: 148毫秒

12.线程的挂起和恢复

线程挂起——暂时停止当前运行中的线程,使之转入阻塞状态,并且不会自动恢复运行。

线程恢复——使得一个已挂起的线程恢复运行。

Thread类提供的相关方法:

public final void suspend()

public final void resume()

用法举例:

package v512.chap16;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JButton;

public class TestSuspend{
    public static void main(String[] args){
        JFrame jf = new JFrame("Timer");
        JButton pause = new JButton("Pause");
        JLabel clock = new JLabel("Timer");
        clock.setBackground(Color.GREEN);
        clock.setOpaque(true);
        clock.setHorizontalAlignment(JLabel.CENTER);
        jf.add(clock,"Center");
        jf.add(pause,"North");
        jf.setSize(140,80);
        jf.setLocation(500,300);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setVisible(true);

        MyThread3 mt = new MyThread3(clock,10000);
        mt.start();
        MyListener ml = new MyListener(clock,mt);
        pause.addActionListener(ml);
    }    
}

class MyThread3 extends Thread{
    private JLabel clock;
    private long time;
    private long end;
    
    public MyThread3(JLabel clock,long time){
        this.clock = clock;    
        this.time = time;
    }
    public void init(){
        long start = new Date().getTime();
        end = start + time;
    }    
    public void run(){
        this.init();
        while(true){
            long now = new Date().getTime();
            time = end - now;
            if(time > 0){
                String s = this.convert(time);    
                clock.setText(s);
            }else{
                break;    
            }
            try{
                Thread.sleep(10);    
            }catch(InterruptedException e){
                e.printStackTrace();    
            }
        }
        clock.setText("时间到!");    
        clock.setBackground(Color.RED);
    }    
    public String convert(long time){
        long h = time / 3600000;
        long m = (time % 3600000) / 60000;
        long s = (time % 60000) / 1000;
        long ms = (time % 1000) / 10;
        String ph = h<10 ? "0":"";
        String pm = m<10 ? "0":"";
        String ps = s<10 ? "0":"";    
        String pms = ms<10 ? "0":"";    
        String txt = ph + h + ":" + pm + m + ":" + ps + s + "." + pms + ms; 
        return txt;
    }    
}

class MyListener implements ActionListener{
    private JLabel clock;
    private MyThread3 mt;
    private boolean running= true;
    
    public MyListener(JLabel clock,MyThread3 mt){
        this.clock = clock;
        this.mt = mt;
    }    
    public void actionPerformed(ActionEvent e){
        if(!mt.isAlive())
            return;
        JButton jb = (JButton)(e.getSource());
        if(running){
            jb.setText("Replay");
            clock.setBackground(Color.YELLOW);
            mt.suspend();
        }else{
            jb.setText("Pause");
            clock.setBackground(Color.green);
            mt.init();
            mt.resume();
        }
        running = !running;
    }    
}

测试结果:

5      6      7

13.终止线程

一种特别优雅的方式终止线程(设置标志 flag)

实例:

public class Test {
    public static void main(String args[]){
        Runner r = new Runner();
        Thread t = new Thread(r);
        t.start();


        for(int i=0;i<10;i++){
            try{
                Thread.sleep(5);
                System.out.println("\nin thread main i=" + i);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("Thread main is over");
        r.shutDown();
    }
}
public class Runner implements Runnable {
    private boolean flag=true;

    public void run() {
        int i = 0;
        while (flag == true) {
            System.out.print(" " + i++);
        }
    }


    public void shutDown() {
        flag = false;
    }
}

main线程结束之后,子线程也就结束了(flag == false)

14.线程控制的基本方法

8

15. 临界资源问题

临界资源问题实例:

两个线程A和B在同时操纵Stack类的同一个实例(栈),A向栈里push一个数据,B则要从堆栈中pop一个数据。

栈的模拟

public class Stack{
        int idx=0;
        char[ ] data = new char[6];


        public void push(char c){
            data[idx] = c;
            idx++;
        }
        public char pop(){
            idx--;
            return data[idx];
        }
    }

可能出现的问题

9

(4 中 c 应该是不存在的)

16. 互斥锁 和 死锁

在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。

每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。

synchronized用法

①用于方法声明中,标明整个方法为同步方法:这个时候调用这个方法的对象是处于被当前的线程锁定的状态

    public synchronized void push(char c){
                data[idx] = c;
                idx++;
        }

②用于修饰语句快,标明整个语句块为同步块:这个时候 synchronized  中的对象是被当前的线程锁定了的

    public char pop(){
            //其它代码
            synchronized(this){
                    idx--;
                    return data[idx];
            }
            //其它代码
    }

并发运行的多个线程间彼此等待、都无法运行的状态称为线程死锁。

线程死锁实例一:

package v512.chap16;
public class TestDeadLock{
    public static void main(String args[]){
        StringBuffer sb = new StringBuffer("ABCD");
        MyThread4 t = new MyThread4(sb);
        t.start();    
        synchronized(sb){
            try{
                t.join();
            }catch(InterruptedException e){
                e.printStackTrace();    
            }
            System.out.println(sb);    
        }
        System.out.println("Main thread is over!");    
    }    
}

class MyThread4 extends Thread{
    private StringBuffer sb;
    public MyThread4(StringBuffer sb){
        this.sb = sb;
    }
    public void run(){
        synchronized(sb){
            sb.reverse();
        }
        System.out.println("Sub thread is over!");    
    }    
}

控制台什么都没有输出来!

分析:在main方法的同步块中串行插入了子线程t,并锁定了sb对象,然而子线程的run方法中也有一个同步块,它要锁定的对象正是sb对象,

然而sb对象已经被主线程给锁定了,子线程只能等待,另一方面,主线程又必须要等到子线程结束了之后才可以执行,两者就这么一直等待着,

陷入了死锁的状态!

如果删除掉main中的同步,就可以看到结果:

Sub thread is over!
DCBA
Main thread is over!

实例二:

package v512.chap16;
public class TestDeadLock2{
    public static void main(String args[]){
        char[] a = {'A','B','C'};
        char[] b = {'D','E','F'};
        MyThread5 t1 = new MyThread5(a,b);
        MyThread5 t2 = new MyThread5(b,a);
        t1.start();
        t2.start();        
    }    
}

class MyThread5 extends Thread{
    private char[] source;
    private char[] dest;
    
    public MyThread5(char[] source,char[] dest){
        this.source = source;
        this.dest = dest;
    }
    public void run(){
        synchronized(source){
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();    
            }
            
            synchronized(dest){
                System.arraycopy(source,0,dest,0,source.length);
                System.out.println(dest);
            }        
        }
    }    
}

假设线程t1先执行run,然后将字符数组a锁定了,接着t1休眠了,线程t2开始执行,并将数组b锁定了,之后线程t2进入休眠,

然后线程t1重新执行,这时候发现数组b已经被线程t2给锁定了,只能等待,休眠之后,线程t2同样发现数组a被线程t1给锁定了,

两者相互之间等待着,就陷入了死锁状态!

17.线程的同步通信

为避免死锁,在线程进入阻塞状态时应尽量释放其锁定的资源,以为其他的线程提供运行的机会。

相关方法:

public final void wait() :会导致当前的线程进入阻塞状态并尽量释放其锁定的资源

public final void notify():将某个线程重新恢复过来,但不是马上就进入到就绪状态,它可能还要等,等到它要使用的对象解除了锁定才行

public final void notifyAll()

10

生产者 和 消费者 问题:

同步的栈:

package v512.chap16;
public class SyncStack { // 支持多线程同步操作的堆栈的实现
    private int index = 0;
    private char[] data = new char[6];

    public synchronized void push(char c) {
        while (index == data.length) {
            try {
                this.wait();
            } catch (InterruptedException e) {
            }
        }
        this.notify();
        data[index] = c;
        index++;
        System.out.println("produced:" + c);
    }

    public synchronized char pop() {
        while (index == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
            }
        }
        this.notify();
        index--;
        System.out.println("consumed:" + data[index]);
        return data[index];
    }
}

测试类:包括了生产者和消费者

package v512.chap16;
public class SyncTest{
    public static void main(String args[]){
        SyncStack stack = new SyncStack();
        Runnable p=new Producer(stack);
        Runnable c = new Consumer(stack);
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();
    }
}


class  Producer implements Runnable{
    SyncStack stack;    
    public Producer(SyncStack s){
        stack = s;
    }
    public void run(){
        for(int i=0; i<20; i++){
            char c =(char)(Math.random()*26+'A');
            stack.push(c);
            try{                                    
                Thread.sleep((int)(Math.random()*300));
            }catch(InterruptedException e){
            }
        }
    }
}

class Consumer implements Runnable{
    SyncStack stack;    
    public Consumer(SyncStack s){
        stack = s;
    }
    public void run(){
        for(int i=0;i<20;i++){
            char c = stack.pop();
            try{                                       
                Thread.sleep((int)(Math.random()*500));
            }catch(InterruptedException e){
            }
        }
    }
}

输出结果:

produced:M
consumed:M
produced:N
consumed:N
produced:P
produced:A
consumed:A
consumed:P
produced:Z
consumed:Z
produced:F
produced:J
produced:X
produced:Q
consumed:Q
produced:N
produced:W
produced:K
consumed:K
produced:I
consumed:I
produced:A
consumed:A
produced:Z
consumed:Z
produced:M
consumed:M
produced:X
consumed:X
produced:X
consumed:X
produced:P
consumed:P
produced:V
consumed:V
consumed:W
consumed:N
consumed:X
consumed:J
consumed:F

18.多线程编程专题  (还未仔细看)

线程间数据传输

例16-16 使用管道流实现线程间数据传输

类的同步性与线程安全

例16-17 验证同步类的线程安全性

定时器

例16-18 使用定时器实现数字时钟功能

原文地址:https://www.cnblogs.com/yinger/p/2162621.html