Java基础温习6

多线程

并发性(concurrency)和并行性(parallel)是两个概念,并行指在同一时刻,有多条指令在多个处理器上同时执行;并发指在同一时刻只能有一条指令执行,但多个进程指令快速轮换执行,使得在宏观上具有多个进程同时执行的结果。
多进程的并发在不同硬件和操作系统下采用不同的策略,必将常见的有1,公用式的多任务操作策略,例如Windows3.1和Mac OS 9;2,目前使用效率更高的抢占式多任务操作策略,例如 WindowsNT、Windows2000以及UNIX等操作系统;

线程是独立运行的。线程的执行是抢占式的,也就是说当前的线程在任何时候都可能被挂起,以便另外一个线程可以运行。

线程的创建和启动

Java使用Thread类代表线程类,所有的线程对象都必须是Thread类或其子类的实例。

继承Thread类创建线程类

  1. 定义Thread类的子类,并重写run()方法,方法体代表了线程需要完成的任务,因此也称为线程执行体。
  2. 创建子类的实例
  3. 调用线程对象的start()方法启动线程。
public class FirstThread extends Thread{
	private int i;
	@Override
	public void run() {
		for(;i < 100 ; i ++) {
			System.out.println(getName() + " " + i);
		}
	}
	public static void main(String[] args) {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + " " + i);
			if(i == 20) {
				new FirstThread().start();
				new FirstThread().start();
			}
		}
	}
}

上面的运行的结果有三个线程:其中默认线程是main线程

Thread:

  • static Thread currentThread();该方法总是返回当前正在执行的线程对象。
  • getName():该方法时Thread类的实例方法,该方法返回调用该方法的线程名字。
  • setName(String name);

使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。因为每次new的是有实例变量会被重置;

实现Runnable接口创建线程类

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体是线程的执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。也能在常见Thread对象时为Thread对象指定一个名字。
  3. 调用线程对象的start()方法来启动该线程。
    new(Runnable target);
    new(Runnable target,String name);

Runnable 接口中只包含一个抽象方法,从Java8开始,Runnable接口使用了@FunctionInterface修饰,也就是说,Runnable接口是函数式接口,可使用Lambda表达式创建Runnable对象。接下来加上的Callable接口也是函数式接口。

用Runnable接口的实现类作为target的Thread中,target中的实例变量是可以共享的,因为target只new了一次。

使用Callable和Future创建线程

从Java5开始,Java提供了Callable接口,该接口怎么看都是Runnable接口的增强版。提供了一个call()方法,可以作为线程执行体,但call()方法比run()方法功能更强大。

  1. call()方法可以由返回值。
  2. call()方法可以声明抛出异常。

因此完全可以提供一个Callable对象作为Thread的target,但Callable不是Runnable接口的子接口,因此不能作为Thread的target;
Java5提供了Future接口来代表Callable接口里call()方法的返回值,并未Future接口提供了FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口,可以作为Thread的target。

在Future接口里定义了如下几个公共方法来控制它关联的Callable任务。

  • boolean cancel(boolean mayInterruptIfRunning):试图取消该Future里关联的Callable任务。
  • V get():返回Callable任务里call()方法的返回值。动用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值。
  • V get(long timeout,TimeUnit unit):返回Callable任务call()方法的返回值。该方法让程序最多阻塞timeout和unit指定的时间,如果经过指定时间后Callable任务依然没有返回值,将会抛出TimeoutException异常。
  • boolean isCancelled();如果Callable任务正常完成前被取消,则返回true。
  • boolean isDone():如果Callable任务已完成,则返回true。

Callable接口中的call方法允许声明抛出异常,而且允许带返回值。

创建线程的三种方式对比

通过集成Thread类或者实现Runnable,Callable接口都可以实现多线程,不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常而已。

通常使用Runnable和Callable接口来实现线程;

线程的生命周期

线程生命周期的5中状态:新建(new)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)。线程启动时,它不可能一直霸占着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也就会在运行、阻塞之间切换。
new后,线程处于新建状态;
start()调用后,线程处于就绪状态。至于什么时候开始,取决于JVM里线程调度器的调度。

线程的调用要使用start()方法,使用run()方法时,仅仅作为一个多项的run方法调用,并没有启动线程。

运行和阻塞状态

