[关闭]
@w460461339 2016-11-16T07:09:02.000000Z 字数 7218 阅读 825

Java学习day12(多线程)

Java基础


妈蛋= - 今天因为安装机械手出错被批评了…自己怎么那么不细心。下次安装的时候千万不要图省事漏安装零件,老老实实一个一个的安装!

(反省完毕)

今天主要学习了一下多线程的定义,构造以及使用,下面直接开始吧。

1.多线程定义

首先,我们需要知道什么是进程。

打开任任务管理器后,我们发现里面有一个进程选项,可以查看当前开启的进程。因此,我们知道,进程就是目前正在运行的程序。

多进程概述:

正在运行的程序,是系统进行资源分配和清理使用的独立单位
每一个进程都有它自己的内存空间和系统资源

而对于一个类似于迅雷这样的程序,可以同时对多个目标进行下载,那么每一个下载都是一个单独的线程,因此每一个进程里面可以包含多线程。

多线程概述:

是进程中的单个顺序控制流,是一条执行语句;
一个进程如果只有一条执行路径,就是单线程的;
一个进程如果有多条志新路径,就是多线程的。

多进程的目的是提高CPU的使用率;
多线程的目的是让该进程更加重要,提高该进程使用CPU的使用率。

另外,多线程具有随机性, 不敢保证在哪个时刻是哪个线程抢到了CPU资源。

这就是,多线程。

2.多线程使用

![此处输入图片的描述][1]

2.1多线程使用方式 Way1:继承Tread类

让自定义类进程Thread类,并重写run方法,在run方法中放置需要在线程中跑的程序。在测试类中创建该自定义类的对象,并调用start方法即可(该方法在Thread中)

构造类

  1. package myThread;
  2. //继承Thread类
  3. public class MyThreadDemo extends Thread {
  4. //重写run方法,将要在线程中运行的程序写入;
  5. //一般都是写入比较费时的程序
  6. @Override
  7. public void run() {
  8. // TODO Auto-generated method stub
  9. for(int i=0;i<200;i++){
  10. //getName方法,得到当前类的名字
  11. System.out.println(this.getName()+":"+i);
  12. }
  13. }
  14. }

测试类

  1. package myThread;
  2. public class myThread_test {
  3. public static void main(String[] args) {
  4. //创建自定义类对象;(也是创建线程对象)
  5. //需要几个线程就创建几个对象
  6. MyThreadDemo mtd1=new MyThreadDemo();
  7. MyThreadDemo mtd2=new MyThreadDemo();
  8. //重置当前类的名字
  9. mtd1.setName("6666");
  10. mtd2.setName("7777");
  11. //开始线程,注意不能用run
  12. mtd1.start();
  13. mtd2.start();
  14. //获取主线程的名字
  15. //这里除了main主线程外,还有一个回收机制,
  16. //所以算上自己写的两个,应该有4个线程。
  17. System.out.println(Thread.currentThread().getName());
  18. }
  19. }
2.2多线程的优先级

多线程调用CPU资源采用抢占式调用模型,优先级越高越有可能抢到,优先级相同就随机分配。(注意,不是优先级高就一定能够抢到的!)
另外,默认优先级是5,最低是1,最高是10,只能在这个范围内选取。
构造类同上

  1. package myThread;
  2. public class myThread_test {
  3. public static void main(String[] args) {
  4. MyThreadDemo mtd1=new MyThreadDemo();
  5. MyThreadDemo mtd2=new MyThreadDemo();
  6. mtd1.setName("6666");
  7. mtd2.setName("7777");
  8. //输出默认优先级,为5
  9. System.out.println(mtd1.getPriority());
  10. //重新设置其优先级为10
  11. mtd1.setPriority(10);
  12. //结果可以发现,mtd1和mtd2仍然会交叉出现,
  13. //但mtd1明显在前面出现的多。
  14. mtd1.start();
  15. mtd2.start();
  16. System.out.println(Thread.currentThread().getName());
  17. }
  18. }
2.3线程休眠(sleep)

sleep方法,让当前线程暂停一定时间。因为针对当前线程,所以是写在构造类中。
构造类

  1. package myThread;
  2. import java.util.Date;
  3. public class MyThreadDemo extends Thread {
  4. @Override
  5. public void run() {
  6. // TODO Auto-generated method stub
  7. for(int i=0;i<200;i++){
  8. System.out.println(this.getName()+":"+i);
  9. System.out.println(new Date());
  10. //由于覆盖了方法, 所以异常只能try catch
  11. try {
  12. //休眠1000ms,当前线程进入阻塞状态。
  13. Thread.sleep(1000);
  14. } catch (InterruptedException e) {
  15. // TODO Auto-generated catch block
  16. e.printStackTrace();
  17. }
  18. }
  19. }
  20. }
2.4线程加入(join)

等待该线程终止。

