黑马程序员——Java语言基础——04.多线程(1)多线程概念
来源:互联网 发布:ubuntu vi删除行 编辑:程序博客网 时间:2024/05/21 03:24
------- android培训、java培训、期待与您交流! ----------
只是对象线程对象开辟了内存空间和初始化数据。
就绪:新建的对象调用start方法,就开启了线程,线程就到了就绪状态。
在这个状态的线程对象,具有执行资格,没有执行权。
运行:当线程对象获取到了CPU的资源。
在这个状态的线程对象,既有执行资格,也有执行权。
冻结:运行过程中的线程由于某些原因(比如wait,sleep),释放了执行资格和执行权。
当然,他们可以回到运行状态。只不过,不是直接回到。
而是先回到就绪状态。
死亡:当线程对象调用的run方法结束,或者直接调用stop方法,就让线程对象死亡,在内存中变成了垃圾。
(1)这两个方法来自不同的类,sleep()来自Thread类,和wait()来自Object类。
(2)sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,
要让b线程睡觉要在b的代码中调用sleep。而wait()是Object类的非静态方法
(3)sleep()释放资源不释放锁,而wait()释放资源释放锁;
(4)使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
1-1 多线程的概念
1-1-1 进程、线程、多进程的概念
线程:进程中一个负责程序执行的控制单元(执行路径)。
P.S.
1、一个进程中可以有多个执行路径,称之为多线程。
2、一个进程中至少要有一个线程。
3、开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
多线程的弊端:线程太多,会导致效率的降低。
①执行main函数的线程,该线程的任务代码都定义在main函数中。
②负责垃圾回收的线程。
1-1-2 创建线程方式一:继承Thread类
2、覆盖Thread类中的run方法。
3、直接创建Thread的子类对象创建线程。
4、调用start方法开启线程并调用线程的任务run方法执行。
jvm创建的主线程的任务都定义在了主函数中。
而自定义的线程,它的任务在哪儿呢?
Thread类用于描述线程,线程是需要任务的。所以Thread类也有对任务的描述。这个任务就是通过Thread类中的run方法来体现。也就是说,run方法就是封装自定义线程运行任务的函数,run方法中定义的就是线程要运行的任务代码。
开启线程是为了运行指定代码,所以只有继承Thread类,并复写run方法,将运行的代码定义在run方法中即可。
<span style="font-family:Microsoft YaHei;font-size:14px;">class ThreadTest{//多线程演示,开启多线程的子类必须继承Thread类,开启线程必须使用start();方法//使用run时仅仅是普通的调用,不会开启多线程public static void main(String[] args){Demo d1 = new Demo("你好");//d1.setName("sha111----");d1.start();//d1.run();Demo d2 = new Demo("我好");d2.start();//d2.setName("sha2_____----");//d2.run();for (int i=0; i<50; i++){System.out.println(i+"nihaoassssss------");}}}class Demo extends Thread{private String name;Demo(String name){super(name);//给线程命名}public void run(){for (int i=0; i<50; i++){System.out.println(i+"大家好"+Thread.currentThread().getName());}}}</span>
1-1-3 创建线程方式二:实现Runnable接口
2、覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3、通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
为什么?因为线程的任务都封装在Runnable接口子类对象的run方法中。
所以要在线程对象创建时就必须明确要运行的任务。
4、调用线程对象的start方法开启线程。
实现Runnable接口的好处:
1、将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
2,避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。
<span style="font-family:Microsoft YaHei;font-size:14px;">class Ticket implements Runnable{//多个线程卖票,卖100张,不可以用for循环,因为每个循环中的ticket都不一样,必须用while。//这是第二种建立循环的方式:实现Runnable接口,再将建立后的对象传入Thread中。private int ticket = 100;public void run(){while(ticket>0){System.out.println(Thread.currentThread().getName()+"卖票"+ticket);ticket--;}}}class TicketTest{public static void main(String[] args){Ticket t = new Ticket();Thread t1 = new Thread(t);Thread t2 = new Thread(t);Thread t3 = new Thread(t);Thread t4 = new Thread(t);t1.start();t2.start();t3.start();t4.start();}}</span>
为什么要有Runnable接口的出现?
1:通过继承Thread类的方式,可以完成多线程的建立。但是这种方式有一个局限性,如果一个类已经有了自己的父类,就不可以继承Thread类,因为java单继承的局限性。
可是该类中的还有部分代码需要被多个线程同时执行。这时怎么办呢?
只有对该类进行额外的功能扩展,java就提供了一个接口Runnable。这个接口中定义了run方法,其实run方法的定义就是为了存储多线程要运行的代码。
所以,通常创建线程都用第二种方式。
因为实现Runnable接口可以避免单继承的局限性。
2:其实是将不同类中需要被多线程执行的代码进行抽取。将多线程要运行的代码的位置单独定义到接口中。为其他类进行功能扩展提供了前提。
所以Thread类在描述线程时,内部定义的run方法,也来自于Runnable接口。
实现Runnable接口可以避免单继承的局限性。而且,继承Thread,是可以对Thread类中的方法,进行子类复写的。但是不需要做这个复写动作的话,只为定义线程代码存放位置,实现Runnable接口更方便一些。所以Runnable接口将线程要执行的任务封装成了对象。
-------------------------------------------------------
//面试new Thread(new Runnable(){ //匿名public void run(){System.out.println("runnable run");}}){public void run(){System.out.println("subthread run");}}.start(); //结果:subthread run
-------------------------------------------------------
Try {
Thread.sleep(10);
}catch(InterruptedException e){}// 当刻意让线程稍微停一下,模拟cpu切换情况。
1-2 线程安全问题
1-2-1 线程安全问题产生的原因
1、多个线程在操作共享的数据。
2、操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
1-2-2 同步
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。
必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象){
需要被同步的代码;
}
考点问题:请写一个延迟加载的单例模式?写懒汉式;当出现多线程访问时怎么解决?加同步,解决安全问题;效率高吗?不高;怎样解决?通过双重判断的形式解决。
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;}}同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
同步的前提:必须有多个线程并使用同一个锁。
使用this对象作为锁,也就是使用了当前对象,因为锁住了方法,所以相对于代码块来说效率相对较低。
注:静态同步函数的锁是该方法所在的类的字节码文件对象,即类名.class文件
格式:
修饰词 synchronized 返回值类型 函数名(参数列表)
{
需同步的代码;
}
1、同步函数的锁是固定的this。
2、同步代码块的锁是任意的对象。
建议使用同步代码块。
public class ThreadTest {public static void main(String[] args) {Ticket t = new Ticket();Thread t1 = new Thread(t);Thread t2 = new Thread(t);Thread t3 = new Thread(t);t1.start();t2.start();t3.start();}}class Ticket implements Runnable {private static int tick = 100;private boolean flag = true;public void run(){while(flag){synchronized (Ticket.class) {if(tick>0)System.out.println(Thread.currentThread().getName()+" "+tick--);elseflag = false;}}}}示例2:
<span style="font-family:Microsoft YaHei;font-size:14px;">/** * 需求:两个用户向银行存钱,每人存三次 * @author user * */public class BankDemo {public static void main(String[] args) {ATM a = new ATM();new Thread(a).start();new Thread(a).start();}}class Bank{Object obj = new Object();private int sum = 0;public void add(int i) {synchronized (obj) {sum = sum + i;System.out.println(sum);}}}class ATM implements Runnable{private Bank b = new Bank();public void run(){for (int i=0; i<3; i++){b.add(100);}}}</span>
1-3 线程的状态
只是对象线程对象开辟了内存空间和初始化数据。
就绪:新建的对象调用start方法,就开启了线程,线程就到了就绪状态。
在这个状态的线程对象,具有执行资格,没有执行权。
运行:当线程对象获取到了CPU的资源。
在这个状态的线程对象,既有执行资格,也有执行权。
冻结:运行过程中的线程由于某些原因(比如wait,sleep),释放了执行资格和执行权。
当然,他们可以回到运行状态。只不过,不是直接回到。
而是先回到就绪状态。
死亡:当线程对象调用的run方法结束,或者直接调用stop方法,就让线程对象死亡,在内存中变成了垃圾。
(1)这两个方法来自不同的类,sleep()来自Thread类,和wait()来自Object类。
(2)sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,
要让b线程睡觉要在b的代码中调用sleep。而wait()是Object类的非静态方法
(3)sleep()释放资源不释放锁,而wait()释放资源释放锁;
(4)使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
1-4 死锁
class Test implements Runnable{private boolean flag;Test(boolean flag){this.flag = flag;}Object obj = new Object();public void run(){if(flag){while(true){synchronized(obj){System.out.println(Thread.currentThread().getName()+"...if locka ");synchronized(this){System.out.println(Thread.currentThread().getName()+"..if lockb");}}}}else{while(true){synchronized(this){System.out.println(Thread.currentThread().getName()+"..else lockb");synchronized(obj){System.out.println(Thread.currentThread().getName()+".....else locka");}}}}}}/*class MyLock{static Object locka = new Object();static Object lockb = new Object();}*/class DeadLock{public static void main(String[] args) {Thread t1 = new Thread(new Test(true));Thread t2 = new Thread(new Test(false));t1.start();t2.start();}}要避免这种情况。
------- android培训、java培训、期待与您交流! ----------
- 黑马程序员——Java语言基础——04.多线程(1)多线程概念
- 黑马程序员——Java语言基础:多线程
- 黑马程序员——Java语言基础——04.多线程(2)线程间通信
- 黑马程序员——Java语言多线程
- 黑马程序员——Java语言--多线程
- 黑马程序员——Java基础--多线程(1)
- 黑马程序员——Java基础---多线程(1)
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——java基础---多线程
- 黑马程序员——Java基础多线程
- 黑马程序员——Java基础->多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——java基础---多线程
- 黑马程序员——Java基础---多线程
- FFMPEG SDK流媒体开发2---分离.mp4等输入流音视频并且进行解码输出
- MyEclipse报错 Could not create the view: An unexpected exception was thrown.
- 连接linphone的服务器实现来电通话的问题解决
- 字符串匹配算法(三)
- LeetCode OJ 之 Binary Tree Preorder Traversal (二叉树的前序遍历)
- 黑马程序员——Java语言基础——04.多线程(1)多线程概念
- Linux新手入门:账号和密码文件 /etc/passwd和/etc/shadow抢沙发
- 派遣敢死队
- Apache DBCP数据库连接池溢出调整
- Android自定义ViewGroup中LayoutParam的应用
- 使用busybox制作rootfs
- Apache DBCP连接数据库异常重连
- python 读取文件的最后一行
- 在linux中,如何增加、修改、删除、暂停和冻结用户名