线程在运行过程中要被中断,目的是使其他线程获得执行的机会。在抢占式策略的系统上,系统会给每个线程一个小时间段来处理任务,在选择下一个线程时,系统或考虑程序的优先级。
一些小型设备如手机可能采用协作是调度策略,只有调用它的sleep()或yield()方法,才会放弃所占用的资源。
导致阻塞的情况:

  1. sleep()
  2. IO阻塞
  3. 试图获取同步监视器,但监视器被其他线程占用。
  4. 等待某个通知notify();
  5. 使用suspend()方法将该线程挂起,但这个方法容器导致死锁,所以尽量避免使用该方法

恢复后是就绪状态,而不是运行;
yield()方法可以让运行状态的线程转入就绪状态。

线程死亡

线程会以如下三种方式结束:

  1. run()或call()方法执行结束,线程正常结束
  2. 线程抛出一个未捕获的Exception或Error;
  3. 直接调用线程的stop()方法结束该线程–该方法容易导致死锁,通常不建议使用。

测试线程是否死亡:isAlive()方法,当线程处于就绪、运行、阻塞三种状态是,该方法将返回true;当处于新建、死亡两种状态时,返回false。

不要试图对一个已经死亡的线程调用start()方法使它重新启动。死亡的线程不可再次作为线程执行。调用后会报IllegalThreadStateException异常。

控制线程

1,join线程
线程等待另外一个线程完成的方法–join()方法。调用者执行,当前线程阻塞,直至调用者执行完毕。
join()方法有如下三种重载形式:

  1. join():等待被join的线程执行完毕
  2. join(long millis):等待join的线程时间最长是long毫秒,如果还没有执行完毕,将不再等待。
  3. join(long millis,int nanos);
public class JoinThread extends Thread{
	public JoinThread() {
	}
	
	public JoinThread(String name) {
		super(name);
	}
	int i;
	@Override
	public void run() {
		for(; i < 100 ; i ++) {
			System.out.println(getName() + "循环的值i = " + i);
		}
	}
	public static void main(String[] args) {
		new JoinThread("NotJoinThread").start();
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + "循环的值 i = " + i);
			
			if(i == 20) {
				JoinThread join = new JoinThread("JoinThread");
				join.start();
				try {
					join.join(); //直到join线程执行完毕,main线程才结束阻塞
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}

后台线程

有一种线程,它是后台运行的,它的任务是为其他的线程提供服务,这种线程被称为“后台线程(Daemon Thead)”,又称为“守护线程”或“精灵线程”。JVM的垃圾回收线程就是典型的后台线程。
特征:前台线程全部死亡后,后台线程会自动死亡。
调用Thread对象的setDaemon(true)方法可将指定线程设置成后台线程。
前台线程全部运行结束后,JVM会主动退出。

public class DaemonThread extends Thread{
	int i;
	@Override
	public void run() {
		for(;i < 999; i++) {
			System.out.println(getName() + "i = " + i);
		}
	}
	public static void main(String[] args) {
		DaemonThread daemon = new DaemonThread();
		daemon.setDaemon(true);
		daemon.start();
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "i = " + i);
		}
	}
}
	

显示的结果是前台线程结束后,后台线程没有结束JVM就退出了,但前台线程结束时并不是立即退出。
判断线程是否是后台线程:isDaemon();
上面的线程默认是前台的,需要修改为后台的。前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。
前台线程死亡后,JVM会通知后台线程死亡,但从它接受指令到作出相应,需要一定的时间。而且要将默认线程设置为后台线程,必须在该线程启动之前设置,也就是说setDaemon(true)必须在start()方法之前调用,否则会引发IllegalThreadStateException异常。

线程睡眠

用于线程暂停一段时间,并进入阻塞状态
Thread的sleep方法有两种重载形式。

  1. static void sleep(long millis)
  2. static void sleep(long millis,int nanos);

很少用到第二种,因为大部分系统无法精确到nanos;

public class SleepThread extends Thread{
	int i;
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("i = " + i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) {
		new SleepThread().start();
	}
}

线程让步:yield

yield()方法是一个和sleep()方法有点相似。它也是Thread类提供的一个静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,只是将该线程转入就绪状态,让当前线程暂停一下,让系统的线程调度器重新调度一次,即重新开始竞争CPU。完全有可能的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。
实际上,当某个线程调用了yield()方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会。

