java 多线程详解(多线程的创建、同步思想、死锁等)
2017-09-24 09:51
645 查看
线程和进程的概念
什么是进程?进程就是正在运行的应用程序,是系统进行资源分配和调用的一个独立单位。每一个进程都有它自己的内存空间和系统资源。
一般来说,进程包括以下三个特征:
1、独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。
2、动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中就扔了时间的概念。进程拥有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。
3、并发性:多个进程可以在单个处理器上并发执行,多个进程间不会互相影响
多进程有什么意义?
单进程的计算机只能做一件事情。现在的计算机都支持多进程的。
多进程可以在一个时间段内执行多个任务,提高CPU的使用率。
一边玩游戏,一边听音乐是同时进行的吗?
不是。因为单CPU在某一个时间点上只能做一件事情。而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换,让我们以为它们是同时进行的。
什么是线程?
一个进程内可执行多个任务,这每一个任务就可以看成一个线程。一个进程有多个线程。线程是程序的执行单元,是一条执行路径。是程序使用CPU的最基本单位。
单线程程序:一个进程只有一条执行路径。
多线程程序:一个进程有多条执行路径。
多线程有什么意义?
不是提高程序的执行速度。是提高应用程序的使用率。
程序的执行其实都是在抢CPU的资源,CPU的执行权。多个线程都在抢这个资源,而如果其中的某一个进程的执行路径比较多,就会有更高的几率抢到CPU的执行权。
线程的执行具有随机性,因为我们不确定哪一个线程在哪一个时刻抢到这个资源。
并发和并行?
并行:逻辑上同时发生,指在某一个时间内同时运行多个程序。
并发:物理上同时发生,指在某一个时间点同时运行多个程序。
java程序运行原理是什么?
java命令会启动 java虚拟机(java virtual machine即JVM),JVM启动就相当于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个”主线程”,然后主线程去调用某个类的main方法。所以main方法运行在主线程中,在此之前的所有程序都是单线程的。
JVM虚拟机的启动时单线程还是多线程的?
多线程的。因为垃圾回收线程也要先启动,否则很容易出现内存溢出。垃圾回收线程和主线程,最低启动了两个线程,所以是多线程的。
多线程的优势:
当操作系统创建一个进程时,必须为该进程分配独立的内存空间,并分配大量的相关资源。但创建一个线程则简单的多。
进程之间不能共享数据,单线程之间共享内存非常容易。
系统创建进程时需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高。
java语言内置了多线程功能的支持,而不是单纯地作为底层操作系统的调度方式,从而简化了java的多线程变成
多线程创建的四种方式
http://blog.csdn.net/qq_36748278/article/details/78143998创建线程的几种方式的比较
为什么要重写run()方法?因为不是类中所有的代码都要被线程执行,为了区分哪些代码能够被线程执行,Thread类中的run()方法只是包含那些被线程执行的代码。一般来说,被线程执行的代码是比较耗时的。
run()和start()的区别是什么?
run():仅仅是封装被线程执行的代码,直接调用仅仅是普通方法。
start():首先启动线程,然后由JVM去调用该线程的run()方法。
采用实现Runnable接口的方式有什么好处?
通过实现接口的方式解决了java单继承带来的局限性
适合多个相同的程序代码去处理同一个资源的情况,把线程同程序的代码以及数据进行有效分离,较好的体现了面向对象的设计思想。
public class MyThread extends Thread { private String myParam = null; @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(this.getName() + ":" + i); } } } MyThread myThread = new MyThread("这是第一个线程"); MyThread myThread2 = new MyThread("这是第二个线程"); //有几个就new几次 MyRunnable mr = new MyRunnable(); //只new了一次 Thread t1 = new Thread(mr,"小骨"); Thread t2 = new Thread(mr,"玥公子");
如果MyThread类中有别的成员变量,他就会创建很多次myParam变量。而通过实现接口的方式,他只new了一次,然后被重复利用,适合多个相同的程序代码去处理同一个资源的情况。
异常
IllegalThreadStateException:非法的线程状态异常。同一个线程不能被调用两次。需要创建两个线程。
方法
public final String getName():获取线程的名称
public final void setName(String name):设置线程的名称
public static Thread currentThread():返回当前正在执行的线程对象
线程调度
线程的两种调度模型?分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片多一些。
java采用的是抢占式调度模型。
线程的优先级?
线程优先级高仅仅表示线程获取的CPU时间片的几率高,但是要在次数比较多或者多次运行的情况下效果才明显。
public final int getPriority():获取线程对象的优先级。线程默认的优先级都是5
public final void setPriority(int newPriority):更改线程的优先级
public static final int MAX_PRIORITY:线程可以具有的最高优先级。值为10
public static final int NORM_PRIORITY:分配给线程的默认优先级。值为5
public static final int MIN_PRIORITY:线程可以具有的最低优先级。值为1
IllegalArgumentException异常:向方法传递了一个不合法或不正确的参数
线程类:
public class ThreadPriority extends Thread { @Override public void run() { for(int i = 0;i < 100;i++){ System.out.println(getName() + ":" + i); } } }
测试类:
public class ThreadPriorityDemo { public static void main(String[] args) { ThreadPriority tp = new ThreadPriority(); ThreadPriority tp1 = new ThreadPriority(); ThreadPriority tp2 = new ThreadPriority(); tp.setName("老大"); tp1.setName("老二"); tp2.setName("老三"); //获取默认优先级 System.out.println(tp.getPriority()); //5 System.out.println(tp1.getPriority()); //5 System.out.println(tp2.getPriority()); //5 System.out.println(Thread.MAX_PRIORITY); //最高优先级:10 System.out.println(Thread.MIN_PRIORITY); //最低优先级:1 System.out.println(Thread.NORM_PRIORITY); //默认优先级:5 tp.start(); tp1.start(); tp2.start(); } }
线程控制
线程休眠: public static void sleep(long miliis)线程加入: public final void join()。使用join()方法的线程中止之后才能执行后续的线程。
ThreadJoin类:
public class ThreadJoin extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + ":" + i); } } }
测试类:
public class ThreadJoinDemo { public static void main(String[] args) { ThreadJoin ts = new ThreadJoin(); ThreadJoin ts1 = new ThreadJoin(); ThreadJoin ts2 = new ThreadJoin(); ts.setName("爸爸"); // 有了爸爸,之后才可能有老大和老二 ts1.setName("老大"); ts2.setName("老二"); ts.start(); try { ts.join(); //老大和老二,必须等爸爸执行完了才能执行 } catch (InterruptedException e) { e.printStackTrace(); } ts1.start(); ts2.start(); } }
线程礼让: public static void yield()。
暂停当前线程对象,并执行其他线程。让多个线程执行更和谐,但是不能靠它保证一人一次。
后台线程: public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。守护线程是守护被守护的线程的,被守护的线程关闭之后,守护线程也要关闭。当正在运行的线程都是守护线程时,java虚拟机退出。该方法必须在启动前调用。
ThreadDaemon类:
public class ThreadDaemon extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + ":" + i); } } }
测试类:
public class ThreadDaemonDemo { public static void main(String[] args) { ThreadDaemon ts = new ThreadDaemon(); ThreadDaemon ts1 = new ThreadDaemon(); ts.setName("左膀,守护将军"); ts1.setName("右臂,守护将军"); // 把ts和ts1都设置为守护线程,当主线程不存在的时候,他们两个守护线程也不能存在了 ts.setDaemon(true); ts1.setDaemon(true); ts.start(); ts1.start(); Thread.currentThread().setName("将军"); // 将军不在了,左膀右臂也不能存在了 for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + ":" + i);; } } }
中断线程
public final void stop():让线程停止,已经过时了。具有不安全性,不建议使用
public void interrupt():中断线程,把线程状态终止,并抛出一个InterruptedException。也就是说走了异常,后面的代码还是会执行。
ThreadInterrupt类:
public class ThreadInterrupt extends Thread { @Override public void run() { System.out.println("开始执行:"+new Date()); //休息10秒钟 try { Thread.sleep(10000); } catch (InterruptedException e) { System.out.println("线程被终止了"); } System.out.println("结束执行:"+new Date()); } }
测试类:
public class ThreadInterruptDemo { public static void main(String[] args) { ThreadInterrupt ts = new ThreadInterrupt(); ts.start(); //超过3秒钟不醒过来,就结束线程 try { Thread.sleep(3000); ts.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } }
控制台输出:
开始执行:Sun Sep 24 15:29:51 CST 2017 线程被终止了 结束执行:Sun Sep 24 15:29:54 CST 2017
线程的生命周期
线程的一些状态:1、新建:创建线程对象
2、就绪:线程有执行资格,没有执行权
3、运行:有执行资格,有执行权
4、阻塞:由于一些操作让线程改变了状态,没有执行资格,没有执行权
另一些操作可以把它给激活,激活处于就绪状态
5、死亡:线程对象变成垃圾,等待被回收
用多线程实现卖电影票实例
通过使用Runnable接口的方式:SellTicket类:
public class SellTicket implements Runnable { private int tickets = 100; //通过实现接口的方式,这个类只会被创建一次,因此这个变量也是被共享的。 @Override public void run() { while (true) { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } } } }
测试类:
public class SellTicketDemo { public static void main(String[] args) { //创建资源对象 SellTicket st = new SellTicket(); //创建3个线程对象 Thread t1 = new Thread(st,"窗口1"); Thread t2 = new Thread(st,"窗口2"); Thread t3 = new Thread(st,"窗口3"); t1.start(); t2.start(); t3.start(); } }
在真实生活中,售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟。因此我们采用每次卖票延迟100毫秒来模拟真实的情况:
修改SellTicket类:
public class SellTicket implements Runnable { private int tickets = 100; @Override public void run() { while (true) { if (tickets > 0) { //为了模拟更真实的场景,在这里sleep:100ms try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } } } }
控制台输出错误事例情况之一:只贴出了错误信息:
窗口2正在出售第1张票 窗口3正在出售第0张票 窗口1正在出售第-1张票
通过加入延迟后,就发生了两个问题:
1、相同的票卖了多次:CPU的每一次执行必须是一个原子性(最简单基本的)操作。
(1)假设此时tickets=100
(2)因为当A线程进入了if(tickets>0)之后,它会睡眠
(3)此时B线程进来了,它进入了if(tickets>0),之后它也睡眠
(4)然后A线程醒了,它卖了第100张票。
(5)CPU的每一次执行必须是一个原子性(最简单基本的)操作。但是tickets– 并不是原子性操作。
a:它先记录以前的值
b:然后tickets–
c:然后输出以前的值
d:tickets的值变成99。
(6)如果上面的操作一次性做完,是正常的,但是如果上面的操作做了一半,然后B线程醒了,此时tickets还没变成99,那么也就是说B线程也卖了第100张票。因此就出现了相同的票卖了多次的现象。
2、出现了负票
(1)假设现在tickets = 1
(2)A线程进来了if(tickets > 0)并休息,B线程也进来了也休息,C线程也进来了也休息。
(3)A 正在出售第1张票,tickets = 0;
B 正在出售第0张票,tickets = -1;
C 正在出售第-1张票,tickets = -2;
这就是我们所说的线程安全问题。下面我将会讲解如何解决这些线程安全问题。
线程安全问题
在什么情况下会出现线程安全问题?当同时满足以下三个情况的时候,我们的程序就会出现问题。(1)是否是多线程环境?是
(2)是否有共享数据?是
(3)是否有多条语句操作共享数据?是
如何解决多线程安全问题?
基本思想:让程序没有安全问题的环境。
也就是说把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
多线程安全是什么?
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。
同步解决线程安全问题:
方式一、同步代码块:同步代码块的锁对象是任意对象。多个线程的锁必须是一把锁。这个对象也必须使用公用的。
synchronized(任意对象){
需要同步的代码
}
卖电影票的解决方法:
public class SellTicket implements Runnable { private int tickets = 100; //创建锁对象。(保证所有的线程都是用的同一把锁) private Object obj = new Object(); @Override public void run() { while (true) { //同步代码块 synchronized (obj) { if (tickets > 0) { //为了模拟更真实的场景,在这里sleep:100ms try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } } } } }
方式二、同步方法:把同步关键字加在方法上。锁对象是this
卖电影票的解决方法:
public class SellTicket implements Runnable { private int tickets = 100; @Override public void run() { while (true) { if(tickets%2 == 0){ //可以知道,同步方法的锁对象是this synchronized (this) { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } } }else{ sellTicket(); } } } private synchronized void sellTicket(){ if (tickets > 0) { //为了模拟更真实的场景,在这里sleep:100ms try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } } }
方式三、静态方法:把同步关键字加在方法上。锁对象是类的字节码文件
卖电影票的解决方法:
public class SellTicket implements Runnable { private static int tickets = 100; @Override public void run() { while (true) { if(tickets%2 == 0){ //可以知道,静态方法的锁对象是类的字节码文件对象 synchronized (SellTicket.class) { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } } }else{ sellTicket(); } } } private static synchronized void sellTicket(){ if (tickets > 0) { //为了模拟更真实的场景,在这里sleep:100ms try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } } }
JDK5之后的Lock锁。(一般情况下使用synchronized)
虽然我们可以通过同步代码块和同步方法添加锁对象,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock但是我们可以发现:Lock锁对象其实是一个接口。
java.util.concurrent.locks :接口 Lock
所有已知实现类: ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock
下面还是用卖票的例子来看看Lock对象是怎么使用的:
SellTicket类:
public class SellTicket implements Runnable { private int tickets = 100; // 定义锁对象 private Lock lock = new ReentrantLock(); @Override public void run() { while (true) { try { // 加锁 lock.lock(); if (tickets > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets-- + "张票"); } } finally { // 释放锁(放在try-finally中:防止前面出问题之后,就没办法释放锁。通过放在finally之后,无论发生什么情况都会释放锁) lock.unlock(); } } } }
测试类:
public class SellTicketDemo { public static void main(String[] args) { SellTicket st = new SellTicket(); Thread t1 = new Thread(st, "窗口1"); Thread t2 = new Thread(st, "窗口2"); Thread t3 = new Thread(st, "窗口3"); t1.start(); t2.start(); t3.start(); } }
同步思想
同步的前提:有多个线程,并且多个线程使用的是同一个锁对象。同步的好处:同步的出现解决了多线程的安全问题
同步的弊端:
(1)当线程非常多的时候,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
(2)容易产生死锁。
死锁问题
死锁:两个或两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。Lock类:创建两个锁对象
public class MyLock { //创建两把锁对象 public static final Object objA = new Object(); public static final Object objB = new Object(); }
死锁代码:MyLock类
public class DieLock extends Thread { private boolean flag; public DieLock(boolean flag) { this.flag = flag; } @Override public void run() { if (flag) { synchronized (MyLock.objA) { System.out.println("if objA"); synchronized (MyLock.objB) { System.out.println("if objB"); } } }else{ synchronized (MyLock.objB) { System.out.println("else objB"); synchronized (MyLock.objA) { System.out.println("else objA"); } } } } }
测试类:
public class DieLockDemo { public static void main(String[] args) { DieLock dl1 = new DieLock(true); DieLock dl2 = new DieLock(false); dl1.start(); dl2.start(); } }
控制台输出:我们可以发现出现了互相等待的情况,也就是死锁现象
else objB if objA
常用的线程安全的类
线程安全的类(都有synchronized修饰,线程安全,效率低)StringBuffer
Vector
Hashtable
如何把一个线程不安全的集合类变成一个线程安全的集合类:
public static List synchronizedList(List list)
List list1 = Collections.synchronizedList(new ArrayList());
相关文章推荐
- Java笔记3 多线程<1>线程概述、多线程的创建、多线程的安全问题、静态同步函数的锁、死锁
- Java多线程之同步与死锁
- 黑马程序员--读写字节数组,随机读写流,集合IO的思维导图,多线程部分,单例设计模式,线程和进程的概念,Java中的线程的创建方式,线程的随机性,线程的状态图,多线程操作共享数据的安全性,死锁
- bo2-About Java 多线程 -------->菜鸟课堂:详解Java多线程开发中的数据同步(unLook!)
- [Java]多线程之同步及死锁
- java中用Runnable和Thread实现多线程,多线程间的同步和死锁。
- 黑马程序员——java第十一、十二天:多线程(创建线程1-2、多线程同步代码、实现Runnable接口、安全死锁)
- 黑马程序员-Java 多线程(二)-线程的同步、死锁、Lock接口
- 从头认识java-17.4 详解同步(5)- 死锁
- 详解Java多线程开发中的数据同步
- java多线程的同步和死锁
- 多线程详解,同步,死锁,等待-通知
- Java多线程之同步与死锁
- 黑马程序员——Java基础——多线程的同步、死锁和等待唤醒机制
- Java基础之多线程(一)--概述、同步、死锁、单例模式
- 菜鸟课堂:详解Java多线程开发中的数据同步
- 黑马程序员_java多线程的同步和死锁
- Java多线程--同步与死锁:synchronized;等待与唤醒:wait、notify、notifyAll;生命周期
- java多线程详解(3)-线程的互斥与同步
- Java多线程---------同步与死锁:synchronized;等待与唤醒:wait、notify、notifyAll;生命周期