构造类就是最简单的,没有sleep函数的那种

  1. public class MyThreadJoin {
  2. //在调用join方法是抛出异常
  3. public static void main(String[] args) throws InterruptedException {
  4. //创建自定义对象
  5. MyThreadDemo myd1=new MyThreadDemo();
  6. MyThreadDemo myd2=new MyThreadDemo();
  7. MyThreadDemo myd3=new MyThreadDemo();
  8. //设置名字
  9. myd1.setName("Thread1");
  10. myd2.setName("Thread2");
  11. myd3.setName("Thread3");
  12. //开始Thread1
  13. myd1.start();
  14. //设置仅当Thread1走完了,才能走Thread2,以及3
  15. myd1.join();
  16. myd2.start();
  17. //设置仅当Thread2走完了才能走Thread3
  18. myd2.join();
  19. myd3.start();
  20. }
  21. }
2.5线程礼让(yield)

暂停当前正在执行的线程对象,并执行其他线程。

  1. //构造类
  2. public class MyThreadDemo extends Thread {
  3. @Override
  4. public void run() {
  5. for(int i=0;i<200;i++){
  6. System.out.println(this.getName()+":"+i);
  7. Thread.yield();
  8. }
  9. }
  10. }
  1. package myThread;
  2. public class MyThreadJoin {
  3. public static void main(String[] args) throws InterruptedException {
  4. MyThreadDemo myd1=new MyThreadDemo();
  5. MyThreadDemo myd2=new MyThreadDemo();
  6. myd1.setName("Thread1");
  7. myd2.setName("Thread2");
  8. myd1.start();
  9. myd2.start();
  10. }
  11. }

很大概率出现你一次我一次的情况

2.6后台线程(setDaemon)

将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。

意思是,在某个线程A里又调用的其他的线程B和C,设置B和C为A的后台线程,则当A结束时,B和C一定会马上结束。

  1. package myThread;
  2. public class MysetDaemon {
  3. public static void main(String[] args) {
  4. MyThreadDemo myd1=new MyThreadDemo();
  5. MyThreadDemo myd2=new MyThreadDemo();
  6. myd1.setName("张飞");
  7. myd2.setName("关羽");
  8. //此时这些程序都在主线程里开启
  9. //myd1和myd2设置为主线程的后台(守护)线程
  10. //当主线程完了时,不管myd1和myd2是否走完了,都不会继续下一个
  11. //但会向前抽抽两下…
  12. myd1.setDaemon(true);
  13. myd2.setDaemon(true);
  14. Thread.currentThread().setName("刘备");
  15. for(int i=0;i<10;i++){
  16. System.out.println(Thread.currentThread().getName()+i);
  17. }
  18. myd1.start();
  19. myd2.start();
  20. }
  21. }
  1. public class MyThreadDemo extends Thread {
  2. @Override
  3. public void run() {
  4. //次数要够多,不然太少,会在抽抽的时候抽光,就看不出效果。
  5. for(int i=0;i<1000;i++){
  6. System.out.println(this.getName()+":"+i);
  7. }
  8. }
  9. }
2.7线程中断(interrupt)

比如在构造类中,要让当前线程睡10秒再走,但是在测试类中说,只能睡3秒,3秒之后就起。这个时候就需要用到interrupt
构造类

  1. public class MyThreadDemo extends Thread {
  2. @Override
  3. public void run() {
  4. try {
  5. Thread.sleep(30000);
  6. } catch (InterruptedException e) {
  7. System.out.println("线程被中断");
  8. }
  9. System.out.println("线程结束");
  10. }
  11. }

测试类

  1. public class MyThreadInterrupt {
  2. public static void main(String[] args) {
  3. MyThreadDemo myd1=new MyThreadDemo();
  4. myd1.start();
  5. //开始只让睡3s,之后中断
  6. //此时构造类中有sleep仍在执行,因此interrupt向构造类中sleep抛出一个InterruptedException的异常,中断构造类中sleep
  7. //
  8. try {
  9. Thread.sleep(3000);
  10. myd1.interrupt();
  11. } catch (InterruptedException e) {
  12. // TODO Auto-generated catch block
  13. e.printStackTrace();
  14. }
  15. }
  16. }

因此显示结果为:线程被中断,线程结束。

2.8 多线程构造方式 Way2:实现Runnable接口

构造类

  1. public class MyThreadDemo implements Runnable {
  2. @Override
  3. public void run() {
  4. // TODO Auto-generated method stub
  5. for(int i=0;i<1000;i++){
  6. //注意,由于实现了Runnable接口,所以不能直接利用this.getName来获取当前线程的名字
  7. //只能通过利用Tread.currentThread().getName()来得到
  8. System.out.println(Thread.currentThread().getName()+
  9. ":"+i);
  10. }
  11. }
  12. }