public class YieldTest extends Thread{
	public YieldTest(String threadName) {
		super(threadName);
	}
	public YieldTest() {
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 50; i++) {
			System.out.println(getName() + " " + i);
			if(i == 20) {
				Thread.yield();
			}
		}
	}
	public static void main(String[] args) {
		YieldTest yt1 = new YieldTest("高级");
		yt1.setPriority(Thread.MAX_PRIORITY);
		yt1.start();
		YieldTest yt2 = new YieldTest("低级");
		yt2.start();
	}
	
}

改变线程优先级

每个线程执行时,都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级的线程则获得较少的执行机会。
默认的优先级都与创建它的父线程的优先级相同,默认情况下,main线程具有普通优先级。
Thread类提供了setPriority(int newPriority);getPriority()方法来设置和返回指定线程的优先级。
优先级的范围是1-10,也可以使用Thread类的如下三个静态常量

  • MAX_PRIORITY 10
  • MIN_PRIORITY 1
  • NORM_PRIORITY 5

高优先级的线程将会获得更多的执行机会。
java提供了10个优先级别,但这些优先级别需要操作系统的支持。但不同的操作系统优先级并不相同,而且不能很好地和Java的10个优先级对应,因此应该尽量避免直接使用数值指定优先级,而应使用提供的静态变量来设置优先级。

线程同步

同步代码块

为了解决安全同步性,Java的多线程支持引入了同步监视来解决这个问题,使用同步监视器的通用方法就是同步代码块:

	synchronized(obj){
		...
	}

obj就是同步监视器,线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。同一时间只有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对同步监视器的锁定。
虽然Java程序允许使用任何对象作为同步监视器,但想一下同步监视器的目的:阻止两个线程对同一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器。

public class Account {
	
	private String accountNo;
	private double balance;
	
	
	public Account(String accountNo, double balance) {
		super();
		this.accountNo = accountNo;
		this.balance = balance;
	}
	public String getAccountNo() {
		return accountNo;
	}
	public void setAccountNo(String accountNo) {
		this.accountNo = accountNo;
	}
	public double getBalance() {
		return balance;
	}
	public void setBalance(double balance) {
		this.balance = balance;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		long temp;
		temp = Double.doubleToLongBits(balance);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Account other = (Account) obj;
		if (Double.doubleToLongBits(balance) != Double.doubleToLongBits(other.balance))
			return false;
		return true;
	}
}
-------------------------------------------------------------------------------------------
public class DrawThread extends Thread {

	private Account account;
	
	private double drawAmount;

	public DrawThread(String name,Account account, double drawAmount) {
		super(name);
		this.account = account;
		this.drawAmount = drawAmount;
	}
	
	@Override
	public void run() {
		
		synchronized (account) {
			if(account.getBalance() >= drawAmount) {
				System.out.println( getName() + "取钱成功!吐出钞票:" + drawAmount);
				
				
				try { Thread.sleep(1); } catch (Exception e) { e.printStackTrace(); }
				
				account.setBalance(account.getBalance() - drawAmount);
				System.out.println("\t 余额为:" + account.getBalance());
			} else {
				System.out.println(getName() + "取钱失败,余额不足");
			}
			
		}
	}
	
	public static void main(String[] args) {
		Account ac = new Account("1234567", 1000);
		new DrawThread("甲", ac, 800).start();
		new DrawThread("乙", ac, 800).start();
		
	}
省略geter,setter

同步方法

与同步代码块对应,Java的多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字来修饰某个方法,则该方法称为同步方法。
对于synchronized修饰的实例方法而言,无须显式指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。

线程安全的类有如下特征:

