Java线程笔记1
2014-10-24 08:27
113 查看
一、几个概念
1.进程
一个正在执行中的程序
每个进程都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元
2.线程
进程中一个独立的控制单元,线程在控制着进程的执行,线程才是进程中真正执行的部分
一个进程中至少有一个线程,但是可以拥有多条执行路径,即多个线程
3.多线程
多个线程并发执行
4.多线程存在的意义
多线程可以让程序产生同时运行效果,提高程序执行效率。
以虚拟机为例,Java vm 启动的时候会有一个进程java.exe,该进程中至少有一个线程负责Java程序的执行,而且这个线程运行的代码存在main方法中,该线程称之为主线程。jvm启动时不止一个线程,除了主线程之外,还有一个线程专门负责垃圾回收机制。如果只有主线程没有垃圾负责垃圾回收的线程,一旦垃圾过多内存用完,程序将无法执行下去,抑或主线程停下来去处理垃圾,原本正在执行的程序将处于等待状态,影响效率。而多线程可以使多段代码同时执行,可以一边执行程序一边处理垃圾。
二、创建线程的两种方式
第一种:继承Thread类
1.定义类继承Thread
2.覆写Thread类中的run()方法
3.调用线程的start方法
程序示例
public static Thread currentThread() 获取当前线程对象
public String getName() 获取线程名称
设置线程名称:setName方法或者构造函数
线程都有自己默认的名称,Thread-编号 该编号从0开始,如果不设置线程名就会采用这种默认的命名方式
运行截图
每一次的运行结果都不一样
第二种:实现Runnable接口
1.定义类实现Runnable接口
2.覆盖Runnable接口中的run方法
3.通过Thread类建立线程对象
4.将Runnale接口的子类对象作为实际参数传递给Thread类的构造函数
5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
程序示例
运行结果截图
这里涉及到的线程安全的问题将在后面解释
两种方式中都覆写了run方法了,为什么要覆盖run方法?
Thread类用于描述线程,该类定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法,即run方法用于存储线程要运行的代码。复写run方法的目的是将自定义代码存储在run方法,让线程运行。
既然run方法用于存储线程要运行的代码,为什么要不直接调用run方法而是调用start方法?
start方法有两个作用:启动线程,调用run()方法
调用start方法与run方法的区别
start用于开启线程并执行该线程的方法。注意:重复调用start方法程序会抛出异常
调用run与一般的对象调用没有区别,线程创建了,并没有运行,依然只有一个线程
继承方式和实现方式的区别
继承Thread:线程代码存放在Thread子类的run方法中
实现Runnable:线程代码存放在Runnable接口子类run方法中
实现方式方式的好处:避免了单继承的局限行,定义线程时,建议使用这种方式
两个程序每次运行的结果都不一样,这又是为什么呢?
这和CPU的执行原理有关。
因为多个线程都在获取CPU的执行权,CPU执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序在运行(多核除外)。CPU在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象的把多线程的运行行为看成是在互相抢夺CPU的执行权。
这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长时间,由CPU决定。
[align=justify]三、线程的几种状态
1.被创建:通过new关键字创建了Thread类(或其子类)的对象,等待被启动[/align]
[align=justify] 2.运行状态:具备运行资格和执行权[/align]
[align=justify] 3.临时阻塞状态:具备运行资格但是没有执行权[/align]
[align=justify] 4.冻结状态:线程因为调用sleep方法或者wait方法等进入阻塞,放弃CPU的执行权[/align]
[align=justify] 5.消亡状态:run方法结束或者线程调用了stop方法
[/align]
线程状态图
四、多线程安全问题
卖票的程序中,票据出现了0号票,现实生活中是不应该出现0号票,这里就涉及到了线程安全的问题
产生的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程执行完,在执行过程中,其他线程不可以参与执行。
Java中提供的解决方法----同步
1.同步代码块
用法:
synchronized(对象){
//需要被同步的代码
}
同步代码块解决线程安全的原因在于这个对象,好比一把锁,每个线程要执行被同步的代码都必须获取此对象的锁,如果这个对象的锁被其他线程获取就必须等待其他对象释放这个锁。
加入了同步的卖票的例子
可以看到没有0号票了
2.同步函数
用法:
在函数返回值类型前加上synchronized关键字
同步函数用的是哪一个锁?
函数需要被对象调用,那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this
如果同步函数被静态修饰后,使用的锁又是什么呢?
静态函数使用的不是this锁,因为在静态方法中也不可以定义this。
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码对象 类名.class ,该对象的类型是Class
静态的同步方法,使用的锁是该方法所在类的字节码对象 ,即类名.class
示例:
3.同步的前提:
1).必须要有两个或者两个以上的线程
2).必须是多个线程使用同一个锁
4.如何找到多线程中的安全问题
1).明确哪些代码是多线程运行代码
2).明确共享数据
3).明确多线程运行代码中哪些语句是操作共享数据的
5.同步的好处与弊端
好处:解决了多线程的安全问题
弊端:多个线程都需要判断锁,较为消耗资源
五、死锁问题
死锁产生的原因:
同步中嵌套同步
死锁的例子
程序运行结果截图
出现死锁,程序无法继续执行下去
1.进程
一个正在执行中的程序
每个进程都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元
2.线程
进程中一个独立的控制单元,线程在控制着进程的执行,线程才是进程中真正执行的部分
一个进程中至少有一个线程,但是可以拥有多条执行路径,即多个线程
3.多线程
多个线程并发执行
4.多线程存在的意义
多线程可以让程序产生同时运行效果,提高程序执行效率。
以虚拟机为例,Java vm 启动的时候会有一个进程java.exe,该进程中至少有一个线程负责Java程序的执行,而且这个线程运行的代码存在main方法中,该线程称之为主线程。jvm启动时不止一个线程,除了主线程之外,还有一个线程专门负责垃圾回收机制。如果只有主线程没有垃圾负责垃圾回收的线程,一旦垃圾过多内存用完,程序将无法执行下去,抑或主线程停下来去处理垃圾,原本正在执行的程序将处于等待状态,影响效率。而多线程可以使多段代码同时执行,可以一边执行程序一边处理垃圾。
二、创建线程的两种方式
第一种:继承Thread类
1.定义类继承Thread
2.覆写Thread类中的run()方法
3.调用线程的start方法
程序示例
<span style="font-size:14px;"><span style="font-size:12px;">class SubThread extends Thread{ /** * 创建线程类的子类,重写run()方法 */ // String name; public SubThread(String name){ // this.name = name; super(name); } //覆写run方法 public void run(){ for(int i=1; i<=600; i++) //输出线程名以及运行的次数 System.out.println(Thread.currentThread().getName()+"SubThread run----"+i); } } public class ThreadDemo1 { public static void main(String[] args) { SubThread st1 = new SubThread("one---");//创建一个线程 st1.start();//开启线程 SubThread st2 = new SubThread("two+++");//创建一个线程 st2.start();//开启线程 //主线程代码部分 for(int i=1; i<=600; i++) System.out.println("hello world!--"+i); } }</span></span>
public static Thread currentThread() 获取当前线程对象
public String getName() 获取线程名称
设置线程名称:setName方法或者构造函数
线程都有自己默认的名称,Thread-编号 该编号从0开始,如果不设置线程名就会采用这种默认的命名方式
运行截图
每一次的运行结果都不一样
第二种:实现Runnable接口
1.定义类实现Runnable接口
2.覆盖Runnable接口中的run方法
3.通过Thread类建立线程对象
4.将Runnale接口的子类对象作为实际参数传递给Thread类的构造函数
5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
程序示例
<span style="font-size:14px;"><span style="font-size:12px;">/* * 卖票的例子 */ class Ticket implements Runnable{ private static int ticket = 100; //总票数 @Override public void run(){ while(true){ if(ticket>0){ try { Thread.sleep(10);//线程休眠10毫秒 } catch (InterruptedException e) { e.printStackTrace(); } //打印当前线程名称以及售出的票 System.out.println(Thread.currentThread().getName()+"---sale--- "+ticket--); } } } } public class TicketDemo { public static void main(String[] args) { //创建Runable子类的对象 Ticket ticket = new Ticket(); //创建Thread类,将Runnable接口的子类对象传给它 Thread t1 = new Thread(ticket); Thread t2 = new Thread(ticket); //开启线程 t1.start(); t2.start(); } }</span></span>
运行结果截图
这里涉及到的线程安全的问题将在后面解释
两种方式中都覆写了run方法了,为什么要覆盖run方法?
Thread类用于描述线程,该类定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法,即run方法用于存储线程要运行的代码。复写run方法的目的是将自定义代码存储在run方法,让线程运行。
既然run方法用于存储线程要运行的代码,为什么要不直接调用run方法而是调用start方法?
start方法有两个作用:启动线程,调用run()方法
调用start方法与run方法的区别
start用于开启线程并执行该线程的方法。注意:重复调用start方法程序会抛出异常
调用run与一般的对象调用没有区别,线程创建了,并没有运行,依然只有一个线程
继承方式和实现方式的区别
继承Thread:线程代码存放在Thread子类的run方法中
实现Runnable:线程代码存放在Runnable接口子类run方法中
实现方式方式的好处:避免了单继承的局限行,定义线程时,建议使用这种方式
两个程序每次运行的结果都不一样,这又是为什么呢?
这和CPU的执行原理有关。
因为多个线程都在获取CPU的执行权,CPU执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序在运行(多核除外)。CPU在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象的把多线程的运行行为看成是在互相抢夺CPU的执行权。
这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长时间,由CPU决定。
[align=justify]三、线程的几种状态
1.被创建:通过new关键字创建了Thread类(或其子类)的对象,等待被启动[/align]
[align=justify] 2.运行状态:具备运行资格和执行权[/align]
[align=justify] 3.临时阻塞状态:具备运行资格但是没有执行权[/align]
[align=justify] 4.冻结状态:线程因为调用sleep方法或者wait方法等进入阻塞,放弃CPU的执行权[/align]
[align=justify] 5.消亡状态:run方法结束或者线程调用了stop方法
[/align]
线程状态图
四、多线程安全问题
卖票的程序中,票据出现了0号票,现实生活中是不应该出现0号票,这里就涉及到了线程安全的问题
产生的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程执行完,在执行过程中,其他线程不可以参与执行。
Java中提供的解决方法----同步
1.同步代码块
用法:
synchronized(对象){
//需要被同步的代码
}
同步代码块解决线程安全的原因在于这个对象,好比一把锁,每个线程要执行被同步的代码都必须获取此对象的锁,如果这个对象的锁被其他线程获取就必须等待其他对象释放这个锁。
加入了同步的卖票的例子
<span style="font-size:14px;"><span style="font-size:12px;">/* * 卖票的例子,已加入同步 */ class Ticket2 implements Runnable{ private static int ticket = 100; //总票数 Object obj = new Object(); //用于同步的对象 @Override public void run(){ while(true){ synchronized(obj){ //加入同步 if(ticket>0){ try { Thread.sleep(20);//线程休眠10毫秒 } catch (InterruptedException e) { e.printStackTrace(); } //打印当前线程名称以及售出的票 System.out.println(Thread.currentThread().getName()+"---sale--- "+ticket--); } } } } }</span></span>程序运行截图
可以看到没有0号票了
2.同步函数
用法:
在函数返回值类型前加上synchronized关键字
同步函数用的是哪一个锁?
函数需要被对象调用,那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this
如果同步函数被静态修饰后,使用的锁又是什么呢?
静态函数使用的不是this锁,因为在静态方法中也不可以定义this。
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码对象 类名.class ,该对象的类型是Class
静态的同步方法,使用的锁是该方法所在类的字节码对象 ,即类名.class
示例:
<span style="font-size:14px;"><span style="font-size:12px;">/* * 懒汉式单例设计模式的安全问题 */ public class Single { private static Single s = null; private Single(){} public static Single getInstance(){ if(s == null) synchronized(Single.class){ //加入同步,静态函数中使用的锁只能是类对应的字节码对象 if(s == null) s = new Single(); } return s; } }</span></span>
3.同步的前提:
1).必须要有两个或者两个以上的线程
2).必须是多个线程使用同一个锁
4.如何找到多线程中的安全问题
1).明确哪些代码是多线程运行代码
2).明确共享数据
3).明确多线程运行代码中哪些语句是操作共享数据的
5.同步的好处与弊端
好处:解决了多线程的安全问题
弊端:多个线程都需要判断锁,较为消耗资源
五、死锁问题
死锁产生的原因:
同步中嵌套同步
死锁的例子
<span style="font-size:14px;"><span style="font-size:12px;"> /* * 死锁的例子 */ class MyLock { static Object lockA = new Object(); static Object lockB = new Object(); } class Test implements Runnable{ private boolean flag; public Test(boolean flag){ this.flag = flag; } @Override public void run() { if(flag){ synchronized(MyLock.lockA){ //上lockA锁 System.out.println("if lockA"); synchronized(MyLock.lockB){ //上lockB锁 System.out.println("if lockB"); } } }else{ synchronized(MyLock.lockB){ //上lockB锁 System.out.println("else lockB"); synchronized(MyLock.lockA){ //上lockA锁 System.out.println("else lockA"); } } } } } public class DeadLockDemo { public static void main(String[] args) { Thread t1 = new Thread(new Test(true)); Thread t2 = new Thread(new Test(false)); t1.start(); t2.start(); } } </span></span>
程序运行结果截图
出现死锁,程序无法继续执行下去
相关文章推荐
- 最近仔细研究了一下Java的NIO以及线程并发,搞清了点思路,特作笔记如下(NIO篇)
- Java线程笔记一:
- java线程笔记
- Java多线程编程总结笔记——六线程的同步与锁
- Java多线程编程总结笔记——五线程状态的转换
- Java入门笔记6_线程
- Java线程学习笔记之并发集合类
- java线程笔记
- java学习笔记-线程的初步研究
- 【Java学习笔记】线程学习笔记
- Java入门笔记 6 线程
- Java多核线程笔记-volatile的原理与技巧
- 开发笔记:创建Java线程的两种方法
- JAVA学习笔记之四线程层次结构
- java编程笔记21 线程
- 最近仔细研究了一下Java的NIO以及线程并发,搞清了点思路,特作笔记如下(NIO篇)
- Java 学习笔记 (5) - 线程 Thread
- Java多核线程笔记-volatile的原理与技巧
- Java多线程设计模式详解学习笔记——Introduction1 Java语言的线程
- 《Java Concurrency in Practice》 学习笔记--第二章:线程安全