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

---------------------- 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培训、期待与您交流! ----------------------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值