Java多线程(1)

来源:互联网 发布:java socket链接不上 编辑:程序博客网 时间:2024/06/07 18:29
23.01 多线程程序的引入
23.02 进程概述及多进程的意义
23.03 线程概述及多线程的意义
23.04 并行和并发的区别
23.05 Java程序运行原理和JVM的启动是多线程的吗
23.06 实现多线程及多线程方式1的思路
23.07 多线程方式1的代码实现
23.08 获取和设置线程对象名称
23.09 线程调度及获取和设置线程优先级
23.10 线程控制之休眠线程
23.11 线程控制之加入线程
23.12 线程控制之礼让线程
23.13 线程控制之守护线程
23.14 线程控制之中断线程
23.15 线程生命周期图解
23.16 多线程方式2的思路及代码实现
23.17 实现接口方式的好处
23.18 继承Thread类的方式卖电影票案例
23.19 实现Runnable接口的方式卖电影票案例
23.20 买电影票出现了同票和负数票的原因分析
23.21 线程安全问题的产生原因分析
23.22 同步代码块的方式解决线程安全问题
23.23 同步的特点及好处和弊端
23.24 同步代码块的锁及同步方法应用和锁的问题

23.25 以前的线程安全的类回顾


23.01  多线程程序的引入

如果一个程序只有一个执行流程,所以这样的程序就是单线程程序。

如果一个程序有多条执行流程,那么,该程序就是多线程程序。

23.02  进程概述及多进程的意义

要想说线程,首先必须得知道进程,因为线程是依赖于进程存在的

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

 

多进程意义:多进程的作用不是提高执行速度,而是提高CPU的使用率

单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),所以我们常见的操作系统都是多进程操作系统。

比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。

 

对于单核计算机来讲,游戏进程和音乐进程不是同时运行的,因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。

23.03  线程概述及多线程的意义

在一个进程内部又可以执行多个任务,而这每一个任务就可以看成是一个线程。线程是程序中单个顺序的控制流,是程序使用CPU的基本单位。

线程:

是进程中的单个顺序控制流,是一条执行路径

一个进程如果只有一条执行路径,则称为单线程程序。

一个进程如果有多条执行路径,则称为多线程程序。

多线程意义:   多线程的作用也不是提高执行速度,而是为了提高应用程序的使用率。

多个线程共享同一个进程的资源(堆内存和方法区),但是栈内存是独立的,一个线程一个栈。所以他们仍然是在抢CPU的资源执行。一个时间点上只有能有一个线程执行。而且谁抢到,这个不一定,所以,造成了线程运行的随机性。

23.04  并行和并发的区别

注意两个词汇的区别:并行和并发

并行是逻辑上同时发生,指在某一个时间内同时运行多个程序

并发是物理上同时发生,指在某一个时间点同时运行多个程序

23.05  Java程序运行原理和JVM的启动是多线程的吗

Java程序运行原理:java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个“主线程”,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。

由于JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的

23.06  实现多线程及多线程方式1的思路

由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。Java是不能直接调用系统功能的,但是Java可以去调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,然后提供一些类供我们使用。我们就可以实现多线程程序了。

Java提供的类 Thread

通过查看API,知道有2中方式实现多线程程序。

方式1:继承Thread类

步骤:

1:自定义类MyThread继承Thread类。

2:MyThread类重写run()方法

3:创建对象

4:启动线程

23.07  多线程方式1的代码实现

例:

