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基础学习笔记10_多线程
- 黑马程序员—10—java基础:有关多线程安全的学习笔记和学习心得体会
- [置顶] JavaSE学习笔记_10:Java多线程
- Java多线程与并发库高级应用 学习笔记 10-16课
- Java 多线程编程 学习笔记
- Java多线程学习笔记1
- C\C++ 程序员从零开始学习Android - 个人学习笔记(十) - java基础 - 多线程(待续)
- 孙鑫JAVA学习笔记9-10
- 学习java多线程的笔记4--传智播客_张孝祥_空中网挑选实习生的面试题(来源于视频)
- 学习笔记7—Java基础5_多线程
- Java学习笔记---多线程
- java多线程学习笔记3
- Java 学习笔记 (10) - Java 函数的递归调用
- 学习java多线程的笔记5--初体验
- 学习java多线程的笔记1--Thread(Runnable t)与重写run()方法等
- Java学习笔记10:求两个数的最小公倍数和最大公约数
- Java学习笔记18天---(10)
- Java学习笔记(五、多线程)
- Effective Java 学习笔记 (10)
- Java学习笔记---10.面向对象编程05-面向对象程序的设计步骤