您的位置:首页 > 职场人生

黑马程序员 java学习笔记——多线程1

2014-08-01 12:23 579 查看

---------------------- ASP.Net+Android+IO开发S.Net培训、期待与您交流!
----------------------

多线程概述

进程

进程是一个正在执行的程序。

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

线程

线程就是进程中的一个独立的控制单元。

线程在控制着进程的执行

一个进程中至少有一个线程。

如JVM启动的时候会有一个进城java.exe,该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。

其实,JVM启动的时候不止一个线程,还有负责垃圾回收机制的线程也在同时运行。

线程的创建

如何在自定义的代码中,自定义一个线程呢?

通过API的查找发现,java已经提供了对线程这类事物的描述,就是Thread类。

创建线程的第一种方式:

继承Thread类,并复写其中的run()方法。

步骤:

1、定义类继承Tread类

2、复写run()方法

目的:将自定义代码存储在run方法中,让线程运行

3、调用线程的start方法

该方法有两个作用:启动线程,调用run()方法

示例代码如下:

class Demo6 extends Thread{
private static int tickets=100;
public void run(){
while(true){
if(tickets>0)
System.out.println(currentThread().getName()+"......."+(tickets--));
else
break;
}
}
}
public class Thread1 {
public static void main(String[] args){
Demo6 demo1=new Demo6();
Demo6 demo2=new Demo6();
Demo6 demo3=new Demo6();
demo1.start();
demo2.start();
demo3.start();
}
}


结果分析:

从运行结果发现每一次都不同。

这也是多线程的一个特性:随机性

因为每个线程都需要先获取CPU的执行权,CPU执行到谁,谁就执行。更明确地说,在某一时刻,CPU只能运行一个程序(多核除外)。之所以看起来像是同时运行,那是因为CPU在做着快速切换,我们可以形象地把多线程的运行行为在互相抢夺cpu的执行权。

为什么要覆盖run方法呢?

Thread类用于描述线程。

该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。

也就是说想要同时运行某段代码,必须复写Thread类中的run方法。

线程创建的第二种方式:

实现Runnable接口

步骤:

1、定义类实现Runnable接口

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

3、通过Thread类建立线程对象

4、将runnable接口的子类对象作为实现参数传递给thread类的构造函数。

为什么要将Runnalb接口的子类对象传递给thread的构造函数?

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

5、调用Thread类的start方法开启线程并调用Runnable饥饿扩子类的run方法。

示例代码如下:

class Demo7 implements Runnable{
private int tickets=100;
public void run(){
while(true){
if(tickets>0)
System.out.println(Thread.currentThread().getName()+"......."+(tickets--));
else
break;
}
}
}
public class Thread2 {
public static void main(String[] args){
Demo7 demo=new Demo7();
new Thread(demo).start();
new Thread(demo).start();
new Thread(demo).start();
}
}


Thread中的方法

public static Thread currentThread()

返回对当前正在执行的线程对象的引用

public static void yield()

暂停当前正在执行的线程对象,并执行其他线程

public static void sleep(long millis) throws InterruptedException

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。

public static void sleep(long millis,int nanos) throws InterruptedException

在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。

public void start()

使该线程开始执行;Java 虚拟机调用该线程的 run 方法。

结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。

多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。

public void run()

如果该线程是使用的Runnable运行对象构造的,则调用该Runnable对象的run方法;否则,该方法不执行任何操作并返回。

Thread的子类应该重写该方法。

public void interrupt()

中断线程

public final void setPriority(int newPriority)

更改线程的优先级。

所有线程的默认优先级都为5,范围1—10。

Thread类中有三个字段,分别对应最高、最低和正常优先级

MAX_PRIORITY MIN_PRIORITY NORM_PRIORITY

public final int getPriority()

返回线程的优先级。

public final void setName(String name)

改变线程名称,使之与参数 name 相同。

public final String getName()

返回该线程的名称

public final void join() throws InterruptedException

等待该线程终止。

当A线程执行到了B线程的join方法时,A就会等待,等B线程执行完毕后,A才会执行。

代码示例如下:

class Demo3 implements Runnable{
public void run(){
for(int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+"  "+i);
}
}
}
public class JoinDemo {
public static void main(String[] args){
Demo3 demo=new Demo3();
Thread t1=new Thread(demo);
Thread t2=new Thread(demo);
t1.start();
t2.start();
try{
t1.join();
}catch(InterruptedException e){
System.out.println(Thread.currentThread().getName()+" Exception");
}
for(int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+"  "+i);
}
}
}


public final void setDaemon(boolean on)

将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。

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

示例代码如下:

class Demo2 implements Runnable{
public synchronized void run(){
while(true){
System.out.println(Thread.currentThread().getName()+" Thread run");
}
}
}
public class ShouHu {
public static void main(String[] args){
Demo2 demo=new Demo2();
Thread t1=new Thread(demo);
Thread t2=new Thread(demo);
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
int num=0;
while(true){
if(num++==60)
break;
System.out.println(Thread.currentThread().getName()+"main run");
}
}
}


public String toString()

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

public static void yield()

暂停当前正在执行的线程对象,并执行其他线程

示例代码如下:

