黑马程序员_java基础_多线程
来源:互联网 发布:房子装修软件app 编辑:程序博客网 时间:2024/06/07 18:44
要理解多线程,就必须理解线程。而要理解线程,就必须知道进程。
1、 进程
是一个正在执行的程序。
2、线程
就是进程中的一个独立的控制单元。线程在控制着进程的执行。只要进程中有一个线程在执行,进程就不会结束。
一个进程中至少有一个线程。
3、多线程
在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。像种在一个进程中有多个线程执行的方式,就叫做多线程。
4、多线程存在的意义
多线程的出现能让程序产生同时运行效果。可以提高程序执行效率。
5、计算机CPU的运行原理
我们电脑上有很多的程序在同时进行,就好像cpu在同时处理这所以程序一样。但是,在一个时刻,单核的cpu只能运行一个程序。而我们看到的同时运行效果,只是cpu在多个进程间做着快速切换动作。
二、创建线程的方式
创建线程共有两种方式:继承方式和实现方式(简单的说)。
1、 继承方式
通过查找java的帮助文档API,我们发现java中已经提供了对线程这类事物的描述的类——Thread类。这第一种方式就是通过继 承Thread类,然后复写其run方法的方式来创建线程。
创建步骤:
a,定义类继承Thread。
b,复写Thread中的run方法。
目的:将自定义代码存储在run方法中,让线程运行。
c,创建定义类的实例对象。相当于创建一个线程。
d,用该对象调用线程的start方法。该方法的作用是:启动线程,调用run方法。
注:如果对象直接调用run方法,等同于只有一个线程在执行,自定义的线程并没有启动。
覆盖run方法的原因:
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要执行的代码。该存储功能就run方法。也就是说,Thread类中的run方法,用于存储线程要运行的代码。
继承方式代码:
/* 创建两线程,和主线程交替运行。 */ //创建线程Test class Test extends Thread { private String name; Test(String name) { super(name); } //复写run方法 public void run() { for(int x=0;x<60;x++) System.out.println(Thread.currentThread().getName()+"..run..."+x); } } class ThreadTest { public static void main(String[] args) { new Test("one+++").start();//开启一个线程 new Test("tow———").start();//开启第二线程 //主线程执行的代码 for(int x=0;x<170;x++) System.out.println("Hello World!"); } }
2、 实现方式
使用继承方式有一个弊端,那就是如果该类本来就继承了其他父类,那么就无法通过Thread类来创建线程了。这样就有了第二种创建线程的方式:实现Runnable接口,并复习其中run方法的方式。
创建步骤:
a,定义类实现Runnable的接口。
b,覆盖Runnable接口中的run方法。目的也是为了将线程要运行的代码存放在该run方法中。
c,通过Thread类创建线程对象。
d,将Runnable接口的子类对象作为实参传递给Thread类的构造方法。
实现方式代码:/*需求:简单的卖票程序。多个窗口卖票。*/class Ticket implements Runnable//extends Thread{private int tick = 100;public void run(){while(true){if(tick>0){//显示线程名及余票数System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);}}}} class TicketDemo{public static void main(String[] args) {//创建Runnable接口子类的实例对象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();}}
三、线程安全问题
1、导致安全问题的出现的原因:
当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。
2、解决办法——同步
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
在java中对于多线程的安全问题提供了专业的解决方式——synchronized(同步)
这里也有两种解决方式,一种是同步代码块,还有就是同步函数。都是利用关键字synchronized来实现。
a、同步代码块
用法:
synchronized(对象)
{需要被同步的代码}
同步可以解决安全问题的根本原因就在那个对象上。其中对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
同步代码块的代码如下:/* 给卖票程序示例加上同步代码块。 */ class Ticket implements Runnable { private int tick=100; Object obj = new Object(); public void run() { while(true) { //给程序加同步,即锁 synchronized(obj) { if(tick>0) { try { //使用线程中的sleep方法,模拟线程出现的安全问题 //因为sleep方法有异常声明,所以这里要对其进行处理 Thread.sleep(10); } catch (Exception e) { } //显示线程名及余票数 System.out.println(Thread.currentThread().getName()+"..tick="+tick--); } } } } }
b,同步函数
格式:
在函数上加上synchronized修饰符即可。
那么同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。
同步函数代码如下:
class Ticket implements Runnable { private int tick=100; Object obj = new Object(); public void run() { while(true) { show(); } } //直接在函数上用synchronized修饰即可实现同步 public synchronized void show() { if(tick>0) { try { //使用线程中的sleep方法,模拟线程出现的安全问题 //因为sleep方法有异常声明,所以这里要对其进行处理 Thread.sleep(10); } catch (Exception e) { } //显示线程名及余票数 System.out.println(Thread.currentThread().getName()+"..tick="+tick--); } } }
3、同步的前提
a,必须要有两个或者两个以上的线程。
b,必须是多个线程使用同一个锁。
4、同步的利弊
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。
四、静态函数的同步方式
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不在是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如:
类名.class 该对象的类型是Class
这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class
静态函数同步方式代码如下:/* 加同步的单例设计模式————懒汉式 */ class Single { private static Single s = null; private Single(){} public static void getInstance() { if(s==null) { synchronized(Single.class) { if(s==null) s = new Single(); } } return s; } }
五、死锁
当同步中嵌套同步时,就有可能出现死锁现象。
死锁的代码如下:/*写一个死锁程序*/ //定义一个类来实现Runnable,并复写run方法class LockTest implements Runnable{private boolean flag;LockTest(boolean flag){this.flag=flag;}public void run(){if(flag){while(true){synchronized(LockClass.locka)//a锁{System.out.println(Thread.currentThread().getName()+"------if_locka"); synchronized(LockClass.lockb)//b锁{System.out.println(Thread.currentThread().getName()+"------if_lockb");}}}}else{while(true){synchronized(LockClass.lockb)//b锁{ System.out.println(Thread.currentThread().getName()+"------else_lockb"); synchronized(LockClass.locka)//a锁{ System.out.println(Thread.currentThread().getName()+"------else_locka");}}}}}} //定义两个锁class LockClass{static Object locka = new Object();static Object lockb = new Object();} class DeadLock{public static void main(String[] args){//创建2个进程,并启动new Thread(new LockTest(true)).start();new Thread(new LockTest(false)).start();}}
六、线程间通信
其实就是多个线程在操作同一个资源,但是操作的动作不同。
使用同步操作同一资源的代码:/*有一个资源一个线程往里存东西,如果里边没有的话一个线程往里取东西,如果里面有得话*/ //资源class Resource{private String name;private String sex;private boolean flag=false;public synchronized void setInput(String name,String sex){if(flag){try{wait();}catch(Exception e){}//如果有资源时,等待资源取出}this.name=name;this.sex=sex; flag=true;//表示有资源notify();//唤醒等待} public synchronized void getOutput(){if(!flag){try{wait();}catch(Exception e){}//如果木有资源,等待存入资源}System.out.println("name="+name+"---sex="+sex);//这里用打印表示取出flag=false;//资源已取出notify();//唤醒等待}} //存线程class Input implements Runnable{private Resource r;Input(Resource r){this.r=r;}public void run()//复写run方法{int x=0;while(true){if(x==0)//交替打印张三和王羲之{r.setInput("张三",".....man");}else{r.setInput("王羲之","..woman");}x=(x+1)%2;//控制交替打印}}} //取线程class Output implements Runnable{private Resource r;Output(Resource r){this.r=r;}public void run()//复写run方法{while(true){r.getOutput();}}} class ResourceDemo2 {public static void main(String[] args) {Resource r = new Resource();//表示操作的是同一个资源 new Thread(new Input(r)).start();//开启存线程 new Thread(new Output(r)).start();//开启取线程}}
七、停止线程
只有一种办法,那就是让run方法结束。
那怎么样让run方法结束呢
开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。
停止线程,要设置一个flag标记,代码如下:
public void run() { while(flag) { System.out.println(Thread.currentThread().getName()+"....run"); } }
- 黑马程序员_Java基础_多线程1
- 黑马程序员_Java基础_多线程2
- 黑马程序员_Java基础_多线程_11
- 黑马程序员_Java基础_多线程_12
- 黑马程序员_Java基础03_多线程
- 黑马程序员_Java基础_多线程1
- 黑马程序员_Java基础_多线程2
- 黑马程序员_Java基础_多线程
- 黑马程序员_java基础_多线程
- 黑马程序员_java基础_多线程
- 黑马程序员_学习日记_Java基础_多线程
- 黑马程序员_java基础加强6_多线程加强
- 黑马程序员_java基础加强7_多线程加强
- 黑马程序员_java基础加强8_多线程加强
- 黑马程序员_java基础加强9_多线程加强
- 黑马程序员_java基础_多线程学习笔记
- 黑马程序员_java基础学习笔记10_多线程
- 黑马程序员_java基础--多线程
- linux shell — 2.Linux的档案属性和目录配置
- hdu1669杰米的电话联系人(多重匹配)
- 欢迎使用CSDN-markdown编辑器
- OnTouchEvent、 自定义属性
- apk在launcher配置多个icon启动入口
- 黑马程序员_java基础_多线程
- POJ 3126 Pime Path bfs
- GridViewAdapter
- hdu 1285
- ajax全接触--imooc
- 黑马程序员_java学习笔记 9. 正则表达式
- POJ 1087 A Plug for UNIX (网络流)
- C++中基类的虚析构函数问题
- 《活着英文版自序》读后感