复制代码
 1 public class Practice  2 { 3     public static void main(String[] args) 4     { 5         //创建对象 6         //MyThread mt = new MyThread(); 7         //run()方法直接调用其实就相当于普通的方法调用,所以看到的是单线程的效果 8         //要想看到多线程的效果,就必须使用另一个方法:start() 9         //mt.run();10         // 创建两个线程对象11         MyThread my1 = new MyThread();12         MyThread my2 = new MyThread();13 14         my1.start();15         my2.start();16         17     }18 }19 //继承Thread类20 class MyThread extends Thread 21 {22     //重写run()方法23     @Override24     public void run() 25     {26         // 一般来说,被线程执行的代码肯定是比较耗时的。所以用循环改进27         for (int x = 0; x < 200; x++) 28         {29             System.out.println(x);30         }31     }32 }
复制代码

run()和start()的区别

run():仅仅是封装被线程执行的代码,直接调用是普通方法

start():首先启动了线程,然后再由jvm去调用该线程的run()方法

继承Thread类的类为什么要重写run()方法?

不是类中的所有代码都需要被线程执行的。而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。

23.08  获取和设置线程对象名称

public final String getName():返回该线程的名称

public final void setName(String name):改变线程名称,使之与参数 name 相同。

例:

复制代码
 1 public class Practice  2 { 3     public static void main(String[] args) 4     { 5 //        MyThread my1 = new MyThread(); 6 //        MyThread my2 = new MyThread(); 7 // 8 //        //调用方法设置名称 9 //        my1.setName("线程1");10 //        my2.setName("线程2");11 //        //启动线程12 //        my1.start();13 //        my2.start();14         15         //带参构造方法给线程起名字16          MyThread my1 = new MyThread("线程1");17          MyThread my2 = new MyThread("线程2");18          my1.start();19          my2.start();20          21         //获取main方法所在的线程对象的名称22         //public static Thread currentThread():返回当前正在执行的线程对象23         System.out.println(Thread.currentThread().getName());24         25     }26 }27 class MyThread extends Thread 28 {29     30     public MyThread()31     {32         super();33     }34     35     public MyThread(String name) 36     {37         super(name);38     }39     @Override40     public void run() 41     {42         for (int x = 0; x < 200; x++) 43         {44             System.out.println(getName()+":"+x);45         }46     }47 }
复制代码

运行结果:

main线程1:0(省略部分结果)线程2:8(省略部分结果)

通过Thread类的getName( )方法可以获取线程的名称,默认的命名方式是:Thread-编号(从0开始)

为什么名称是:Thread-编号?

看源码:

复制代码
 1 class Thread 2 { 3     private char name[]; 4     private static int threadInitNumber; 5     private static synchronized int nextThreadNum()  6     { 7         return threadInitNumber++; 8     } 9     public Thread() 10     {11         init(null, null, "Thread-" + nextThreadNum(), 0);12     }13     public final String getName() 14     {15         return String.valueOf(name);16     }17     private void init(ThreadGroup g, Runnable target, String name,long stackSize) 18     {19         init(g, target, name, stackSize, null);20     }21     22     private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) 23     {24         //省略部分代码25         this.name = name.toCharArray();26     }27 }
复制代码

23.09  线程调度及获取和设置线程优先级

线程调度:

假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?

线程有两种调度模型:

分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。

Java使用的是抢占式调度模型。

 

设置和获取线程优先级

public final int getPriority():返回线程的优先级。

public final void setPriority(int newPriority):更改线程的优先级。

例:

复制代码
 1 public class Practice  2 { 3     public static void main(String[] args) 4     { 5         MyThread my1 = new MyThread(); 6         MyThread my2 = new MyThread(); 7         MyThread my3 = new MyThread(); 8          9         //设置优先级,最小为1,最大为1010         my3.setPriority(9);11         //获取优先级,默认为512         System.out.println(my1.getPriority());//513         System.out.println(my2.getPriority());//514         System.out.println(my3.getPriority());//915         16         my1.start();17         my2.start();18         my3.start();//获取的 CPU 时间片相对多一些19     }20 }21 class MyThread extends Thread 22 {23     @Override24     public void run() 25     {26         for (int x = 0; x < 200; x++) 27         {28             System.out.println(getName()+":"+x);29         }30     }31 }
复制代码

23.10  线程控制之休眠线程

public static void sleep(long millis)throws InterruptedException

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。

例:

复制代码
 1 class MyThread extends Thread  2 { 3     @Override 4     public void run()  5     { 6         for (int x = 0; x < 5; x++)  7         { 8             System.out.println(getName()+":"+x+":"+new Date()); 9             try 10             {11                 //休眠1秒12                 Thread.sleep(1000);13             } 14             catch (InterruptedException e) 15             {16                 e.printStackTrace();17             }18         }19     }20 }
复制代码

23.11  线程控制之加入线程

public final void join()throws InterruptedException

等待该线程终止。

例:

复制代码
 1 public class Practice  2 { 3     public static void main(String[] args) 4     { 5         MyThread my1 = new MyThread(); 6         MyThread my2 = new MyThread(); 7         MyThread my3 = new MyThread(); 8          9         my1.setName("线程1");10         my2.setName("线程2");11         my3.setName("线程3");12         13         my1.start();14         try 15         {16             //等待线程1线程,线程1运行完成后,线程2线程3开始争夺资源17             my1.join();18         } 19         catch (InterruptedException e) 20         {21             e.printStackTrace();22         }23         my2.start();24         my3.start();25     }26 }
复制代码

23.12  线程控制之礼让线程

public static void yield()

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

例:

复制代码
 1 class MyThread extends Thread  2 { 3     @Override 4     public void run()  5     { 6         for (int x = 0; x < 100; x++)  7         { 8             System.out.println(getName()+":"+x); 9             //暂停当前线程,并执行其他线程,不能保证机会均等10             Thread.yield();11         }12     }13 }
复制代码

23.13  线程控制之守护线程

public final void setDaemon(boolean on)

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

例:

复制代码
 1 public class Practice  2 { 3     public static void main(String[] args) 4     { 5         MyThread my1 = new MyThread(); 6         MyThread my2 = new MyThread(); 7          8         my1.setName("线程1"); 9         my2.setName("线程2");10         11         //将该线程标记为守护线程12         my1.setDaemon(true);13         my2.setDaemon(true);14         15         my1.start();16         my2.start();17         18         Thread.currentThread().setName("主线程");19         for (int i = 0; i < 20; i++) 20         {21             System.out.println(Thread.currentThread().getName()+":"+i);22         }23     }24 }
复制代码

23.14  线程控制之中断线程

1.public void interrupt() 中断线程。

2.public final void stop()  停止线程,已过时。该方法具有固有的不安全性。

例:

复制代码
 1 public class Practice  2 { 3     public static void main(String[] args) 4     { 5         MyThread my = new MyThread(); 6         my.setName("线程1"); 7         my.start(); 8         try  9         {10             Thread.sleep(2000);11             //2秒后终止my线程,使用stop()后面的语句都不会执行12             //my.stop();该方法已过时,具有不安全性13             my.interrupt();//后面的语句会执行14         } 15         catch (InterruptedException e) 16         {17             e.printStackTrace();18         }19     }20 }
复制代码

23.15  线程生命周期图解

23.16  多线程方式2的思路及代码实现

步骤:

1:自定义类MyRunnable实现Runnable接口

2:重写run()方法

3:创建MyRunnable类的对象

4:创建Thread类的对象,并把3步骤的对象作为构造参数传递

 

复制代码
 1 public class Practice  2 { 3     public static void main(String[] args) 4     { 5         // 创建MyRunnable类的对象 6         MyRunnable my = new MyRunnable(); 7  8         // 创建Thread类的对象,并把C步骤的对象作为构造参数传递 9         // 构造方法 Thread(Runnable target)10         // Thread t1 = new Thread(my);11         // Thread t2 = new Thread(my);12         // t1.setName("小明");13         // t2.setName("小红");14 15         // 构造方法 Thread(Runnable target, String name)16         Thread t1 = new Thread(my, "小明");17         Thread t2 = new Thread(my, "小红");18 19         t1.start();20         t2.start();21     }22 }23 class MyRunnable implements Runnable24 {25     @Override26     public void run() 27     {28         for (int i = 0; i < 200; i++) 29         {30             System.out.println(Thread.currentThread().getName()+":"+i);31         }32     }33 }
复制代码

23.17  实现接口方式的好处

1.可以避免由于Java单继承带来的局限性。

2.适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。

23.18  继承Thread类的方式卖电影票案例

某电影院电影票共有100张,有3个售票窗口售票,请设计一个程序模拟该电影院售票。

复制代码
 1 public class Practice  2 { 3     public static void main(String[] args) 4     { 5         sellTicket st1 = new sellTicket(); 6         sellTicket st2 = new sellTicket(); 7         sellTicket st3 = new sellTicket(); 8          9         st1.setName("窗口1");10         st2.setName("窗口2");11         st3.setName("窗口3");12         13         st1.start();14         st2.start();15         st3.start();16         17     }18 }19 class sellTicket extends Thread20 {21     // 定义100张票22     // private int tickets = 100;23     // 为了让多个线程对象共享这100张票,我们其实应该用静态修饰24     private static int tickets = 100;25 26     @Override27     public void run() 28     {29         // 定义100张票30         // 每个线程进来都会走这里,这样的话,每个线程对象相当于买的是自己的那100张票,这不合理,所以应该定义到外面31         // int tickets = 100;32 33         // 是为了模拟一直有票34         while (true) 35         {36             if (tickets > 0) 37             {38                 System.out.println(getName() + "正在出售第" + (tickets--) + "张票");39             }40         }41     }42 }
复制代码

23.19  实现Runnable接口的方式卖电影票案例

复制代码
 1 public class Practice  2 { 3     public static void main(String[] args) 4     { 5         SellTicket st = new SellTicket(); 6          7         new Thread(st, "窗口1").start(); 8         new Thread(st, "窗口2").start(); 9         new Thread(st, "窗口3").start();10     }11 }12 class SellTicket implements Runnable 13 {14     // 定义100张票15     private int tickets = 100;16 17     @Override18     public void run() 19     {20         while (true) 21         {22             if (tickets > 0) 23             {24                 System.out.println(Thread.currentThread().getName() + "正在出售第"25                         + (tickets--) + "张票");26             }27         }28     }29 }
复制代码

23.20  买电影票出现了同票和负数票的原因分析

例:

复制代码
 1 class SellTicket implements Runnable  2 { 3     // 定义100张票 4     private int tickets = 100; 5  6     @Override 7     public void run()  8     { 9         while (true) 10         {11             //该语句存在安全隐患,如果当票数还剩1张的时候,此时4个线程可能12             //由于CPU的随机切换而通过该语句,当执行下面的语句时就会出现负的13             //票数,同时也可能出现相同的票卖出多次14             if (tickets > 0) 15             {16                 try 17                 {18                     Thread.sleep(10);19                 } 20                 catch (InterruptedException e) 21                 {22                     e.printStackTrace();23                 }24                 System.out.println(Thread.currentThread().getName() + "正在出售第"25                         + (tickets--) + "张票");26             }27         }28     }29 }
复制代码

相同的票出现多次:CPU的一次操作必须是原子性的

出现了负数的票:随机性和延迟导致的

23.21  线程安全问题的产生原因分析

线程安全问题产生的原因:

1.多个线程在操作共享的数据

2.操作共享数据的线程代码有多条

当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生

23.22  同步代码块的方式解决线程安全问题

思想:把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,其他线程不能来执行。Java提供了:同步机制。

同步代码块:

synchronized(对象)

{需要同步的代码;}

例:

复制代码
 1 public class SellTicket implements Runnable  2 { 3     // 定义100张票 4     private int tickets = 100; 5     //创建锁对象 6     private Object obj = new Object(); 7     @Override 8     public void run()  9     {10         while (true) 11         {12             //锁对象必须是同一个13             synchronized(obj)14             {15                 if (tickets > 0) 16                 {17                     try 18                     {19                         Thread.sleep(100);20                     } 21                     catch (InterruptedException e) 22                     {23                         e.printStackTrace();24                     }25                     System.out.println(Thread.currentThread().getName() + "正在出售第"26                             + (tickets--) + "张票");27                 }28             }29         }30     }31 }
复制代码

23.23  同步的特点及好处和弊端

同步的前提:多个线程且多个线程使用的是同一个锁对象

同步的好处:同步的出现解决了多线程的安全问题

同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

23.24  同步代码块的锁及同步方法应用和锁的问题

同步方法就是把同步关键字加到方法上

例:

复制代码
 1 //同步方法 2     private synchronized void sellTicket() 3     { 4         if (tickets > 0)  5         { 6             try  7             { 8                 Thread.sleep(100); 9             } 10             catch (InterruptedException e) 11             {12                 e.printStackTrace();13             }14             System.out.println(Thread.currentThread().getName() + "正在出售第"15                     + (tickets--) + "张票");16         }17     }
复制代码

1.同步代码块的锁对象是任意对象

2.同步函数使用的锁是this

3.静态的同步函数使用的锁是该函数所属的字节码文件对象可以用getClass方法获取,也可以用当前类名.class表示

23.25  以前的线程安全的类回顾

Collections中让集合同步功能

例:

// public static <T> List<T> synchronizedList(List<T> list)List<String> list1 = new ArrayList<String>();// 线程不安全List<String> list2 = Collections.synchronizedList(new ArrayList<String>()); // 线程安全


0 0
原创粉丝点击