---------- android培训java培训、期待与您交流! ----------

一、概述

  (一)进程

  正在执行中的程序,每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。

  (二)线程

  进程中的一个独立的控制单元。线程在控制着进程的执行。一个进程中至少有一个线程。只要进程中有一个线程在执行,进程就不会结束。

  (三)多线程

  在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。这种在一个进程中有多个线程执行的方式,就叫做多线程。

  (四)多线程创建线程的目的和意义

  1、创建目的:开启一条执行路径;运行的代码就是执行路径的任务。JVM创建的主线程的任务都定义在了主函数中。run方法中,定义的代码就是线程要运行的任务代码。

  2、创建的意义:可以让程序产生同时运行效果;可以提高程序执行效率。

二、创建线程的方式

  创建线程共有两种方式:继承方式和实现方式。

  (一)“继承Thread类”创建多线程

  Java API文档的java.lang包中提供了Thread类,通过继承Thread类,然后覆写其run方法来创建线程。

  1、创建步骤

  (1)定义类,继承Thread。

  (2)覆写Thread类中的run方法。

  (3)调用start方法,启动线程,调用run方法执行线程任务代码。

  2、多线程run和start的特点

  为什么要覆盖run方法呢?Thread类用于描述线程,该类定义了一个功能,用于存储要运行的代码,这些运行的代码就存储在run方法中。

  3、练习:创建两线程,和主线程交替运行。示例代码如下:

 //创建线程Test
class Test extends Thread{
Test(String name){
super(name);
}
//覆写run方法
public void run(){
for(int x=0;x<150;x++)
        System.out.println(Thread.currentThread().getName()+"...run..."+x);
}
}
class MultiThread{
public static void main(String[] args){
new Test("Thread one-------").start();//开启第一个线程
new Test("Thread two---").start();//开启第二个线程
//主线程执行的代码
for(int x=0;x<150;x++)
System.out.println("Hello World!");
}
}

  说明:上述代码每次运行的结果都不一样。因为多个线程都在获取CPU的执行权,CPU执行到谁谁就运行。更明确一点说,在某一时刻,只能有一个程序在运行(多核除外)。CPU是在做着快速的切换,已达到看上去是同时在运行的效果。

  (二)“实现Runnable接口”创建多线程

  使用Thread类继承方式有一个弊端,即如果该类本来就继承了其他父类,那么就无法通过继承Thread类来创建多线程。这样就有了第二种创建线程的方式:实现Runnable接口,并覆写其中的run方法来创建多线程。

  1、创建步骤

  (1)定义类实现Runnable接口。

  (2)覆盖Runnable接口中的run方法。

  (3)通过Thread类创建线程对象。

  (4)将Runnable接口的子类对象作为实参传递给Thread类的构造方法。

  (5)调用Thread类中start方法启动线程,start方法会自动调用Runnable接口子类的run方法。

  2、细节问题

  (1)为什么要将Runnable接口的子类对象传递给Thread类的构造函数?

  因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定对象的run方法,就必须明确该run方法所属对象。

  (2)实现Runnable的好处:将线程任务从线程的子类中分离出来,进行单独的封装。避免了Java单继承的局限性。在定义线程时,建议使用实现方式。

  (三)“实现方式”和“继承方式”的区别

  1、继承方式:线程代码存放在Thread子类的run方法中。

  2、实现方式:线程代码存在接口的子类run方法中。

  (四)线程运行状态结构图

  运行状态结构图说明:

  1、被创建:等待启动,调用start启动。

  2、运行状态:具有执行资格和执行权。

  3、临时状态(阻塞):有执行资格,但是没有执行权。

  4、冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。

  5、消忙状态:stop()方法,或者run方法结束。

  (五)获取线程对象以及名称

  1、原来的线程都有自己默认的名称,命名方式是Thread-编号,编号从0开始。

  2、通过对象this.getName()、super(name),可以获取线程对象的名称。也可以通过Thread.currentThread().getName()标准方式获取线程的名称。说明:① static Thread currentThread():获取当前线程对象。② getName():获取线程名称。

  3、设置线程名称:setName(String name)方法或者构造函数都可以实现。

  4、局部变量在每一个线程区域中都有独立的一份。

  (六)练习:售票例子

 /* 简单卖票程序:多窗口同时卖票 */
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){
Ticket t = new Ticket();//创建Runnable接口子类的实例对象
//有多个窗口在同时卖票,创建三个线程
Thread t1 = new Thread(t);//
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
//启动线程
t1.start();
t2.start();
t3.start();
}
}

