多线程
前言:传说,很多人的Java入门到放弃都是因为多线程。大多数初学者表示很难理解多线程的原理,老师也说每次多线程都会准备讲两遍或三遍,当时听到这些,我好方!!感觉随时做好了shi 的准备。However,现在学完了多线程,觉得没有那么复杂,但确实会有的懵,要反复去领悟,多敲几次代码,书读百遍,其义自见嘛!那么,接下来就来看看我的多线程总结吧!!
一、什么是线程,线程和进程的联系、区别?
- 进程:进程是指一个内存中运行中的应用程序。每个进程都有自己独立的一块内存空间,一个应用程序可以同时启动多个进程。比如在Windows系统中,一个运行的abc.exe就是一个进程。
- 线程:线程是指进程中的一个执行任务(控制单元),一个进程可以同时并发运行多个线程,如:多线程下载软件,多任务系统,该系统可以运行多个进程,一个进程也可以执行多个任务,一个进程可以包含多个线程。
- 进程和线程的关系:1)一个进程至少有一个线程,为了提高效率,可以在一个进程中开启多个执行任务,即多线程。 2)多进程:操作系统中同时运行的多个程序。 3)多线程:在同一个进程中同时运行的多个任务。
- 进程与线程的区别:1)进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。 2)线程:堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响的,又称为轻型进程或进程元。
二、一个Java程序中,线程有三个部分组成:虚拟的cpu、代码和数据。至少有2个线程,分别是主线程和垃圾回收线程(后台线程)。
三、线程的各种状态:
1、创建状态:在程序中用构造方法创建了一个线程对象后,即是处于新建状态了,但还不可运行。 “Thread thread = new Thread()”。
2、就绪状态:新建线程对象后,调用该线程的start()方法就可以启动线程,但不是马上就执行,只是进入了线程池。
3、运行状态:当就绪状态的线程被调用并获得处理器资源时(通俗来讲,就是抢到CPU的执行权),线程就进入了运行状态。此时,自动调用该线程对象的run()方法。run()定义了该线程的操作和功能。
4、堵塞状态:由于一些原因失去CPU的执行权;在可执行的状态下,如果调用sleep()、suspend()、wait()等方法,线程也会进入堵塞状态。解除堵塞就进入就绪状态。
5、死亡状态:线程调用stop()方法时或run()方法执行结束后,就处于死亡状态,处于死亡状态的线程不具有继续运行的能力。
四、线程的创建:
方式一:继承Thread类
1、子类覆盖父类中的run方法,将线程运行的代码存放在run中。
2、建立子类对象的同时线程也被创建。
3、通过调用start方法开启线程
Demo:
public class ThreadDemo {
public static void main(String[] args) {
Test t1 = new Test("haoren");
t1.start();
Test t2 = new Test("huairen");
t2.start();
}
}
class Test extends Thread{
private String name;
public Test(String name){
this.name = name;
}
public void run(){
for(int i=0;i<10;i++){
System.out.println(name+":"+i);
}
}
}
!注意:线程不能重复开启;千万不要调用run方法,如果调用run方法好比是对象调用方法,依然还是只有一个线程,并没有开启新的线程.
方式二:实现Runnable接口
1、子类覆盖接口中的run方法。
2、通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。
3、Thread类对象调用start方法开启线程。
Demo:
public class RunnableDemo {
public static void main(String[] args) {
Test t1 = new Test("haoren");
Test t2 = new Test("hairen");
Thread th1 = new Thread(t1);
Thread th2 = new Thread(t2);
th1.start();
th2.start();
}
}
class Test implements Runnable{
private String name;
public Test(String name){
this.name = name;
}
public void run(){
for(int i=0;i<10;i++){
System.out.println(name+":"+i);
}
}
}
五、Thread和Runnable的关系:
1、Thread是Runnable 的子类
2、实际上Thread和Runable的关系和我们前边讲的代理设计模式很像,这里的Thread就是代理类。我们自己所实现的类才是real。 3、Runnable可以共享对象,实际Runnable每个线程内跑的是同一个对象
六、多线程的优势:
1、进程之前不能共享内存,而线程之间共享内存(堆内存)则很简单。
2、系统创建进程时需要为该进程重新分配系统资源,创建线程则代价小很多,因此实现多任务并发时,多线程效率更高.
3、Java语言本身内置多线程功能的支持,而不是单纯第作为底层系统的调度方式,从而简化了多线程编程.
4、多线程是为了同步完成多项任务,不是为了提供程序运行效率,而是通过提高资源使用效率来提高系统的效率。
七、补充知识:
宽带带宽是以位(bit)计算,而下载速度是以字节(Byte)计算,1字节(Byte)等于8位(bit),
所以1024kb/s是代表上网带宽为1024千位(1M),而下载速度需要1024千位/秒(1024kb/s)带宽除以8,得出128千字节/秒(128KB/s)。
八、线程同步[synchronized](重难点):
1、同步的特点:
前提:同步需要两个或者两个以上的线程;多个线程使用的是同一个锁。未满足这两个条件,不能称其为同步。
弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
2、线程的安全问题:
(1)原因:多个线程访问出现延迟;线程随机性 。(一般出现在在多个线程有数据共享,并且run中的语句不止只有一句)
(2)解决方案:
a、同步代码块
语法:
synchronized(对象)
{
需要同步操作的代码
}
Demo:(同步的经典案例--卖票)
public class TicketDemo {
public static void main(String[] args) {
Person2 p1 = new Person2("haoren");
Thread t1 = new Thread(p1);
Thread t2 = new Thread(p1);
t1.start();
t2.start();
}
}
class Person2 implements Runnable {
private String name;
private int tickets = 5;
public Person2(String name) {
this.name = name;
}
Object object1 = new Object();
public void run() {
for (int i = 0; i < 5; i++) {
synchronized (object1) {//object1就是传说中的锁,要同步必须使用同一个锁
if (tickets > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tickets--);
}
}
}
}
}
同步锁:
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制.
同步监听对象/同步锁/同步监听器/互斥锁
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
Java程序运行使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为同步监听对象.
!注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着.
b、同步方法
语法:在函数上加上synchronized修饰符即可
同步锁是谁:
对于非static方法,同步锁就是this.
对于static方法,我们使用当前方法所在类的字节码对象(Apple2.class).
同步函数的锁是this,而同步代码块的锁可以是任意对象
Demo:
public class Tickets1 {
public static void main(String[] args) {
/*
* GetTickets gt1 = new GetTickets(); GetTickets gt2 = new GetTickets();
* GetTickets gt3 = new GetTickets(); gt1.setName("窗口一");
* gt2.setName("窗口二"); gt3.setName("窗口三"); gt1.start(); gt2.start();
* gt3.start();
*/
GetTickets2 gt = new GetTickets2();
Thread th1 = new Thread(gt, "窗口一");
Thread th2 = new Thread(gt, "窗口二");
Thread th3 = new Thread(gt, "窗口三");
th1.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
gt.flag = true;
th2.start();
th3.start();
}
}
class GetTickets2 implements Runnable {
private int tickets = 10;
boolean flag = false;
Object ob = new Object();
public void run() {
if (flag) {
for (int i = 0; i < 10; i++) {
//synchronized (ob) {//如果用ob就无法同步
synchronized (this) {
if (tickets > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "卖出" + (tickets--) + "号票"+":同步代码块");
}
}
}
} else {
for (int i = 0; i < 10; i++) {
function();
}
}
}
public synchronized void function() {
if (tickets > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出"
+ (tickets--) + "号票"+":同步函数");
}
}
}
/*
* class GetTickets extends Thread{ //private static int tickets = 10; private
* int tickets = 10; public void run(){
*
* for (int i = 0; i < 10; i++) { if(tickets>0){
* System.out.println(Thread.currentThread().getName()+"卖出"+(tickets--)+"号票"); }
* } } }
*/
c、锁机制
!注意:不要使用synchronized修饰run方法,修饰之后,某一个线程就执行完了所有的功能. 好比是多个线程出现串行.
解决方案:把需要同步操作的代码定义在一个新的方法中,并且该方法使用synchronized修饰,再在run方法中调用该新的方法即可.