Java基础-多线程
来源:互联网 发布:淘宝网休闲鞋女 编辑:程序博客网 时间:2024/06/05 00:34
进程:正在运行中的程序。
线程:就是进程中一个执行单元或执行情景或执行路径。负责进程中代码执行的控制单元。
多线程:一个进程中至少要有一个线程,当一个进程中有多个线程时,就是多线程。
多线程的好处:可以让多部分代码同时执行。
什么是同时执行呢? 在单核时代其实是cpu在瞬间做着快速的切换完成的; 现在的多核时代,才算是真正的多线程。
其实java运行就是多线程的。 main函数+gc垃圾回收器
在执行main函数中内容的同时,垃圾回收器也在回收堆内存的垃圾,所以执行main方法的线程,和执行垃圾回收器的线程同时在执行,这就是多线程。
创建多线程的目的:
当有多部分代码需要同时执行时。而且每一个线程都有自己要执行的内容,这个内容称之为:线程任务。 简单说:启动多线程就是为了执行任务,当任务有多个,需要同时执行时,就需要多个线程。
线程的状态
1、被创建。2、运行:这种状态的线程,具备着cpu的执行资格,具备着执行资格。3、冻结:这种状态的线程,释放了cpu执行资格,并释放了cpu执行权。相当于睡着了,但还会醒来。 有两种方法实现: a、sleep(time).唤醒sleep(time时间到)。 b、wait()睡着,唤醒notify()。 wait()方法也可以指定时间,如果不指定时间需要用notify()来唤 醒。(是Object中的方法。)4、临时阻塞状态:这种状态的线程,具备着cpu执行权,不具备执行资格。5、消亡:线程结束了。通过stop()方法来完成。
如何创建线程?
Java要调用底层才能完成进程的建立和线程的创建,所以java对外提供了描述线程的对象,方便程序员对线程的操作。创建线程的方式: A:自定义一个类继承Thread类。 B:自定义一个类实现Runnable接口方式1:继承Thread类。 A:定义一个类继承Thread类。 B:子类要重写Thread类的run()方法。 C:让线程启动并执行。注意:启动线程并执行,是不能使用run()方法的。这个时候,必须使用另外的一个方法。 这个方法名是start()。***这个方法其实做了两件事情,第一,让线程启动。第二,自动调用run()方法。***
代码演示:
自定义类继承Thread类
/* * 为了看到每次确实在变化,我们要是能够知道线程对象的名字有多好呢? * 在Thread类中提供了一个方法: * public final String getName():获取线程对象的名称。默认情况下,名字的组成 Thread-编号(编号从0开始) * public final void setName(String name):设置线程名称。 */public class MyThread extends Thread { @Override public void run() { for (int x = 0; x < 10; x++) { System.out.println(getName()+"---hello" + x); } }}
线程测试类:
public class ThreadDemo { public static void main(String[] args) { MyThread my1 = new MyThread(); MyThread my2 = new MyThread(); my1.setName("用友"); my2.setName("金蝶"); // my.run(); // my.run(); // 同一个线程对象连续两次start,报错:IllegalThreadStateException // 表示该线程的状态有问题。 // my.start(); // my.start(); my1.start(); my2.start(); }}
结果:
分析:
我们可以看到程序开启了两个线程,在输出结果的时候,线程1和线程2在交替着运行。因为他们都在等待着cpu的执行。并且每个线程执行的时间都是不确定,由cpu分配。
方式2: A:创建一个类实现Runnable接口 B:重写run()方法 C:创建类的实例 D:把类的实现作为Thread的构造参数传递,创建Thread对象
自定义类实现Runnable接口
代码演示:
/* * public static Thread currentThread():返回当前正在执行的线程对象引用 */public class MyRunnable implements Runnable { @Override public void run() { for (int x = 0; x < 100; x++) { // getName()方法是Thread类的,而MyRunnable只实现了Runnable接口,本身没有getName(),所以不能使用。 System.out.println(Thread.currentThread().getName() + "---hello" + x); } }}
测试类:
public class MyRunnableDemo { public static void main(String[] args) { MyRunnable my = new MyRunnable(); // my.start(); // 实现了Runnable接口的类没有start()方法,而我们启动线程必须调用start()方法。 // 又由于,start()方法只有Thread类有。所以,我们就考虑这个把该类转换成Thread类。 Thread t1 = new Thread(my); Thread t2 = new Thread(my); t1.setName("乔峰"); t2.setName("慕容复"); t1.start(); t2.start(); }}
结果:
分析:
这里同样实现了多线程的需求。
那么问题来了:既然有了继承Thread类的方式,为什么还要有实现Runnable接口的方式呢?
A:避免的单继承的局限性
因为我们都知道,在Java的类中,只支持单继承,如果一个类继承了Thread类,就不能继承其他类了,这样会让自
义类的功能有很大的局限性。
B:实现接口的方式,只创建了一个资源对象,更好的实现了数据和操作的分离。
一般我们选择第二种方式。
**注意:** 1、多个线程的运行是不规律的。 2、必须得所有线程程序运行结束,进程才结束。 3、调用run方法和调用start方法的区别: 调用run方法,仅仅是一般对象调用对象中的方法,并没有开启线程。 调用start方法,开启一个线程,让这个线程去执行run方法中的内容。
需求:通过四个窗口卖票,一共有100张票。用第二种创建线程方式实现。
卖票程序如果处理不当,会有同步问题。
/* * 目前这个代码是符合真实的卖票程序。 * 但是,有问题,居然出现了负数票的情况。 * 那么,产生的原因是什么呢? * 线程的随机性和延迟性,导致了线程访问共享数据出现了问题。 * 怎么解决呢? */public class TicketRunnable implements Runnable { private int tickets = 100; @Override public void run() { while (true) { //t1,t2,t3,t4过来了 //tickets = 1 if (tickets > 0) { //t1首先抢到了CPU的执行权,接着,进行了判断,发现是满足条件的,就进来了 //t2就抢到了,也进行了判断,发现还是满足,也就进来了 //t3抢到了,也进行了判断,发现还是满足,也就进来了 //t4抢到了,也进行了判断,发现还是满足,也就进来了 // public static void sleep(long millis) try { //t1睡着了 //t2睡着了 //t3睡着了 //t4睡着了 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //t1醒了 -- 窗口1正在出售第1张票 tickets=0 //t2醒了 -- 窗口2正在出售第0张票 tickets=-1 //t3醒了 -- 窗口3正在出售第-1张票 tickets=-2 //t4醒了 -- 窗口4正在出售第-2张票 tickets=-3 System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); //注意:如何卖出相同的票的呢? //关键点:tickets-- //A:读取tickets的操作 100 //B:修改tickets的操作 99 //C:把最后的值赋值给tickets = 99 } } }}
Test类
public class RunnableTest { public static void main(String[] args) { TicketRunnable tr = new TicketRunnable(); Thread t1 = new Thread(tr); Thread t2 = new Thread(tr); Thread t3 = new Thread(tr); Thread t4 = new Thread(tr); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t4.setName("窗口4"); t1.start(); t2.start(); t3.start(); t4.start(); }}
那么,产生的原因是什么呢?
线程的随机性和延迟性,导致了线程访问共享数据出现了问题。
怎么解决呢?
在多线程程序中,一般来说,不会是所有的代码都有问题,所以,我们只需要找到那些可能出问题的代码,
我把可能出问题的代码, 跟包起来,看做是一个整体,只有这个整体完毕,别人才能继续访问。
为了安全,把这个整体包起来的代码加个锁。给锁起来。
怎么找?(多线程出问题的判断条件)
A:看有没有共享数据
B:看对共享数据的操作是不是多条语句
C:看是不是在多线程程序中
找到后,就把同时满足这三个条件的代码给锁起来。
怎么锁?
java提供了一种锁机制方式:
synchronized(锁对象)
{
需要被锁的代码;
}
锁对象:怎么做呢?反正不知道,所以,我们就用Object类的实例。
注意:多个线程必须使用同一把锁。
举例:
排队上厕所。
厕所 -- 资源多个人,就可以看成是多个线程。他们发现厕所哪里的灯是绿色,张三去了,李四去了,王五去了...由于张三最近,张三抢到了,就进去了,灯就变成红色了。张三...张三出来,灯比绿色,但是,张三拉肚子,所以,他一回身又进去了。 ... ...
代码演示:
public class TicketRunnable implements Runnable { private int tickets = 100; private Object obj = new Object(); @Override public void run() { while (true) { //tickets=1 //t1,t2,t3,t4都来了 //假设t1抢到,看到是开的状态 synchronized (obj) { //锁对象的状态:开,关 //t1进来了,给外界了一个关的状态。 if (tickets > 0) { try { //t1睡了 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } } //t1把状态修改为开 } }}
Test类
public class RunnableTest { public static void main(String[] args) { TicketRunnable tr = new TicketRunnable(); Thread t1 = new Thread(tr); Thread t2 = new Thread(tr); Thread t3 = new Thread(tr); Thread t4 = new Thread(tr); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t4.setName("窗口4"); t2.start(); t3.start(); t4.start(); t1.start(); }}
结果:
分析:
由于加上了锁,所以共享的数据在同一时间内,只被一个线程所操作,直到该线程不再持有这个锁,其他线程才有机会得到这个锁,只有得到这个锁的线程,才有访问共享数据的资格。
同步:
*同步原理*:其实就是将需要同步的代码进行封装,并在该代码上加一个锁。*同步好处*:解决了多线程安全问题。*同步弊端*:降低性能。(注意):加了同步,安全问题还在,如何解决?利用同步的两个前提来解决。*同步的前提:* 必须要保证在同步中有多个线程,因为同步中只有一个线程,该同步是没有意义的。 必须要保证多个线程在同步中使用的是同一个锁。**注意:当锁定义在局部中时,相当于每个线程都具备一个锁。这时就不是同一个锁了。**
多线程安全问题:
多线程安全的原因:1、多个线程在操作共享数据。2、操作共享数据的代码有多条。 一个线程在执行多条操作共享数据的过程中,其他线程参与了运算,这时就会发生安全问题。
分析多线程是否安全的依据:
线程中任务中有没有共享数据,该数据是否被多条语句操作。
安全问题解决方案:
只要保证一个线程在执行多条操作共享数据的语句时,其他线程不能参与运算即可。当该线程都执行完后,其他线程才可以执行这些语句。在多线程操作的代码上加上同步(synchronzied)。
Synchornzied同步:
同步的原理:其实就是将需要同步的代码进行封装,并在该代码上加上一个锁。同步的好处:解决多线程安全问题。同步弊端:降低程序的运行效率,同时有可能会出现死锁情况。一种现象:出现了多线程安全问题,为了解决,加上同步,发现问题依旧,怎么办?
同步的前提:
必须要保证在同步中有多个线程,以为同步中只有一个线程的同步是没有意义的。 必须要保证多个线程同步中使用的是同一锁。(将锁对象定义在成员位置)
多线程的程序,如何加同步?
1、 从线程任务代码中分析,也就是run方法中的代码中分析。2、 分析啥呢?是否有共享数据,有没有多条语句在操作共享数据?
同步函数:将同步加在函数上。
如何将同步代码块以同步函数的形式表现?
如果run方法中的所有数据都要被同步,那么直接在run方法中加同步,如果只有部分代码需要同步,那么将需要被同步的代码用一个函数封装,然后在run方法中调用这个同步函数就行了。
同步函数和同步代码块有什么区别?
1、 同步函数使用的锁是this,同步代码块使用的锁是任意指定的对象。
建议开始时,使用同步代码块,因为锁可以是任意的。尤其是需要用到多个不同锁时。
同步函数使用的锁是什么?
同步函数使用的锁是this。
静态同步函数使用的锁是什么?
静态随着类的加载而加载,这时内存中只存储的对象至少一个,就是该类字节码文件对象(类名.class)。所以静态同步函数使用的锁是字节码对象。
关于单例模式中懒汉式的知识点补充:
我们都知道,单例模式中的懒汉式中,有线程同步安全问题。也就是说如果不添加同步锁的情况下,可能保证不了在内存中只有一个对象的特点。。
改进代码:
package cn.itcast.single;//懒汉式的多线程处理:双重判断。public class Single { //创建私有化并且静态修饰的对象引用 private static Single s = null; //私有化无参构造方法,目的是不让外部创建实例化对象。 private Single() { } //在本类中提供一个公共方法,返回本类的一个对象。 public static Single getInstanse() { if (s == null) { synchronized (Single.class) { if (s == null) s = new Single(); } } return s; }}
死锁:
最常见的死锁情况:同步嵌套。同步中还有同步,两个同步用的不是同一个锁。死锁代码演示:同步嵌套。
死锁问题:
5个哲学家的故事。
5个哲学家去吃饭,菜饭都上齐了,筷子也上了,但是,一人只有一只筷子,每个人,先思考一会,把筷子借给别人,
然后,别人吃完了,自己在吃。假如这5个人都饿了,他们就会拿起自己的筷子,但是只有一只,都等待这个别人
放下那一只筷子,然后好拿过来吃饭,而没有任何一个人愿意先放下筷子,所以,就出现了死锁。
代码演示:
DieLock类:
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("true -- objA"); synchronized (MyLock.objB) { System.out.println("true -- objB"); } } } else { synchronized (MyLock.objB) { System.out.println("false -- objB"); synchronized (MyLock.objA) { System.out.println("false -- objA"); } } } }}
锁类:
public class MyLock { public static final Object objA = new Object(); public static final Object objB = new Object();}
测试类:
public class DieLockDemo { public static void main(String[] args) { DieLock d1 = new DieLock(true); DieLock d2 = new DieLock(false); d1.start(); d2.start(); }}
结果:
- Java基础/Java多线程
- Java基础-多线程基础篇
- java多线程基础
- Java多线程编程基础
- java多线程开发基础
- Java多线程基础
- Java -- 多线程技术基础
- 【java】多线程基础
- Java基础:多线程
- Java语言基础:多线程
- Java语言基础:多线程
- Java语言基础:多线程
- java多线程基础分析
- Java多线程编程基础
- java 多线程基础
- Java基础_多线程
- Java多线程基础
- java多线程基础
- 复盘:一个创业项目的失败之路
- 05-图3. 六度空间 (30) floyd bfs
- Elasticsearch集群环境的搭建步骤
- python学习笔记3
- iOS __weak与__block修饰符到底有什么区别
- Java基础-多线程
- 32位和64位系统区别及常用数据类型所占字节数
- Unity3D音效问题
- hdu 5372 Segment Game 2015多校联合训练赛#7 树状数组
- HDU 5379 Mahjong tree
- shell基础(二)
- 网页编码
- 简单谈谈3D打印培训的发展和前景
- poj1664 放苹果 (母函数)