三、多线程的安全问题

  (一)多个线程“安全问题”

  1、问题的原因:当多条线程在操作同一个共享数据时,因为CPU的权限分配而出现一个线程对多条语句只执行一部分,还没有执行完,另外一条线程参与进来执行,导致共享数据的错误。

  2、解决思路:对操作共享数据的多条语句,只能让一个线程都执行完,而其他线程不能执行。

  (二)解决方式

  java对于多线程的安全问题提供了专业的解决方式:为需要操作共享数据的多条语句添加synchronized(同步)修饰符。有两种解决方式:一种是同步代码块,另一种是同步函数。都是利用关键字synchronized来实现。

  1、同步代码块

  (1)格式:synchronized(对象){ 需要被同步的代码块 }

  同步可以解决安全问题的根本原因就在那个对象上。对象如同一把锁,持有锁的线程可以在执行同步代码块,而没有持有锁的线程即使获取CPU执行权,也因为没有获取对象而无法执行同步代码块。

  (2)同步代码块使用的锁是哪一个呢?任意对象。

  (3)同步代码块示例:为多线程卖票程序加上同步代码块,代码如下:

 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--);// 显示线程名及余票数
}
}
}
}
}

  2、同步函数

  (1)格式:synchronized作为函数的修饰符,添加在函数上,就构成了同步函数。

  (2)同步函数用的是哪一个锁呢?因为函数需要被对象调用,那么函数都持有一个所属对象引用,即this。所以同步函数使用的锁是this。

  (3)同步函数示例

 public synchronized void add(int num){
sun = sum + num;
try{
Thread.sleep(10);
}catch(InterruptedException e){
}
System.out.println(“sum = ”+ sum);
}

  3、静态同步函数

  (1)格式:同步函数被static修饰后,就构成了静态同步函数。

  (2)静态同步函数使用的锁是什么呢?Class对象。

  说明:因为静态在进内存时,内存中没有本类对象,但一定有该类对应的字节码文件对象,因此,静态同步函数使用的锁是该函数所属字节码文件对象,即 类名.class,该对象的类型是Class,可以用getClass()方法获取,也可以用 类名.class获取。

  (3)静态同步函数代码示例:

 class StaticSynchDemo implements Runnable{
private static int num = 100;
public void run(){ System.out.println(“num = ”+ num); };
public static synchronized void show() {
if(num>0) {
try{
Thread.sleep(10);
}catch(InterruptedException e){ }
System.out.println(Thread.currentThread().getName()+".....function...."+num--);
}
}
}

  (三)同步的前提和利弊

  1、同步的前提

  (1)必须要有两个或者两个以上的线程。

  (2)必须是多个线程使用同一个锁。

  (3)必须保证同步中,只能有一个线程在运行

  2、同步的利弊

  利:解决了多线的安全问题。

  弊:多个线程需要判断锁,较为消耗资源。

  (四)如何寻找多线程中的安全问题

  (1)明确哪些代码是多线程运行代码。

  (2)明确共享数据。

  (3)明确多线程运行代码中,哪些语句是操作共享数据的。

四、死锁

  (一)死锁:常见情景之一是“同步的嵌套”。

  (二)“同步嵌套”出现的死锁示例:

 /* 写一个死锁程序 */
class DeadLock implements Runnable {
private boolean flag;
DeadLock(boolean flag) {
this.flag = flag;
}
public void run() {
if (flag){
while (true)
synchronized (MyLock.locka) {
System.out.println(Thread.currentThread().getName()+ "..if locka....");
synchronized (MyLock.lockb){
System.out.println(Thread.currentThread().getName()+ "..if lockb....");
}
}
} else {
while (true)
synchronized (MyLock.lockb) {
System.out.println(Thread.currentThread().getName()+ "..else lockb....");
synchronized (MyLock.locka) {
System.out.println(Thread.currentThread().getName()+ "..else locka....");
}
}
}
}
} class MyLock {
public static final Object locka = new Object();
public static final Object lockb = new Object();
} class DeadLockTest {
public static void main(String[] args) {
DeadLock a = new DeadLock(true);
DeadLock b = new DeadLock(false); Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
}
}