class Demo4 implements Runnable{
public void run(){
for(int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+"—"+i);
Thread.yield();
}
}
}
public class YieldDemo {
public static void main(String[] args){
Demo4 demo=new Demo4();
Thread t1=new Thread(demo);
Thread t2=new Thread(demo);
t1.start();
t2.start();
}
}


线程的运行状态

1、被创建

2、运行:调用start方法即可开启线程

3、冻结,放弃了执行资格

sleep(time):睡眠

wait():不唤醒就一直等待

notify():唤醒,与wait配合使用

4、消亡:当run()方法运行结束后,线程结束。在旧版本JDK中,还有stop方法也能结束。

5、临时状态(阻塞):具备运行状态,但没有执行权。



多线程中的安全问题

在此之前,有一个概念要明确,那就是时间片。

时间片是CPU分配给各个程序的时间,每个进程被分配一个时间段,这被称作它的时间片,即该进城允许运行的时间,使各个程序从表面上看是同时进行的。如果在时间片结束时还在运行,则CPU将被剥夺并分配给另一进程,如果进程在时间片结束前阻塞或结束,则CPU当即进行切换,而不会造成CPU资源浪费。

产生资源共享问题的原因,是由于线程分配的时间片长短不一样

示例代码如下:

class Demo8 implements Runnable{
private  int tickets=100;
public void run(){
while(true){
if(tickets>0){
try{
Thread.sleep(10);
}catch(InterruptedException e){
System.out.println(e);
}
System.out.println(Thread.currentThread().getName()+"......."+(tickets--));
}
}
}
}
public class Safe1 {
public static void main(String[] args){
Demo8 demo=new Demo8();
new Thread(demo).start();
new Thread(demo).start();
new Thread(demo).start();
}
}
//该程序执行后,有时会出现0和-1号票,这就是程序不安全造成的后果。


问题的原因

当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完,另一个线程参与进来执行,导致共享数据的错误。

解决办法

对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

java对于多线程的安全问题提供了专业的解决方式,那就是同步代码块。

Sychronized(对象){

需要被同步的代码

}

对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的线程即时获取了cpu的执行权也进不去,因为没有获取锁。

示例代码如下:

class Demo9 implements Runnable{
private int tickets=100;
Object obj=new Object();
public void run(){
while(true){
synchronized(obj){
if(tickets>0){
try{
Thread.sleep(10);
}catch(InterruptedException e){
System.out.println(e);
}
System.out.println(Thread.currentThread().getName()+"-------"+(tickets--));
}
}
}
}
}
public class Safe2 {
public static void main(String[] args){
Demo9 demo=new Demo9();
new Thread(demo).start();
new Thread(demo).start();
new Thread(demo).start();
}
}


同步的前提

1、必须要有两个或两个以上的线程。

2、必须是多个线程使用同一个锁

同步的好处与弊端

好处:解决多线程的安全问题

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

如何找出那些需要同步的代码?

1、明确哪些代码是多线程运行代码

2、明确共享数据

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

同步函数的锁是哪个?

函数需要被对象调用,那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁就是this。

下面程序证明同步函数的锁是this

class Ticket implements Runnable{
private int ticks=100;
Object obj=new Object();
public  boolean flag=true;
public void run(){
if(flag){
while(true){
synchronized(obj){//此处的锁如果是obj,就会出现0号票。
//如果写成this,就不会出现0号票,说明this就是show方法的锁
if(ticks>0){
try{
Thread.sleep(10);
}catch(InterruptedException e){
System.out.println(e);
}
System.out.println(Thread.currentThread().getName()+" code "+(ticks--));
}
}
}
}else{
while(true)
show();
}
}
public synchronized void show(){//如果在public后加static修饰,上面的obj处改成Ticket.class,才
//不会出现0号票,说明Ticket.class是该静态同步函数的锁。
if(ticks>0){
System.out.println(Thread.currentThread().getName()+" show "+(ticks--));
}
}
}
public class SynchronizedDemo2 {
public static void main(String[] args){
Ticket t=new Ticket();
new Thread(t).start();
try{
Thread.sleep(10);
}catch(InterruptedException e){
System.out.println(e);
}
t.flag=false;
new Thread(t).start();
}
}


上面的代码也验证了静态的同步方法,使用的锁是该方法所在类的字节码文件对象,类名.class。该对象的类型是Class

静态方法中不可以定义this,所以this肯定不是它的锁。由于静态进内存时,内存中没有本类对象。

懒汉式单例设计模式的安全问题解决方案

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;
}
}


同步的弊端:死锁

class DeathLockDemo implements Runnable{
boolean flag=true;
DeathLockDemo(boolean flag){
this.flag=flag;
}
public void run(){
while(true){
if(flag){
synchronized(Lock.locka){
System.out.println("Lock.locka   locka");
synchronized(Lock.lockb){
System.out.println("Lock.locka   lockb");
}
}
}else{
synchronized(Lock.lockb){
System.out.println("Lock.lockb  locka");
synchronized(Lock.locka){
System.out.println("Lock.lockb   lockb");
}
}
}
}
}
}
class Lock{
public static Lock locka=new Lock();
public static Lock lockb=new Lock();
}
public class DeathLock {
public static void main(String[] args){
new Thread(new DeathLockDemo(true)).start();
new Thread(new DeathLockDemo(false)).start();
}
}


---------------------- ASP.Net+Android+IO开发S.Net培训、期待与您交流!
----------------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: