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类
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线程结束的时候,守护线程也结束了。
上一篇: 源码剖析——HashMap、HashTable、HashSet的区别
下一篇: EL表达式