  1. 该类的对象可以被多个线程安全地访问。
  2. 每个线程调用对象的任意方法之后都将得到正确的结果
  3. 每个线程调用对象的任意方法之后,该对象状态依然保存合理状态

不可变类总是线程安全的,因为它的对象状态不可改变;但可变对象需要额外的方法来保证线程安全。上面的Account就是一个可变的类,它的accountNo和balance两个成员变量都可以被改变,当两个线程同时修改Account对象的balance成员变量的值时,程序就出现了异常。下面将Account类对balance的访问设置成安全的,那么只要把修改变量的方法变成同步方法。

public class Account {
	private String accountNo;
	private double balance;
	public Account(String accountNo, double balance) {
		super();
		this.accountNo = accountNo;
		this.balance = balance;
	}
	public String getAccountNo() {
		return accountNo;
	}
	//因为账号不用修改,所以保留的该方法
	public void setAccountNo(String accountNo) {
		this.accountNo = accountNo;
	}
	public double getBalance() {
		return balance;
	}
	//balance需要修改,因此不再使用普通的set方法,而使用如下的同步方法
	public synchronized void draw(double drawAmount) {
		if(balance >= drawAmount) {
			System.out.println(Thread.currentThread().getName()
					+ "取钱成功,吐出钞票 :" + drawAmount);
			try {
				Thread.sleep(1);
			} catch (Exception e) {
				e.printStackTrace();
			}
			balance -= drawAmount;
			System.out.println("\t 余额为:" + balance);
		
		} else {
			System.out.println(Thread.currentThread().getName() 
					+ "取钱失败!余额不足");
		}
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		long temp;
		temp = Double.doubleToLongBits(balance);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Account other = (Account) obj;
		if (Double.doubleToLongBits(balance) != Double.doubleToLongBits(other.balance))
			return false;
		return true;
	}
}
-------------------------------------------
public class DrawThread extends Thread {

	private Account account;
	
	private double drawAmount;

	public DrawThread(String name,Account account, double drawAmount) {
		super(name);
		this.account = account;
		this.drawAmount = drawAmount;
	}
	@Override
	public void run() {
		
		account.draw(drawAmount);
	}
	public static void main(String[] args) {
		Account ac = new Account("1234567", 1000);
		new DrawThread("甲", ac, 800).start();
		new DrawThread("乙", ac, 800).start();
		
	}
	getter,setter
}

synchronized关键字可以修饰方法,可以修饰代码块,但不能修饰构造器、成员变量等。

可变类的线程安全是以降低运行效率作为代价的,为了减少负面影响,应该采取下面的策略:

  • 不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源的方法进行同步;
  • 如果可变类有两种运行环境:单线程和和多线程环境,则应该为可变类提供两种版本。即线程安全和线程不安全版本。

释放同步监视器的锁定

线程会在如下几种情况下释放对同步监视器的锁定:

  1. 同步方法、同步代码块执行完毕
  2. 在同步方法、同步方法中遇到break、return终止了该代码块、该方法的继续执行
  3. 在同步方法、同步方法中出现了Error或Exception,导致该代码块、该方法异常结束
  4. 在同步方法、同步方法中执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器。

在如下所示的情况下不会释放同步监视器;

  1. 调用Thread.sleep、Thread.yield
  2. 在执行同步方法、同步方法时,其他线程调用了该线程的suspend方法,将该线程挂起,该线程不会释放同步监视器。尽量避免使用suspend()和resume()方法来控制线程。

同步锁(LOCK)

Java5,可以显示定义同步锁(Lock),实现灵活,并支持多个相关的Condition对象。线程访问共享资源必须先获取Lock;
某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁),Lock和ReadWriteLock是Java5提供的两个根接口,并未Lock提供了ReentrantLock(可重入锁)实现类,为ReadWriteLock提供了ReentrantReadWriteLock实现类。
Java8新增了StampedLock类,在大多数场景下可以代替ReentrantLock和ReentrantReadWriteLock,为读写操作提供了三种模式:Writing、ReadingOptismistic、Reading。Optimistic:乐观。
在实现线程安全的控制中,比较常用的是ReentrantLock(可重入序锁)。使用该Lock对象可以显示地加锁、释放锁,格式如下:

class X {
	//定义锁对象
	private final ReentrantLock lock = new ReentrantLock();
	public void m() {
		//加锁
		lock.lock();
		try{
			//需要保证线程安全的代码	
		}
		finally {
			lock.unlock();}
}

改造Account为

package com.cheng.thread.sych;

import java.util.concurrent.locks.ReentrantLock;

public class Account {

	private static ReentrantLock lock = new ReentrantLock(); 	
	private String accountNo;
	private double balance;

