欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java多线程编程核心技术笔记:第1章 多线程技能

程序员文章站 2022-05-06 07:53:27
...

1、进程和线程的区别:

    (1)进程可以理解为一个程序的执行。如果说QQ.

    (2)线程可以理解为在一个进程中独立运行的子任务。比如说:QQ运行时有:和好友视频的线程,传输数据的线程,发送表情的线程等等。
    多任务操作系统,如Windows系列就是通过多个线程来处理任务的。

    

    多任务环境:异步(多个线程同时执行)。    单任务环境:异步(只有一个线程)。

2.使用多线程:

    (1)一个程序在运行时,至少有一个线程在执行任务。比如public static void main()执行时,就是一个叫做main的线程在执行的。

    获取当前线程的名字:

	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName());
	}

  实现多线程主要有两种方法:继承Thread类,实现Runnable接口  。

 1.1继承Thread类

      (1)public class Thread implements Runnable
        从源码可以看出,Thread类实际上就是实现了Runnable接口,它们之间是多态关系。
   (2)实例:
class MyThread extends Thread{
	@Override
	public void run() {
		System.out.println("MyThread");
	}
}
public class Run {
	public static void main(String[] args) {
		System.out.println("开始运行:("+Thread.currentThread().getName()+")");
		MyThread my=new MyThread();
		my.start();
		System.out.println("运行结束");
	}
}

Java多线程编程核心技术笔记:第1章 多线程技能

从结果可以看出使用多线程时,代码运行结果与代码执行顺序是无关的。

(3)演示线程随机性:

