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

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("运行结束");
	}
}

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

(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();
			}
		}
	}
}

结果:



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("运行结束!");
	}
}


    一共创建了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("运行结束!");
	}
}

  可以看出,线程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();
	}
}


    我们发现,构造方法是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());

    }

结果为:

   

     *在使用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();
    }
}
运行结果:


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();
    }
}

结果:

从结果看,调用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!");
    }
}

结果:


    用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!");
    }
}

结果为:


    第一次为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!");
    }
}

结果:

 

方法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();
    }

} 

 结果:

解开//Thread.yield()后:

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();
    }
}

结果:


    线程的默认优先级是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();
        }

    }
}
 结果:

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

猜你喜欢

转载自blog.csdn.net/sheng_Mu555/article/details/80296885