	public Account(String accountNo, double balance) {
		super();
		this.accountNo = accountNo;
		this.balance = balance;
	}
	public String getAccountNo() {
		return accountNo;
	}
	//因为账号不用修改,所以保留的该方法
	public void setAccountNo(String accountNo) {
		this.accountNo = accountNo;
	}
	public double getBalance() {
		return balance;
	}
	//balance需要修改,因此不再使用普通的set方法,而使用如下的同步方法
	public void draw(double drawAmount) {
		lock.lock();
		try {
			if(balance >= drawAmount) {
				System.out.println(Thread.currentThread().getName()
						+ "取钱成功,吐出钞票 :" + drawAmount);
				try {
					Thread.sleep(1);
				} catch (Exception e) {
					e.printStackTrace();
				}
				balance -= drawAmount;
				System.out.println("\t 余额为:" + balance);
				
			} else {
				System.out.println(Thread.currentThread().getName() 
						+ "取钱失败!余额不足");
			}
		} finally {
			lock.unlock();
		}
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		long temp;
		temp = Double.doubleToLongBits(balance);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Account other = (Account) obj;
		if (Double.doubleToLongBits(balance) != Double.doubleToLongBits(other.balance))
			return false;
		return true;
	}
}

死锁

当两个线程相互等待对方释放同步监视器时就会发生死锁,出现死锁时,线程出入阻塞状态,无法继续。

public class A {
	public synchronized void foo(B b) {
		System.out.println("当前线程名:" + Thread.currentThread().getName()
				
				+ "进入了A实例的foo()方法");//1
		try {
			Thread.sleep(200);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		System.out.println("当前线程名:" + Thread.currentThread().getName()
				+ "企图调用B实例的last()方法");
		b.last();
	}
	public synchronized void last() {
		System.out.println("进入A类的last()方法内部");
	}
}
-----------------------------------
public class B {
	public synchronized void bar(A a) {
		System.out.println("当前线程名:" + Thread.currentThread().getName()

				+ "进入了B实例的foo()方法");// 2
		try {
			Thread.sleep(200);
		} catch (Exception e) {
			e.printStackTrace();
		}

		System.out.println("当前线程名:" + Thread.currentThread().getName() + "企图调用A实例的last()方法");
		a.last();
	}

	public synchronized void last() {
		System.out.println("进入B类的last()方法内部");
	}
}
-------------------------------------
public class DeadLock implements Runnable {
	A a = new A();
	B b = new B();
	public void init() {
		//此方法仅仅是main线程中的执行代码
		Thread.currentThread().setName("主线程");
		a.foo(b);
		System.out.println("进入了主线程之后");
	}
	@Override
	public void run() {
		Thread.currentThread().setName("副线程");
		b.bar(a);
		System.out.println("进入了副线程后");
	}
	public static void main(String[] args) {
		DeadLock d = new DeadLock();
		Thread thread = new Thread(d);
		thread.start();
		d.init();
	}

}

最后main线程(主线程)和副线程都没有释放锁(主线程的锁是a对象,副线程的锁是b对象),当都试图去获取对方的锁,此时出现了死锁。

由于Thread类的suspend()方法也很容易导致死锁,所以Java不再推荐使用该方法。

线程通信

传统的线程通信

借助于Object类提供的wait(),notify(),notifyAll()三个方法,这三个方法必须由同步监视器对象来调用。

  • 使用synchronized修饰的同步方法,this就是同步监视器。
  • 对于synchronized修饰的同步代码块,括号里边的对象就是同步监视器。

wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程。该方法有三种重载:
无时间参数,一直等待,直到其他线程的通知;带有毫秒的方法和带毫秒,毫微秒参数的方法:等待指定时间后自动苏醒。调用wait()方法的当前线程会释放对同步监视器的锁定。

notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则只会唤醒其中的一个线程。选择是任意的。只有当前线程放弃对该同步监视器的锁定后(使用wait()方法),才可以执行被唤醒的线程。

notifyAll():唤醒在此同步监视器上等待的所有线程。

package com.cheng.thread.sych;

public class Account1 {
	
	private String accountNo;
	private double balance;
	
	private boolean flag = false;
	
	public Account1() {
	}
	public Account1(String accountNo, double balance) {
		super();
		this.accountNo = accountNo;
		this.balance = balance;
	}
	