测试类

  1. public class MyTreadWay2 {
  2. public static void main(String[] args) {
  3. //只需要创建一个测试类对象
  4. MyThreadDemo myd=new MyThreadDemo();
  5. //就可以构造出许多的线程
  6. Thread td1=new Thread(myd,"Thread1");
  7. Thread td2=new Thread(myd,"Thread2");
  8. Thread td3=new Thread(myd,"Thread3");
  9. td1.start();
  10. td2.start();
  11. td3.start();
  12. }
  13. }

第二种创建多线程的方法比较好的实现了数据(一般放在run外面)和程序(放在run里面)的分离

3.多线程实例——卖票程序

需求:
三个售票窗口,总共一百张票,出完为止。
网络会出现一些延迟,需要添加延迟消除网络延迟影响。
要求:
不能重复卖票;
不能多卖票;

3.1实现方法

利用第一种方法和第二种方法实现多线程,来创建这个程序,无非就是成员变量是不是静态的问题。因为第二种方法能够更好的表现出数据和程序的分离,所以这里使用第二种方法。
构造类

  1. package sellTicket;
  2. public class Ticket implements Runnable{
  3. private int ticket=100;
  4. @Override
  5. public void run() {
  6. //这个while是为了保证有票就一直卖
  7. while(true){
  8. if(ticket>0){
  9. //延迟,消除网络延迟影响
  10. try {
  11. Thread.sleep(1000);
  12. } catch (InterruptedException e) {
  13. // TODO Auto-generated catch block
  14. e.printStackTrace();
  15. }
  16. System.out.println(Thread.currentThread().getName()+"已经出售第"+(ticket--)+"张票");
  17. }
  18. }
  19. }
  20. }

测试类

  1. package sellTicket;
  2. public class Sell {
  3. public static void main(String[] args) {
  4. Ticket tc=new Ticket();
  5. Thread td1=new Thread(tc,"售票窗口1");
  6. Thread td2=new Thread(tc,"售票窗口2");
  7. Thread td3=new Thread(tc,"售票窗口3");
  8. td1.start();
  9. td2.start();
  10. td3.start();
  11. }
  12. }

此时会出现重复卖票以及卖到第0张和第-1张票的情况。

3.2存在问题及解决方案分析

A:重复卖票
原因:CPU的一次操作必须是原子性的。
B:出现了负数
原因:随机性和延迟导致的。

解决方案:
在A线程执行时,锁死该部分程序,直到A线程该部分程序结束,才能继续多个线程继续争抢。

3.3解决程序,Synchronized关键字

构造类,测试类是一样的

  1. package sellTicket;
  2. public class Ticket implements Runnable{
  3. private int ticket=100;
  4. private Object obj=new Object();
  5. @Override
  6. public void run() {
  7. while(true){
  8. //利用synchronized保住所有需要锁住的代码
  9. //obj可以是任意类的任意对象,但必须定义在run外
  10. synchronized(obj){
  11. if(ticket>0){
  12. try {
  13. Thread.sleep(100);
  14. } catch (InterruptedException e) {
  15. // TODO Auto-generated catch block
  16. e.printStackTrace();
  17. }
  18. System.out.println(Thread.currentThread().getName()+"已经出售第"+(ticket--)+"张票");
  19. }
  20. }
  21. }
  22. }
  23. }

synchronized的格式:

synchronized(类的对象){
    要锁住的代买
}

方法也可以被锁住:

  private static synchronized long nextThreadID() {
    return ++threadSeqNumber;
}

synchronized这个其实叫做同步代码块,锁住需要被锁住的代码内容。主要用来保证多线程的安全。具体的的深入了解还没有= -

3.4如何判断线程是否有错误

线程是否有问题的判断标准:

A:是否是多线程环境
B:是否有共享数据(比如例子中成员变量ticket就是)
C:是否多条操作语句操作共享数据

如何解决问题:

基本思想:把多个语句操作共享数据的代码块锁起来,让任意时刻只能有一个线程执行这些代码块
3.5同步的特点(synchronized的特点)

同步的前提:

A:多线程
B:多个线程使用的是同一个锁对象(定义在run外的obj)

同步的好处以及弊端:

好处:解决了多线程安全问题;
弊端:线程太多时,会消耗大量资源

锁对象问题:

同步代码块:锁对象为任意类的任意对象
同步成员方法:锁对象为this,及本类的当前对象
同步静态方法/变量:锁对象为类的字节码对象(反射内容)
3.6如何创建线程安全的ArrayList

之前讲过,vector是线程安全的,ArrayList是线程不安全的。但是当我们需要线程安全时,也不用vector,是因为我们有更高效的方式。

synchronizedCollection(Collection< T > c);
返回指定 collection 支持的同步(线程安全的)collection。

synchronizedList(List< T > list) 返回指定列表支持的同步(线程安全的)列表。

……还有几个就不列了,那么可以通过这个来创建一个线程安全的ArrayList

  1. List<String> list=Collections.synchronizedList(
  2. new ArrayList<String>());

(终于全部写完了…可以安心的玩一会了~)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注