五、线程间通信

  (一)线程间通信

  多个线程在操作同一个资源,但是操作的动作不同。

  (二)等待唤醒机制

  1、涉及的方法

  (1)wait(): 让线程处于冻结状态,被wait的线程会被存储到线程池中。

  (2)notify():唤醒线程池中一个线程(任意)。

  (3)notifyAll():唤醒线程池中的所有线程。

  2、为什么这些方法都必须定义在同步中?

  因为这些方法是用于操作线程状态的方法,必须要明确到底操作的是哪个锁上的线程。

  3、为什么操作线程的方法wait、notify、notifyAll定义在了Object类中?

  因为这些方法是监视器的方法。监视器其实就是锁,锁可以是任意的对象,任意的对象调用的方式一定定义在Object类中。

  4、wait 和 sleep 区别?

  (1)wait可以指定时间也可以不指定;而sleep必须指定时间。

  (2)在同步中时,各自对CPU的执行权和锁的处理不同:① wait释放执行权,释放锁。② sleep释放执行权,不释放锁。

  5、为什么要定义notifyAll ?

  因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。

 * 练习:多线程通信示例(生产者——消费者)
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name){
while(flag)
try{this.wait();}catch(InterruptedException e){}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
notifyAll();
} public synchronized void out(){
while(!flag)
try{this.wait();}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);
flag = false;
notifyAll();
}
} class Producer implements Runnable{
private Resource r;
Producer(Resource r){
this.r = r;
}
public void run(){
while(true){
r.set("烤鸭");
}
}
} class Consumer implements Runnable{
private Resource r;
Consumer(Resource r){
this.r = r;
}
public void run(){
while(true){
r.out();
}
}
} class ProducerConsumerDemo{
public static void main(String[] args){
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r); Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}

  (三)关于同步和锁的JDK1.5新特性

  1、jdk1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。

  2、Lock接口:替代了同步代码块或同步函数,将同步的隐式锁操作变成显式的锁操作。同时更为灵活,可以一个锁上加上多组监视器。

  3、Condition接口:替代了Object中的wait()、notify()、 notifyAll()方法,将这些监视器方法单独进行了封装,变成Condition监视器对象。Condition对象可以和任意锁进行组合。

  4、接口Lock上的一些方法

  (1)lock():获取锁。

  (2)unlock():释放锁,通常需要定义finally代码块中。

  5、接口Condition上的一些方法

  (1)await():使当前线程在接到信号或被中断之前一直处于等待状态。

  (2)signal():唤醒一个等待线程。

  (3)signalAll():唤醒所有等待线程。

 练习:多生产者多消费者问题(JDK1.5解决办法)
import java.util.concurrent.locks.*;
class Resource2
{
private String name;
private int count = 1;
private boolean flag = false; // 创建一个锁对象。
Lock lock = new ReentrantLock(); //通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。
Condition producer_con = lock.newCondition();
Condition consumer_con = lock.newCondition(); public void set(String name)
{
lock.lock();
try
{
while(flag)
try{producer_con.await();}catch(InterruptedException e){} this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者5.0..."+this.name);
flag = true;
consumer_con.signal();
}
finally
{
lock.unlock();
} } public void out()
{
lock.lock();
try
{
while(!flag)
try{consumer_con.await();}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消费者.5.0......."+this.name);
flag = false;
producer_con.signal();
}
finally
{
lock.unlock();
} }
} class Producer2 implements Runnable
{
private Resource r;
Producer2(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.set("烤鸭");
}
}
} class Consumer2 implements Runnable
{
private Resource r;
Consumer2(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
} class ProducerConsumerDemo2
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer2 pro = new Producer2(r);
Consumer2 con = new Consumer2(r); Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}