	public boolean isFlag() {
		return flag;
	}
	public void setFlag(boolean flag) {
		this.flag = flag;
	}
	public String getAccountNo() {
		return accountNo;
	}
	//因为账号不用修改,所以保留的该方法
	public void setAccountNo(String accountNo) {
		this.accountNo = accountNo;
	}
	public double getBalance() {
		return balance;
	}
	//balance需要修改,因此不再使用普通的set方法,而使用如下的同步方法
	public synchronized void draw(double drawAmount) {
		try {
			if(!flag) {
				wait();
			}
			else {
				System.out.println(Thread.currentThread().getName()
						+ "取钱:" + drawAmount);
				balance -= drawAmount;
				System.out.println("账户余额为:" + balance);
				flag = false;
				notifyAll();
			}
			
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			
		}
	}
	
	public synchronized void deposit(double depositAmount) {
		try {
			if(flag) {
				wait();
			}
			else {
				System.out.println(Thread.currentThread().getName() 
						+ "存款:" + depositAmount);
				balance += depositAmount;
				System.out.println("账号余额为:" + balance);
				flag = true;
				notifyAll();
			}
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		long temp;
		temp = Double.doubleToLongBits(balance);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Account1 other = (Account1) obj;
		if (Double.doubleToLongBits(balance) != Double.doubleToLongBits(other.balance))
			return false;
		return true;
	}
}
----------------------------------------------------
public class DrawThread1 extends Thread{

	private Account1 account;
	
	private double drawAmount;
	
	public DrawThread1(String name,Account1 account,double drawAmount) {
		super(name);
		this.account = account;
		this.drawAmount = drawAmount;
	}
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			account.draw(drawAmount);
		}
	}
}
-----------------------------------------------------
public class DepositThread extends Thread{
	private Account1 account;
	private double depositAmount;
	public DepositThread(String name,Account1 account,double depositAmount) {
		super(name);
		this.account = account;
		this.depositAmount = depositAmount;
	}
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			account.deposit(depositAmount);
		}
	}
	public static void main(String[] args) {
		Account1 a = new Account1("12345",0);
		new DrawThread1("取钱者",a , 800).start();
		new DepositThread("存款者甲", a, 800).start();
		new DepositThread("存款者乙", a, 800).start();
		new DepositThread("存款者丙", a, 800).start();
		
	}
}

使用Condition控制线程通讯

如果程序中不使用synchronized关键字来保证同步,而是直接使用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也就不能使用wait(),notify(),notifyAll()方法进行线程通信了。
使用Lock对象来保证同步时,Java提供了一个Condition类来保持协调。
Condition将同步监视器方法(wait(),notify()和notifyAll())分解成截然不同的对象,和Lock结合,Lock替代了同步方法或代码块,Condition代替了同步监视器的功能。

Condition被绑定在一个Lock对象上。调用Lock对象的newCondition()方法即可。
Condition类提供了如下三个方法:

  • await():有丰富的多变体,long awaitNanos(long Timeout),void awaitUninterruptibly(),awaitUntil(Date dadlie)
  • signal():
  • signalAll():

修改上面类:

public class Account {
	private final Lock lock = new ReetrantLock();
	
	private final Condition cond = lock.newCondition();

	run(){
		lock.lock()
		try{
			cond.await();
			...
			cond.singalAll();
		}
		finally{
			lock.unlock;
		}}

使用阻塞队列(BlockingQueue)控制线程通信

是Queue的子接口,但用途不是作为容器,而是作为线程同步的工具。
BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。
程序的两个线程通过交替向BlockingQueue中放入元素、取出元素,即很好地控制线程的通信。

BlockingQueue提供如下两个支持阻塞的方法:

