Java多线程完整版基础知识
2016-12-28 09:04
211 查看
ava多线程完整版基础知识
(翟开顺由厚到薄系列)
1.前言
线程是现代2.概述
2.1线程是什么
主要是线程与进程的区别,这里不再阐述,自行网上搜索为什么使用线程:操作系统切换多个线程要比调度进程在速度上快很多,进程间无法共享,通讯麻烦。线程之间由于共享数据,所以交换数据很方便下面有个例子去解释多线程与单线程A单线程例子1234567891011121314151617181920212223 | package firstTread; /** *@authorzhaikaishun * */ public class TreadDemo1{ public static void main(String[]args){ new TestThread().run(); //会一直执行这段代码 while ( true ){ System.out.println( "mainthreadisrunning" ); } } } class TestThread{ //这里没有继承Thread类 public void run(){ while ( true ){ System.out.println(Thread.currentThread().getName()+ "ishererun" ); //会一直执行 } } } |
1234567891011121314151617181920212223 | package firstTread; /** *@authorzhaikaishun * */ public class TreadDemo1{ public static void main(String[]args){ new TestThread().run(); //会一直执行这段代码 while ( true ){ System.out.println( "mainthreadisrunning" ); } } } class TestThread{ //这里没有继承Thread类 public void run(){ while ( true ){ System.out.println(Thread.currentThread().getName()+ "ishererun" ); //会一直执行 } } } |
结果:
2.2java对线程的支持
Java吸收了一些多线程操作系统的技术特性,经过优化处理,在语言层次上实现了对线程的支持,它提供了Thread,Runnable,Thread,Group等一系列封装和类的接口,让程序员可以高效的开发java多线程程序,java还提供synchronized关键字和Object的wait(),notify()机制,用来实现进程的同步。3.在java中使用线程
3.1Thread类和Runable方法
(a)继承Thread类Java用Thread类对线程进行封装,一旦创建了这个Thread实例,jvm就会为我们创建一个线程,当我们调用Thread类的strat方法时,线程就开始运行起来。创建线程的方法如下代码3.1,继承thread类创建线程的代码3.2两种线程实现方法的比较
不论是那种方式,最后都需要通过Thread类的实例调用start()方法来开始线程的执行,start()方法通过java虚拟机调用线程中定义的run方法来执行该线程。通过查看java源程序中的start()方法的定义可以看到,它是通过调用操作系统的start0方法来实现多线程的操作的。但是一般在系统的开发中遇到多线程的情况的时候,以实现Runnable接口的方式为主要方式。这是因为实现接口的方式有很多的优点:1、就是通过继承Thread类的方式时,线程类就无法继承其他的类来实现其他一些功能,实现接口的方式就没有这中限制;2.也是最重要的一点就是,通过实现Runnable接口的方式可以达到资源共享的效果。这个不举一个例子可能不太清楚,下面我就举一个买票的程序的例子首先我们先写一个继承Thread类的程序,看看效果首先是一个线程类,继承了程序清单:ThreadTest类12345678910111213141516 | package firstTread; /** *@authorzhaikaishun * */ public class ThreadTest extends Thread{ private int tickets= 100 ; public void run(){ while ( true ){ //模拟买票程序,每次调用这个方法,ticket就会减一张 if (tickets> 0 ) System.out.println(Thread.currentThread().getName()+ "issalingticket" +tickets--); } } } |
12345678910111213141516 | package firstTread; /** *@authorzhaikaishun * */ public class ThreadDemo4{ public static void main(String[]args){ ThreadTestt= new ThreadTest(); t.start(); t.start(); t.start(); t.start(); } } |
123456789101112131415 | package firstTread; /** *@authorzhaikaishun * */ public class ThreadDemo4{ public static void main(String[]args){ new ThreadTest().start(); new ThreadTest().start(); new ThreadTest().start(); new ThreadTest().start(); } } |
12345678910111213141516 | package firstTread; /** *@authorzhaikaishun * */ public class ThreadDemo5{ public static void main(String[]args){ ThreadTestt= new ThreadTest(); //这个类的实例就可以被一个java的thread对象调用。 new Thread(t).start(); //thread对象调用。 new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } } |
12345678910111213141516 | package firstTread; /** *@authorzhaikaishun * */ public class ThreadTest implements Runnable{ //现在是实现Runnable接口 private int tickets= 100 ; public void run(){ while ( true ){ //模拟买票程序,每次调用这个方法,ticket就会减一张 if (tickets> 0 ) System.out.println(Thread.currentThread().getName()+ "issalingticket" +tickets--); } } } |
3.3线程的状态和属性
4.线程同步
4.1线程安全问题:
在上面的卖票得例子中,有可能出现一种我们不想要的情况,那就是有可能同一张票被打印两次多多次,打印的票号码为0甚至是负数等原因在这一段代码中if
(tickets>0)
System.out.println(Thread.currentThread().getName()+
"issalingticket"
+tickets--);假如当tickets=1的时候,线程1刚刚判断完
if
(tickets>0),正要处理下面的语句的时候,cpu被线程2给抢走,线程2开始执行,当线程2执行完一个run方法,这里的tickets会减少1,这时候tickets为0,然后跳转到线程1的中断的地方继续执行,因为之前线程1判断过if
(tickets>0),所以这里不再需要判断,直接执行System.out.println(Thread.currentThread().getName()+
"issalingticket"
+tickets--);,将会打印出为0的票,也就意味着最后一张票卖了2次
4.2同步代码块
使用synchronized方法:保证代码块的内容是“原子的”(物理中原子的也是可以分割的,所以我一般不说原子),保证里面的内容只能被一个线程在执行,必须等执行的线程离开后才能让其他线程执行,也就和独木桥差不多。123 | synchronized(object){ //这里写代码块 } |
同步代码块实现同步的原理:任何类型的对象都有一个标志位,该标志位具有0,1两种状态,其开始为1,当执行到synchrozied方法之后,object对象标识位变为0,另外一个线程执行到synchrozed方法之后,将会先判断这个状态,如果发现是0,就暂时阻塞。可以把这个标志位理解成一个箱子的锁,该箱子只能放一个人的东西。
上述卖票程序的ThreadTest类可以如下:
12345678910111213141516171819202122232425262728 | package firstTread; /** *@authorzhaikaishun * */ public class ThreadTest implements Runnable{ //现在是实现Runnable接口 private int tickets= 100 ; Stringstr= new String( "" ); //这里设置一个对象,任意一个对象都可以 public void run(){ while ( true ){ //模拟买票程序,每次调用这个方法,ticket就会减一张 synchronized (str){ //这里写代码块 if (tickets> 0 ){ try { Thread.sleep( 10 ); } catch (Exceptione){ System.out.println(e.getMessage()); } System.out.println(Thread.currentThread().getName() + "issalingticket" +tickets--); } } } } } |
结果:
Stringstr=
new
String(
""
); 这个标志对象,相当于监听对象,必须放在run方法的外面,如果放在run方法里面,四个线程每次调用run方法,就会产生4个监听对象,这四个同步监视器是4个不同的对象,会导致彼此之间不能同步。
4.3.同步函数
上述是对代码块进行的同步,同样,我们也能对某一个方法进行同步,只需要在同步的函数前加上关键字synchronized即可
例如上述代码可以写成:
1234567891011121314151617181920212223242526 | package firstTread; /** *@authorzhaikaishun * */ public class ThreadTest implements Runnable{ //现在是实现Runnable接口 private int tickets= 100 ; Stringstr= new String( "" ); //这里设置一个对象,任意一个对象都可以 public void run(){ while ( true ){ //模拟买票程序,每次调用这个方法,ticket就会减一张 sale(); } } public synchronized void sale(){ //同步方法 if (tickets> 0 ){ try { Thread.sleep( 10 ); } catch (Exceptione){ System.out.println(e.getMessage()); } System.out.println(Thread.currentThread().getName() + "issalingticket" +tickets--); } } } |
4.4.代码块与函数间的同步
请看下面方法,通过一个str的取值,来判断是代码块还是函数间的同步代码清单ThreadDemo61234567891011121314151617181920 | package firstTread; /** *@authorzhaikaishun * */ public class ThreadDemo6{ public static void main(String[]args){ ThreadTestt= new ThreadTest(); new Thread(t).start(); //thread对象调用。 //让线程暂停一会儿才直观 try {Thread.sleep( 1 );} catch (Exceptione){}; t.str= new String( "method" ); //如果str是method,调用同步函数 new Thread(t).start(); } } |
123456789101112131415161718192021222324252627282930313233343536373839404142434445 | package firstTread; /** *@authorzhaikaishun * */ public class ThreadTest implements Runnable{ //现在是实现Runnable接口 private int tickets= 100 ; Stringstr= new String( "" ); //这里设置一个对象,任意一个对象都可以 public void run(){ if ( "method" .equals(str)){ while ( true ){ sale(); } } else { synchronized (str){ while ( true ){ if (tickets> 0 ){ try { Thread.sleep( 10 ); } catch (Exceptione){ System.out.println(e.getMessage()); } System.out.println(Thread.currentThread().getName() + "issalingticket" +tickets--); } } } } } public synchronized void sale(){ //同步方法 if (tickets> 0 ){ try { Thread.sleep( 10 ); } catch (Exceptione){ System.out.println(e.getMessage()); } System.out.print( "函数方法在执行:" ); System.out.println(Thread.currentThread().getName() + "issalingticket" +tickets--); } } } |
5.死锁:
死锁比较少见,而且难于调试:所谓死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。其实很久之前学习数字电路,经常会遇到一些锁,这也是自动化的一些常见的问题,在计算机中,也有类似的东西,请看下图123456789101112131415161718192021222324252627282930313233343536373839404142434445464748 | package deadlock; public class RunnableTest implements Runnable{ private int flag= 1 ; private static Objectobj1= new Object(),obj2= new Object(); public void run(){ System.out.println( "flag=" +flag); if (flag== 1 ){ synchronized (obj1){ System.out.println( "我已经锁定obj1,休息0.5秒后锁定obj2去,但是估计进不去obj2,因为obj2也正在一个同步方法中" ); try { Thread.sleep( 500 ); } catch (InterruptedExceptione){ e.printStackTrace(); } synchronized (obj2){ System.out.println( "进入了obj2 ); } } } if (flag== 0 ){ synchronized (obj2){ System.out.println( "我已经锁定obj2,休息0.5秒后锁定obj1去,但是估计进不了obj1,因为这obj1也在一个同步方法中 " ); try { Thread.sleep( 500 ); } catch (InterruptedExceptione){ e.printStackTrace(); } synchronized (obj1){ System.out.println( "进入了obj1 ); } } } } public static void main(String[]args){ RunnableTestrun01= new RunnableTest(); RunnableTestrun02= new RunnableTest(); run01.flag= 1 ; run02.flag= 0 ; Threadthread01= new Thread(run01); Threadthread02= new Thread(run02); System.out.println( "线程开始喽!" ); thread01.start(); thread02.start(); } } |
6.线程间的通信
我们先从下面一个例子引出线程中的通信下面例子讲的是一个生产和消费的关系,生产一样东西,取走这样东西。这个程序是每生产出一个PDD(人名),并且给这个人赋值为男然后再取出来,然后生产一个“娇妹”(人名),并且赋值为女,然后再取出来。代码清单如下。一个类Q,用来存储数据Q:123456789101112131415 | package communication; public class Q{ private Stringname= "PDD" ; private Stringsex= "男" ; public synchronized void put(Stringname,Stringsex){ this .name=name; try {Thread.sleep( 1 );} catch (Exceptione){System.out.println(e.getMessage());} this .sex=sex; } public synchronized void get(){ System.out.println(name+ "----" +sex); } } |
123456789101112131415161718 | package communication; public class Producer implements Runnable{ Qq= null ; public Producer(Qq){ this .q=q; } int i= 0 ; public void run(){ while ( true ){ if (i== 0 ) q.put( "PDD" , "男" ); else q.put( "娇妹" , "女" ); i=(i+ 1 )% 2 ; } } } |
1234567891011121314 | package communication; public class Customer implements Runnable{ Qq= null ; public Customer(Qq){ this .q=q; } public void run(){ while ( true ){ q.get(); } } } |
123456789101112 | package communication; public class ThreadCommunication{ public static void main(String[]args){ Qq= new Q(); new Thread( new Producer(q)).start(); try {Thread.sleep( 1 );} catch (Exceptione){System.out.println(e.getMessage());} new Thread( new Customer(q)).start(); } } |
PDD----男
PDD----男
PDD----男
PDD----男
娇妹----女
娇妹----女
娇妹----女
娇妹----女
娇妹----女
娇妹----女
娇妹----女
.......分析:这并不是我们想要的,我们想要的是下面这种类型的,producer每存放一次数据,customer取一次数据,反之,producer必须等customer取完数据之后才能开始存数据。这就是要将的线程间的通信问题,Java通过Object的wait,notify,notifyAll这几个方法实现线程间的通信。PDD----男娇妹----女PDD----男娇妹----女PDD----男娇妹----女wait:告诉当前线程放弃监视器并且进入线程休眠状态,直到其他线程进入相同的监视器并且调用notify为止。notify:唤醒同一对象监视器中调用wait的第一个线程。notifyAll:唤醒同一对象监视器中调用wait的所有线程,具有优先级高的线程将会被先唤醒。如果想让上面的程序满足我们的要求,我们可以在类Q中定义一个新的成员变量bFull来标示数据存储空间的状态,当Customer取走数据后,bFull为false;当Producer存入数据后,bFull为true。只有bFull为true时,Customer才能取走数据,只有当bFull为False时Producer才能放入数据Q的清单如下:
123456789101112131415161718192021222324252627282930313233 | package communication; public class Q{ private Stringname= "PDD" ; private Stringsex= "男" ; boolean bFull= false ; public synchronized void put(Stringname,Stringsex){ if (bFull) try { wait(); } catch (InterruptedExceptione1){ //TODOAuto-generatedcatchblock e1.printStackTrace(); } this .name=name; try {Thread.sleep( 1 );} catch (Exceptione){System.out.println(e.getMessage());} this .sex=sex; bFull= true ; notify(); } public synchronized void get(){ if (!bFull) try { wait(); } catch (InterruptedExceptione1){ //TODOAuto-generatedcatchblock e1.printStackTrace(); } System.out.println(name+ "----" +sex); bFull= false ; notify(); } } |
PDD----男
娇妹----女
PDD----男
娇妹----女
PDD----男
娇妹----女参考文献:【1】J2SE进阶(java研究组织精品图书)【2】张孝祥-Java就业教程【3】java编程思想【4】java核心技术卷1
相关文章推荐
- Java多线程完整版基础知识
- java多线程基础知识:如何编写线程安全代码
- Java 多线程(一) 基础知识与概念
- Java基础知识整理四(多线程编程以…
- 黑马程序员学习log第四篇基础知识:JAVA的面向对象之多线程总结
- 黑马程序员---java基础知识之多线程
- Java多线程编程总结笔记——一多线程基础知识
- Java多线程基础知识
- java 多线程基础知识2---同步机制
- java基础知识 多线程
- java基础知识回顾之java Thread类学习(五)--java多线程安全问题(锁)同步的前提
- Java多线程基础知识
- Java多线程基础知识总结笔记
- 黑马程序员-Java基础知识预备之Java多线程
- java 多线程基础知识3----线程封闭
- Java多线程1——基础知识
- Java 多线程(一) 基础知识与概念
- Java多线程1——基础知识
- JAVA基础知识之java多线程时数据同步问题
- 黑马程序员:java基础知识(多线程)