六、停止线程

  (一)停止线程的方式

    1、stop方法(已过时)。

    2、run方法结束。

  (二)run()方法结束线程

   1、“定义标记方式”结束线程任务

   怎么控制线程的任务结束呢? 开启多线程运行,运行代码通常都会有循环结构,只要控制住循环就可以让run方法结束,也就是线程结束。控制循环通常就用“定义标记”来完成。

   2、“interrupt()方法”结束线程任务

   如果线程处于了冻结状态,无法读取标记,如何结束呢?可以使用Thread对象的interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格,再通过操作标记,让线程任务结束。但是强制动作会发生InterruptedException,记得要处理

  (三)线程停止示例

 class StopThread implements Runnable{
private boolean flag = true;
public synchronized void run(){
while(flag){
try{
wait();
}catch (InterruptedException e){
System.out.println(Thread.currentThread().getName()+"....."+e);
flag = false;
}
System.out.println(Thread.currentThread().getName()+"......++++");
}
}
public void setFlag(){
flag = false;
}
} class StopThreadDemo{
public static void main(String[] args){
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.setDaemon(true);//将该线程标记为守护线程(后台线程), 该方法必须在启动线程前调用。
t2.start(); int num = 1;
for(;;){
if(++num==50){
t1.interrupt();
break;
}
System.out.println("main...."+num);
}
System.out.println("over");
}
}

  (四)守护线程(可理解为:后台线程)

  void setDaemon(boolean on) :将该线程标记为守护线程或用户线程,可以理解为后台进程。

  守护线程的特点:

  1、该方法必须在启动线程前调用。

  2、当正在运行的线程都是守护线程时,Java 虚拟机退出。

  3、当所有的前台线程执行结束,后台线程无论处于什么状态,都自动结束。

  (五)线程类的其他方法

  1、setPriority(int newPriority)方法(用来设置线程的优先级)

  (1)参数newPriority的取值有:MAX_PRIORITY(最高优先级10)、MIN_PRIORITY(最低优先级)、NORM_PRIORITY(默认优先级5)

  (2)格式:线程.setPriority(Thread. MAX_PRIORITY)

  2、join()方法

  当A线程执行到了b线程的.join()方法时,A线程就会等待,等B线程都执行完,A线程才会执行。(此时B和其他线程交替运行。)join可以用来临时加入线程执行。

  3、yield()方法

  可以暂停当前线程,释放执行权,让其他线程执行。

  4、toString()方法

  返回该线程的字符串表示形式,包括线程名称、优先级和线程组。

七、面试题

  (一)试题1:以下代码,如果错误 错误发生在哪一行?  

 class Test implements Runnable{
public void run(Thread t)
{ }
}

  分析:错误在第一行,应该被abstract修饰

  (二)试题2:以下代码的运行结果是什么?深入分析。

 class ThreadTest{
public static void main(String[] args){
new Thread(new Runnable(){
public void run(){
System.out.println("runnable run");
}
})
{
public void run(){
System.out.println("subThread run");
}
}.start();
}
}

  运行结果:subThread run。

  分析:首先当然是以子类为主,所以先执行new Thread(){……..}子类内容。如果子类方法没有内容,再以任务为主,即运行new Runnable(){public void run(){………}}。如果任务都没有,以Thread自己的run方法为主。

---------- android培训java培训、期待与您交流! ----------

