您的位置:首页 > 编程语言 > Java开发

java学习笔记10——多线程的学习

2017-04-11 14:44 417 查看

这一段主要写一下关于java多线程的一些知识。这里仅仅简单的介绍一下,java多线程一直是编程的重点,有很多知识点讲都讲不完,需要很深入的学习和总结。

一、线程和进程

这里摘抄一下概念:

进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。

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

  上述概念其实也就是一个大概,可以很抽象的理解,大家有没有见过网线,一根网线里面有8根颜色不同的铜丝,这里就可以把里面的8根理解成线程,而那根网线就是一个进程,是一个包含的关系。

  多线程就是在宏观上具有不止一个线程在程序中运行。这里需要注意,这里我们以单核处理器为依据,在宏观时间上来分析,如果是微观时间上,那么还是一个个进程来处理的,就看谁可以抢到运行资源了。大家看图:



横着表示多线程,并发

斜线表示多进程,并行

  这里如果要深入理解的话,建议大家可以去看一下操作系统的书,里面讲的很详细,这里不做叙述。

二、实现多线程

在java中,实现多线程主要有两种方法,一种,继承Thread类,还有一种是实现runable接口

1、继承Thread类

继承Thread类,然后重写其中的run()方法。

public class ThreadDemo extends Thread {
private String name;
public ThreadD
4000
emo(String name) {
this.name = name;
}

public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "线程" + i);
try {
sleep(100); //不让当前线程一直霸占资源
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public class Test {
public static void main(String[] args) {
ThreadDemo threadDemo1 = new ThreadDemo("A");
ThreadDemo threadDemo2 = new ThreadDemo("B");
threadDemo1.start();
threadDemo2.start();
}
}


运行结果:

A线程0
B线程0
A线程1
B线程1
A线程2
B线程2
A线程3
B线程3
A线程4
B线程4


  大家看,这里建立了两个线程,然后对其进行start()操作,就是让线程就绪,当抢占到资源的时候就会开始运行。看输出结果也是A和B穿插着的,这就是谁抢到资源就会谁运行。

2、实现runable接口

实现runable接口,实现run()方法。

public class RunableDemo implements Runnable {

private String name;
public RunableDemo(String name) {
this.name = name;
}

@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "线程" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

public class RunableTest {
public static void main(String[] args) {
RunableDemo runableDemo1 = new RunableDemo("C");
RunableDemo runableDemo2 = new RunableDemo("D");
new Thread(runableDemo1).start();
new Thread(runableDemo2).start();
}
}


结果:

C线程0
D线程0
D线程1
C线程1
C线程2
D线程2
D线程3
C线程3
D线程4
C线程4


  这也是一种实现多线程的方法。在这里需要注意,在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

三、thread和runable的区别

这里看两个例子:

public class ThreadDemo extends Thread {

private String name;
private int count = 10;

public ThreadDemo(String name) {
this.name = name;
}

public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(count--);
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

public class Test {
public static void main(String[] args) {
ThreadDemo threadDemo1 = new ThreadDemo("A");
ThreadDemo threadDemo2 = threadDemo1;
threadDemo1.start();
threadDemo2.start();
}
}


结果:

Exception in thread "main" 10
java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:705)
at com.songqijie.demo.thread.Test.main(Test.java:28)
9
8
7
6


用runable来实现:

public class RunableDemo implements Runnable {

private String name;
private int count = 10;

public RunableDemo(String name) {
this.name = name;
}

@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "线程" + (count--));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

public class RunableTest {
public static void main(String[] args) {
RunableDemo runableDemo1 = new RunableDemo("C");
RunableDemo runableDemo2 = runableDemo1;
new Thread(runableDemo1).start();
new Thread(runableDemo2).start();
}
}


运行结果:

C线程10
C线程9
C线程8
C线程7
C线程6
C线程5
C线程4
C线程3
C线程2
C线程1


  可见,对于相同的一个实例,使用runable接口可以生成多个线程运行,而Thread相当于对相同的线程进行操作,这样就报错了,这里如果是一个购票系统或者其他的有固定的数量的系统,那么肯定还是runable更加的安全和实用。

可见,runable实现多线程更具有优势

1. 适合多个相同的程序代码的线程去处理同一个资源

2. 可以避免java中的单继承的限制

3. 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

四、线程中的一些常用方法

线程分为5个阶段:新建,就绪,运行,阻塞,死亡



1. 在线程中,可以设置其优先级,优先级高的线程可以获得较多的运行资源。

- Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:

- static int MAX_PRIORITY

线程可以具有的最高优先级,取值为10。

- static int MIN_PRIORITY

线程可以具有的最低优先级,取值为1。

- static int NORM_PRIORITY

分配给线程的默认优先级,取值为5。

  Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。

每个线程都有默认的优先级。主线程的默认优先级为
Thread.NORM_PRIORITY。


  线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。

  JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。

2. sleep(long millis):

指在制定的时间内让线程进行休眠,也就是暂停执行操作

3. join():

join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。

看下面的代码:

不加join():

public class ThreadDemo extends Thread {

private String name;
private int count = 10;

public ThreadDemo(String name) {
this.name = name;
}

public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "线程" + count--);
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) {
System.out.println("主线程开始");
ThreadDemo threadDemo1 = new ThreadDemo("A");
ThreadDemo threadDemo2 = new ThreadDemo("B");
threadDemo1.start();
threadDemo2.start();
System.out.println("主线程结束");
}
}