  1. put(E e):尝试把E元素放入到BlockingQueue中,如果该队列元素已经满,则阻塞该线程。
  2. take():尝试从BlockingQueue的头部取出元素,如果该队列的元素已经空了,则阻塞该线程。

BlockingQueue继承Queue接口,因此也有Queue的方法
在队列尾部插入元素。包括add(E e),offer(E e)和put(E e),当队列已满时,分别抛出异常,返回false,阻塞队列。
对应的有remove(),poll()和take();
取出但不删除元素element(),peek(),当队列空时,分别抛出异常,返回false;

Java7新增的阻塞队列有:(都是BlockingQueue的实现类):
ArrayBlockingQueue:基于数组
LinkedBlockingQueue:基于链表
PriorityBlockingQueue:不是标准的队列,里边的元素已经排好顺序。
SynchronousQueue: 同步队列,对该队列的存取操作必须交替进行。
DelayQueue:特殊的BlockingQueue,底层基于PriorityBlockingQueue实现,不过要求每个元素时间Delay接口(有long getDelay方法),根据里边的方法来排序。

线程组和未处理的异常。。

ThreadGroup
Thread.UncaughtExceptionHandler

线程池

java增强的线程池

Executors工厂类

ExecutorService
ScheduledExecutorService

java8增强的ForkJoinPool

ForkJoinPool();
ForkJoinPool(int parallelism);
ForkJoinPool static commonPool(); 不会受shutdown影响
int getCommonPoolParallelism():该方法返回通用词的并行级别

submit(ForkJoinTask task)
invoke(ForkJoinTask task)
…ForkJoinTask
…RecursiveAction
…RecursiveTask

线程相关类

ThreadLocal类

线程局部变量,每个线程使用得到的值不一样,不会干扰
使用这个工具类可以很简捷地隔离多线程程序的竞争资源。
T get();
void remove();
void set(T value);

包装线程不安全的集合

调用Collections.synchronized…();

线程安全的集合类

java.util.concurrent包下
以Concurrent开头的
ConcurrentLinkedQueue(比较合适)
ConcurrentSkipListMap
ConcurrentSkipListSet
ConcurrentLinkedQueue
ConcurrentLinkedDeque
ConcurrentHashMap

以CopyOnWrite开头的集合类:
CopyOnWriteArraylist
CopyOnWriteArraySet

#网络编程

Java的基本网络支持

Java提供了InetAddress来代表IP地址,有两个子类:Inet4Address(IPV4)和Inet6Address(IPV6)

InetAddress类没有构造器,而是提供了两个静态方法获得InetAdress实例:

  • getByName(String host);根据主机获取对应的InetAddress对象。
  • getByAddress(byte[] addr);根据原始IP地址来获取对应的InetAddress;
  • getLocalHost();

InetAddress的方法:

  1. String getCanonicalHostName();获取此IP地址的全限定域名。
  2. String getHostAddress():返回该InetAddress实例对应IP地址地址字符串;
  3. String getHostName():获取此IP地址的主机名。
  4. boolean isReachable():测试是否可以到达该地址
	public static void main(String[] args) throws IOException {
		InetAddress ip = InetAddress.getByName("www.crazyit.org");
		System.out.println("crazyit 是否可达" + ip.isReachable(2000));
		System.out.println(ip.getCanonicalHostName());//43.243.169.21
		System.out.println(ip.getHostAddress());//43.243.169.21
		System.out.println(ip.getHostName());//www.crazyit.org
	}

使用URLDecoder和URLEncoder

URLDecoder和URLEncoder用于完成普通字符串和application/x-www-form-urlencoded MIME字符串之前的相互转换。
当URL地址中非西欧字符的字符串时,就会转换为特殊的字符串。
URLDecoder类包含了一个decode(String s,String enc)的静态方法,可以将乱码转为正常的字符串。
URLEncoder类包含了一个encode(String s,String enc)将普通字符转换为application/x-www-form-urlencoded MIME字符串。

	public static void main(String[] args) throws UnsupportedEncodingException {
		String encode = URLEncoder.encode("我在北京", "utf-8");
		System.out.println(encode);//%E6%88%91%E5%9C%A8%E5%8C%97%E4%BA%AC
		System.out.println(URLDecoder.decode(encode, "utf-8"));
	}

URL,URLConnection和URLPermission

URL:同一资源定位器,资源可以是简单的文件或目录,也可以是更为复杂的对象的引用。

URI:同一资源标识符,它的唯一作用就是解析。

URL类提供了多个构造器用于构建URL对象,一旦获得了URL对象之后,就可以调用方法:

  • String getFile(): 获取该URL的资源名
  • String getHost(): 获取该URL的主机名
  • String getpath(): 获取该URL的路径部分
  • int getPort(): 获取该URL的端口
  • String getProtocol(): 获取该URL的协议名称
  • String getQuery(): 获取该URL的查询字符串部分
  • URLConnection openConnection(): 返回一个URLConnectint对象,代表了URl所引用的远程对象的连接。
  • InputString openStream(): 打开与URL的连接,并返回一个用于读取该URL资源的InputStream。

URLConnection

基于TCP协议的网络编程

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值