黑马程序员——【Java基础】——多线程的更多相关文章

  1. 黑马程序员 Java基础<九>---> 多线程

    ASP.Net+Android+IOS开发..Net培训.期待与您交流! 多线程 一.概述: 1.线程是什么 说到线程,我们就得先说说进程.所谓进程,就是一个正在执行(进行)中的程序,每一个进程执行都 ...

  2. 黑马程序员——JAVA基础之多线程的安全问题

    ------- android培训.java培训.期待与您交流! ---------- 导致多线程出现问题的一个特殊的状态:就绪.具备了执行资格,但是还没有获取资源. 导致安全问题的出现的原因: 1. ...

  3. 黑马程序员——JAVA基础之多线程的线程间通讯等

    ------- android培训.java培训.期待与您交流! ---------- 线程间通讯: 其实就是多个线程在操作同一个资源,但是动作不同. wait(); 在其他线程调用此对象的notif ...

  4. 黑马程序员----java基础:多线程

    ------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- ------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ---- ...

  5. 黑马程序员----java基础笔记中(毕向东)

    <p>------<a href="http://www.itheima.com" target="blank">Java培训.Andr ...

  6. 黑马程序员Java基础班+就业班课程笔记全发布(持续更新)

    正在黑马学习,整理了一些课程知识点和比较重要的内容分享给大家,也是给自己拓宽一些视野,仅供大家交流学习,大家有什么更好的内容可以发给我 ,现有黑马教程2000G  QQ 1481135711 这是我总 ...

  7. 黑马程序员----java基础笔记上(毕向东)

    ------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 笔记一共记录了毕向东的java基础的25天课程,分上.中.下 本片为上篇,涵盖前10天课程 1. ...

  8. 黑马程序员——JAVA基础之泛型和通配符

    ------- android培训.java培训.期待与您交流! ---------- 泛型:            JDK1.5版本以后出现新特性.用于解决安全问题,是一个类型安全机制. 泛型好处: ...

  9. 黑马程序员——JAVA基础之简述面向对象,类,变量,匿名对象

    ------- android培训.java培训.期待与您交流! ---------- 面向对象: 面向对象是相对面向过程而言 面向对象和面向过程都是一种思想 面向过程 强调的是功能行为 面向对象 将 ...

  10. 黑马程序员——JAVA基础之语法、命名规则

    ------- android培训.java培训.期待与您交流! ---------- 1.java语言组成:关键字,标识符,注释,常量和变量,运算符,语句,函数,数组. 2.java关键字:被Jav ...

随机推荐

  1. Qt之加载QSS文件

    简述 Qt中关于样式的使用很常见,为了降低耦合性(与逻辑代码分离),我们通常会定义一个QSS文件,然后编写各种部件(例如:QLable.QLineEdit.QPushButton)的样式,最后使用QA ...

  2. C# winform程序怎么打包成安装项目(图解)

    1:新建安装部署项目 打开VS,点击新建项目,选择:其他项目类型->安装与部署->安装向导(安装项目也一样),然后点击确定.(详细见下图) 此主题相关图片如下: 2:安装向导 关闭后打开安 ...

  3. hdu------(3549)Flow Problem(最大流(水体))

    Flow Problem Time Limit: 5000/5000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Tota ...

  4. ARM流水线关键技术分析与代码优化

    引 言    流水线技术通 过多个功能部件并行工作来缩短程序执行时间,提高处理器核的效率和吞吐率,从而成为微处理器设计中最为重要的技术之一.ARM7处理器核使用了典型三级流 水线的冯·诺伊曼结构,AR ...

  5. wait(), notify(),sleep详解

    在JAVA中,是没有类似于PV操作.进程互斥等相关的方法的.JAVA的进程同步是通过synchronized()来实现的,需要说明的是,JAVA的synchronized()方法类似于操作系统概念中的 ...

  6. 20145236 《Java程序设计》实验一实验报告

    北京电子科技学院(BESTI)实验报告 课程:Java程序设计 班级:1452 指导教师:娄嘉鹏 实验日期:2016.04.08 实验名称:Java开发环境的熟悉(Linux + Eclipse) 实 ...

  7. java--常用类summary(三)

    /* 1:正则表达式(理解) (1)就是符合一定规则的字符串 (2)常见规则 A:字符 x 字符 x.举例:'a'表示字符a \\ 反斜线字符. \n 新行(换行)符 ('\u000A') \r 回车 ...

  8. BZOJ3058 四叶草魔杖

    Poetize11的T3 蒟蒻非常欢脱的写完了费用流,发现...边的cost竟然只算一次!!! 然后就跪了... Orz题解:"类型:Floyd传递闭包+最小生成树+状态压缩动态规划首先Fl ...

  9. linux 系统安装 mysql

    安装mysql所需要的依赖环境 yum -y install gcc gcc-c++ gcc-g77 autoconf automake zlib*  libxml* ncurses-devel li ...

  10. php的两个符号@和&---php总会要知道的系列

    在写代码的时候,碰到了在函数和变量前家 @和$的的问题,于是就借这个机会,学习下php的传值和传引用这两种方式 首先 @ 运算符只对表达式有效.对新手来说一个简单的规则就是:如果能从某处得到值,就能在 ...