运行结果:

主线程开始
主线程结束
A线程10
B线程10
B线程9
A线程9
B线程8
A线程8
A线程7
B线程7
B线程6
A线程6


加join():这里只修改test类

public class Test {
public static void main(String[] args) {
System.out.println("主线程开始");
ThreadDemo threadDemo1 = new ThreadDemo("A");
ThreadDemo threadDemo2 = new ThreadDemo("B");
threadDemo1.start();
threadDemo2.start();
try {
threadDemo1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
threadDemo2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程结束");
}
}


运行结果:

主线程开始
A线程10
B线程10
A线程9
B线程9
A线程8
B线程8
B线程7
A线程7
A线程6
B线程6
主线程结束


  可以发现,加了join,主线程会等待join的结束再运行,而不是和join的线程增多资源。

这里需要注意!join之前需要先把线程start(),而不能先join再start

4. yield():

  Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。

  yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

  yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。可看上面的图。

sleep()和yield()的区别

  sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。

   sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程

   另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。

5. interrupt():

  中断某个线程,这种结束方式比较粗暴,如果t线程打开了某个资源还没来得及关闭也就是run方法还没有执行完就强制结束线程,会导致资源无法关闭

6. wait()

  Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

五、线程同步

这里首先需要说一下关于线程安全的概念:

线程安全:

  当多个线程类并发操作某类的某个方法,(在该方法内部)来修改这个类的某个成员变量的值,不会出错,则我们就说,该的这个方法是线程安全的。

某类的某方法是否线程安全的关键是:

- 该方法是否修改该类的成员变量;

- 是否给该方法加锁(是否用synchronized关键字修饰)。

线程不安全:

  当多个线程类并发操作某类的某个方法,(在该方法内部)来修改这个类的某个成员变量的值,很容易就会发生错误,故我们就说,这个方法是线程不安全的。如果要把这个方法变成线程安全的,则用 synchronized关键字来修饰该方法即可。

  注:用 synchronized关键字修饰方法,会导致加锁,虽然可以使该方法线程安全,但是会极大的降低该方法的执行效率,故要慎用该关键字。

  

1. synchronized关键字的作用域有二种:

是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;

是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。

2.除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/区块/},它的作用域是当前对象;

3.synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;

  总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。

在进一步阐述之前,我们需要明确几点:

A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。

B.每个对象只有一个锁(lock)与之相关联。

C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

这里举一个例子:

不加synchronized:

public class ThreadDemo extends Thread {
public static void main(String[] args) {
HasSelfPrivateNum numRef = new HasSelfPrivateNum();
ThreadA athread = new ThreadA(numRef);
athread.start();
ThreadB bthread = new ThreadB(numRef);
bthread.start();
}
}

class HasSelfPrivateNum {
private int num = 0;
public void addI(String username) {
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}

@Override
public void run() {
super.run();
numRef.addI("a");
}
}

class ThreadB extends Thread {
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}

@Override
public void run() {
super.run();
numRef.addI("b");
}
}


运行结果:

a set over!
b set over!
b num=200
a num=200


加了synchronized:只修改一下部分:

synchronized public void addI(String username) {
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}


运行结果:

a set over!
a num=100
b set over!
b num=200


  从运行结果可以发现,加了锁和没加锁是不通的,加了锁以后就编程线程安全了,不会进行资源的互相争抢。

  线程锁博大精深,这里我也就说了一个大概,大家有兴趣可以去买本java多线程编程看看。

参考资料:

http://blog.csdn.net/gf771115/article/details/51682561

http://www.importnew.com/20444.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 多线程