class MyThread extends Thread{
	@Override
	public void run() {
		System.out.println("MyThread开始运行:");
		for(int i=0;i<100;i++) {
			try {
				Thread.sleep((int)Math.random()*1000);
				System.out.println("run="+this.getName());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
public class Run {
	public static void main(String[] args) {
		MyThread thread=new MyThread();
		thread.setName("mythread");
		thread.start();
		for(int i=0;i<100;i++) {
			try {
				Thread.sleep((int)Math.random()*1000);
				System.out.println("main="+Thread.currentThread().getName());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

结果:

Java多线程编程核心技术笔记:第1章 多线程技能


Thread类中的start() 方法通知“线程规划器”此线程已经准备完毕,等待调用线程对象的run()方法,也就是使线程得到运行,启动线程。如果调用Thread.run()就不是异步了,就是同步了,那么此线程对象并不交给"线程规划器"来进行处理。

 1.2实现Runnable接口

       (1)实例:

        

class MyRunnable implements Runnable{
	@Override
	public void run() {
		System.out.println("MyRunnable开始运行:");
		
	}
}
public class Run {
	public static void main(String[] args) {
		MyRunnable mr=new MyRunnable();
		Thread thread=new Thread(mr);
		thread.start();
		System.out.println("运行结束!");
	}
}

注意:使用Thread类的方法实现多线程是有局限性的,因为java是单根继承,不支持多根继承。所以为了改变这种限制,可以使用Runnable接口的方式来实现多线程技术。


3.实例线程和线程安全

        (1)卖票(不共享数据的实例):

class MyRunnable implements Runnable{
	private int count=5;
	@Override
	public void run() {
		while(count>0) {
			count--;
			System.out.println("由"+Thread.currentThread().getName()+"计算,卖出count="+count);
		}
		
	}
}
public class Run {
	public static void main(String[] args) throws InterruptedException {
		System.out.println("开始售票:");
		MyRunnable a=new MyRunnable();
		Thread aThread=new Thread(a,"A");
		Thread bThread=new Thread(a,"B");
		Thread cThread=new Thread(a,"C");
		aThread.start();
		bThread.start();
		cThread.start();
		Thread.sleep(1000);
		System.out.println("运行结束!");
	}
}

Java多线程编程核心技术笔记:第1章 多线程技能

    一共创建了3个线程,每个线程都有自己的count变量。自己减少自己的count变量的值,这样的情况是变量不共享。

    如何设计三个线程同时卖票呢?  我们可不可以把count设置成为static呢?

    (2)卖票(共享数据)

            

class MyRunnable implements Runnable{
	public static int count=5;
	@Override
	public void run() {//这里为了防止某一线程直接运行完毕,其他线程得不到执行的机会,不用for循环
		count--;
		System.out.println("由"+Thread.currentThread().getName()+"计算,卖出count="+count);
	}
}
public class Run {
	public static void main(String[] args) throws InterruptedException {
		System.out.println("开始售票:");
		MyRunnable a=new MyRunnable();
		Thread aThread=new Thread(a,"A");
		Thread bThread=new Thread(a,"B");
		Thread cThread=new Thread(a,"C");
		Thread dThread=new Thread(a,"D");
		Thread eThread=new Thread(a,"E");
		aThread.start();
		bThread.start();
		cThread.start();
		dThread.start();
		eThread.start();
		Thread.sleep(1000);
		System.out.println("运行结束!");
	}
}

Java多线程编程核心技术笔记:第1章 多线程技能

  可以看出,线程A和B和C都打印出了2,说明同时对count进行操作,会出现“非线程安全”问题。而我们要的结果不是重复的,而是递减的。

在某些JVM中,i--的操作被分成了3步:

    <1>取得原有的i值;                 <2>计算i-1;                 <3>对i进行赋值操作。

在这三个步骤中,如果多个线程进行访问,那么一定会出现非线程安全问题。

这是我们可以使用顺序排队的方式进行减一的操作:

    

class MyRunnable implements Runnable{
        public static int count=5;
	@Override
    synchronized public void run() {//这里为了防止某一线程直接运行完毕,其他线程得不到执行的机会,不用for循环
        count--;
        System.out.println("由"+Thread.currentThread().getName()+"计算,卖出count="+count);
    }
}

    在run方法前面加上sychronized(同步)关键字,使多个线程在执行run方法的时候。以排队的方式进行。当一个方法调用run方法之前,先判断是否上锁。如果上锁,就说明其他线程在调用run方法,必须等待其他线程对该run方法调用结束了才能执行run方法。

sychronized可以在任意对象和方法上加锁,加锁的这段代码叫做“互斥区”或“临界区”。

3.1.留意i--与System.out.println()的异常

    那如果将count--;放到System.out.println()中是否就可以不用同步锁了呢?
class MyRunnable implements Runnable{
        public static int count=5;
	@Override
   public void run() {//这里为了防止某一线程直接运行完毕,其他线程得不到执行的机会,不用for循环
        System.out.println("由"+Thread.currentThread().getName()+"计算,卖出count="+(count--));
    }
}

程序运行后还是出现了非线程安全问题,因为:i--的操作是在进入println()之前发生的,所以有发生非线程安全问题的几率。

所以为了防止非线程安全问题的发生,还是要继续使用同步方法。

4.currentThread()方法:

        文章一开始我们就介绍了这个方法,可以通过Thread.currentThread().getName()得到当前线程的名字。

    通过这个方法,我们会发现一些问题:

class MyRunnable implements Runnable{
	public static int count=3;
	public MyRunnable() {
		System.out.println("构造方法的打印:"+Thread.currentThread().getName());
	}
	@Override
	public void run() {
		System.out.println("run方法的打印:"+Thread.currentThread().getName());
		
	}
}
public class Run {
	public static void main(String[] args) throws InterruptedException {
		MyRunnable run=new MyRunnable();
		Thread thread=new Thread(run,"run");
		thread.start();
	}
}

Java多线程编程核心技术笔记:第1章 多线程技能

    我们发现,构造方法是main线程调用的。

5.isAlive()方法

    方法isAlive()的功能是判断当前线程是否处于活动状态。什么是活动状态呢?活动状态就是线程已经启动但是尚未终止。线程处于正在运行或者准备运行的状态,就认为线程是存活的。

class MyThread extends Thread{

    @Override

    public void run(){

       System.out.println("run= "+this.isAlive());

    }

}

public class Main{

    public static void main(String[]args) {

        System.out.println("开始运行程序");

        MyThread myThread=newMyThread();

       System.out.println("begin=="+myThread.isAlive());

        myThread.start();

       System.out.println("end=="+myThread.isAlive());

    }

结果为:

Java多线程编程核心技术笔记:第1章 多线程技能

   

     *在使用isAlive()方法时,如果将线程对象以构造参数的方式传递给Thread对象,进行start()启动时,运行结果是有差异的。造成这种差异主要是Thread.currentThread()和this的差异。

class CountOperate extends Thread{
    public CountOperate() {
        System.out.println("CountOperate--begin:");
        System.out.println("Thread.currentThread().getName():"+Thread.currentThread().getName());
        System.out.println("Thread.currentThread().isAlive():"+Thread.currentThread().isAlive());
        System.out.println("this.currentThread().getName():"+this.getName());
        System.out.println("this.currentThread().isAlive():"+this.isAlive());
        System.out.println("CountOprate--end");
    }

    @Override
    public void run(){
        System.out.println("run--begin:");
        System.out.println("Thread.currentThread().getName():"+Thread.currentThread().getName());
        System.out.println("Thread.currentThread().isAlive():"+Thread.currentThread().isAlive());
        System.out.println("this.getName():"+this.getName());
        System.out.println("this.isAlive():"+this.isAlive());
        System.out.println("run--end");
    }
}
public class Main{
    public static void main(String[] args) {
        System.out.println("开始运行程序");
        CountOperate countOperate=new CountOperate();
        Thread thread=new Thread(countOperate,"countOprate");
        thread.start();
    }
}
运行结果:

Java多线程编程核心技术笔记:第1章 多线程技能

6.停止线程

    大多数停止线程使用的是Thread.interrupt()方法,尽管方法的名称是"停止,中止"的意思,但是这个方法不会终止一个正在运行中的线程,还需要加入一个判断才能完成线程的停止。

    在Java中有三种可以终止正在运行的线程;

       (1)使用退出标志,是线程正常退出,也就是当run方法完成后线程终止。

      (2)使用stop方法强行终止线程,但是不推荐这种方法,因为stop和suspend及resume一样,都是过期作废的方法,使用它们可能会产生不可预料的后果。

       (3)使用interrupt方法中断线程。

    6.1停止不了的线程

class MyThread extends Thread{
    public MyThread() {
    }
    @Override
    public void run(){
        for (int i = 0; i <50000 ; i++) {
            System.out.println("i="+(i+1));
        }
    }
}
public class Main{
    public static void main(String[] args) throws InterruptedException {
        MyThread thread=new MyThread();
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
    }
}

结果:

Java多线程编程核心技术笔记:第1章 多线程技能

从结果看,调用interrupt方法并没有停止线程。

6.2判断线程是否是停止状态

    1)this.interrupted():测试当前线程是否已经中断。

    2)this.isInterrupted():测试线程是否已经中断。

6.2.1 static boolean interrupted()

class MyThread extends Thread{
    @Override
    public void run(){
        for (int i = 0; i <50000 ; i++) {
            System.out.println("i="+(i+1));
        }
    }
}
public class Main{
    public static void main(String[] args) throws InterruptedException {
        MyThread thread=new MyThread();
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
        System.out.println("是否停止1?="+thread.isInterrupted());
        System.out.println("是否停止2?="+thread.isInterrupted());
        System.out.println("end!");
    }
}

结果:

Java多线程编程核心技术笔记:第1章 多线程技能

    用isInterrupted来判断thread所代表的线程是否已经中断,但是,从控制台中显示,线程并未停止。证实了isInterrupted()方法的解释:测试当前线程是否已经中断。‘当前线程’是main,它从未中断过,所以打印的结果是两个false。

尝试让主线程中断:

class MyThread extends Thread{
    @Override
    public void run(){
        for (int i = 0; i <50000 ; i++) {
            System.out.println("i="+(i+1));
        }
    }
}
public class Main{
    public static void main(String[] args) throws InterruptedException {
        MyThread thread=new MyThread();
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
        Thread.currentThread().interrupt();
        System.out.println("是否停止1?="+Thread.interrupted());
        System.out.println("是否停止2?="+Thread.interrupted());
        System.out.println("end!");
    }
}

结果为:

Java多线程编程核心技术笔记:第1章 多线程技能

    第一次为true,说明interrupt设立的中止标记生效了,但是第二次却打印出了false。
    这是为啥呢?

    官方文档中说:方法interrupted测试当前方法是否已经中断。线程的中断状态由该方法清除。

因为第一次调用的时候清除了其中断状态,所以第二次调用interrupted返回的是false。

6.2.2 boolean isInterrupted()

   将上面的例子改为isInterruptd()后:

class MyThread extends Thread{
    @Override
    public void run(){
        for (int i = 0; i <50000 ; i++) {
            System.out.println("i="+(i+1));
        }
    }
}
public class Main{
    public static void main(String[] args) throws InterruptedException {
        MyThread thread=new MyThread();
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
        Thread.currentThread().interrupt();
        System.out.println("是否停止1?="+thread.isInterrupted());
        System.out.println("是否停止2?="+thread.isInterrupted());
        System.out.println("end!");
    }
}

结果:

 Java多线程编程核心技术笔记:第1章 多线程技能

方法isInterrupted()并未消除状态标识,所以打印出了两个true。

总结两个方法:

    this.interrupted():测试当前方法是否已经是中断状态,执行后有将状态清除为false的功能。

    this.isInterrupted():测试当前方法是否是中断状态,但是不清除状态标识。

6.3 能停止的线程

     6.3.1异常法

    在主线程中使用interrupt()来中断MyThread线程,在MyThread线程中使用interrupted()方法确定线程已经中断。若中断就抛出InterruptedException()异常。

class MyThread extends Thread{
    @Override
    public void run() {
        try {
            for (int i = 0; i < 500000; i++) {
                if (this.interrupted()) {
                    System.out.println("已经是停止状态了。退出!");
                    throw new InterruptedException();
                }
                System.out.println("i="+(i+1) );
            }
        }catch(InterruptedException e){
            System.out.println("进入catch块中了");

        }
    }
}
public class Main{
    public static void main(String[] args) throws InterruptedException {
        MyThread thread=new MyThread();
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
        System.out.println("end!");
    }
}

6.3.2 使用return停止线程

    将interrupted与return结合使用也能实现停止线程的效果。

class MyThread extends Thread{
    @Override
    public void run() {
            for (int i = 0; i < 500000; i++) {
                if (this.interrupted()) {
                    System.out.println("已经是停止状态了。退出!");
                    return;
                }
                System.out.println("i="+(i+1) );
            }
    }
}
class ThreadDemo extends Thread{
    @Override
    public void run() {
        long beginTime=System.currentTimeMillis();
        long count=0;
        for(int i=0;i<50000000;i++){
            //Thread.yield();
            count=count+(i+1);
        }
        long endTime=System.currentTimeMillis();
        System.out.println("用时:"+(endTime-beginTime)+"毫秒!");
    }
}

public class yieldDemo {
    public static void main(String[] args) {
        System.out.println("start Thread");
        ThreadDemo thread=new ThreadDemo();
        thread.start();
    }

}

public class Main{ public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); Thread.sleep(1000); thread.interrupt(); System.out.println("end!"); }}

但是最好别采用return来进行终止线程,防止方法中return过多,造成污染。推荐使用异常法进行控制线程的终止。

7.yield方法

yield方法的作用:放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。但是放弃的时间不确定,有可能刚放弃,马上又获得CPU时间。

class ThreadDemo extends Thread{
    @Override
    public void run() {
        long beginTime=System.currentTimeMillis();
        long count=0;
        for(int i=0;i<50000000;i++){
            //Thread.yield();
            count=count+(i+1);
        }
        long endTime=System.currentTimeMillis();
        System.out.println("用时:"+(endTime-beginTime)+"毫秒!");
    }
}

public class yieldDemo {
    public static void main(String[] args) {
        System.out.println("start Thread");
        ThreadDemo thread=new ThreadDemo();
        thread.start();
    }

} 

 结果:

Java多线程编程核心技术笔记:第1章 多线程技能

解开//Thread.yield()后:

Java多线程编程核心技术笔记:第1章 多线程技能

8.线程的优先级

    在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。设置优先级有助于帮助"线程规划器"确定在下一次选择哪一个线程来优先执行。

    JDK的Thread类中使用3个常量来预置定义优先级的值:

    

public final static int MIN_PRIORITY=1;
public final static int NORM_PRIORITY=5;
public final static int MAX_PRIORITY=10;

   实例:

class MyThread1  extends Thread{
    @Override
    public void start() {
        System.out.println("MyThread run priority: "+this.getPriority());
        MyThread2 thread2=new MyThread2();
        thread2.start();
    }

}
class MyThread2 extends  Thread{

    public void start() {
            System.out.println("mythread2 run priority:"+this.getPriority());
    }
}
//优先级默认是5,调用的方法的优先级和被调用方法的优先级一致
public class Run1{
    public static void main(String[] args) {
        MyThread1 thread1=new MyThread1();
        thread1.start();
        Thread.currentThread().setPriority(6);
        System.out.println("Main run priority:"+Thread.currentThread().getPriority());
        MyThread2 thread2=new MyThread2();
        thread2.start();
    }
}

结果:

Java多线程编程核心技术笔记:第1章 多线程技能
    线程的默认优先级是NORM_PRIORITY,值为5。通过上例发现:A线程调用B线程,A线程优先级和B线程优先级是相同的。

9.守护线程

在java中有两种线程,一种是用户线程,一种是守护(Daemon)线程。
什么是守护线程?守护线程的特性有陪伴的含义。当进程中没有非守护线程了,则守护线程自动销毁。典型的守护线程是垃圾回收机制。
class MyThread extends Thread {
    private int i=0;

    @Override
    public void run() {
        while(true){
            try {
                i++;
                System.out.println("i="+(i));
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Run2{
    public static void main(String[] args) {
        try {
            MyThread thread=new MyThread();
            thread.setDaemon(true);
            thread.start();
            Thread.sleep(5000);
            System.out.println("我离开thread对象也不再打印了,也就是停止了!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
 结果:

Java多线程编程核心技术笔记:第1章 多线程技能

可以看出,当Main线程结束的时候,守护线程也结束了。