java多线程

可以使用 Runable 实现类来创建线程,可以共享一个 target 。 

不太常用继承Thread 类,虽然这样写稍微简单些。

可以使用 Callable 实现类来创建线程,可以接受并返回运行体的返回值,可以共享一个 target 。 

public class ThirdThread implements Callable
{
	// 实现call方法,作为线程执行体
	public Integer call()
	{
		int i = 0;
		for ( ; i < 100 ; i++ )
		{
			System.out.println(Thread.currentThread().getName()
				+ " 的循环变量i的值:" + i);
		}
		// call()方法可以有返回值
		return i;
	}

	public static void main(String[] args) 
	{
		// 创建Callable对象
		ThirdThread rt = new ThirdThread();
		// 使用FutureTask来包装Callable对象
		FutureTask task = new FutureTask(rt);
		for (int i = 0 ; i < 100 ; i++)
		{
			System.out.println(Thread.currentThread().getName()
				+ " 的循环变量i的值:" + i);
			if (i == 20)
			{
				// 实质还是以Callable对象来创建、并启动线程
				new Thread(task , "有返回值的线程").start();
			}
		}
		try
		{
			// 获取线程返回值
			System.out.println("子线程的返回值:" + task.get());
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}
}
suspend() 方法可以将线程挂起,由运行状态切换到阻塞状态。

resume() 方法可以使线程重新开始,即由阻塞的状态切换到就绪的状态。

注:已经死亡的线程不可重新开始。


Join 线程:一个线程中,有另一个线程调用join()方法,则原来的线程必须等到新加入的线程结束后才可执行。

还有限制等待时间的方法  join(long mills); 若是等了这么久还没有结束,那么就不等了。


若需要给线程设置为后台线程,而且一定要在start之前就设置,调用方法 setDaemon(true);  主线程死亡,则后台线程也会死亡。

前台线程创建的线程默认为前台线程,后台创建的线程默认为后台线程。


yield()方法会暂时让步给优先级更高的线程(设置线程优先级 setPriority(1~10) )由执行状态切换到就绪状态,若没有优先级更高的,相当于啥也没做。

这个不太常用,常用的是sleep()


同步代码块

synchronized(obj) { //里面写代码,表示给obj加锁,后执行此代码块 }

或者修饰方法,表示同一个对象,此方法不会同时执行。


同步锁

和同步方法有点类似,类中Field 定义锁对象 private final ReentrantLock lock = new ReentrantLock();  可重入锁的实现类,同一时间只能有一个对象可以进入临界区,ReentrantReadWriteLock 读写锁实现类。

                // 加锁
		lock.lock();
		try
		{
			// 账户余额大于取钱数目
			if (balance >= drawAmount)
			{
				// 吐出钞票
				System.out.println(Thread.currentThread().getName()
					+ "取钱成功!吐出钞票:" + drawAmount);
				try
				{
					Thread.sleep(1);
				}
				catch (InterruptedException ex)
				{
					ex.printStackTrace();
				}
				// 修改余额
				balance -= drawAmount;
				System.out.println("\t余额为: " + balance);
			}
			else
			{
				System.out.println(Thread.currentThread().getName()
					+ "取钱失败!余额不足!");
			}
		}
		finally
		{
			// 修改完成,必须释放锁
			lock.unlock();
		}		
一段被锁保护的代码可以调用另一个被相同锁保护的方法。

线程间通信:

传统间线程通信

1.wait()方法:当前线程等待,直到其它线程调用该同步监视器的 notify() 方法或 notifyAll() 来唤醒该线程。

2.notify()方法,唤醒一个正在等待的线程。

3.notifyAll()方法,唤醒全部正在等待的线程。

		try
		{
			// 如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞
			if (!flag)
			{
				wait();
			}
			else
			{
				// 执行取钱
				System.out.println(Thread.currentThread().getName() 
					+ " 取钱:" +  drawAmount);
				balance -= drawAmount;
				System.out.println("账户余额为:" + balance);
				// 将标识账户是否已有存款的旗标设为false。
				flag = false;
				// 唤醒其他线程
				notifyAll();
			}
		}
		catch (InterruptedException ex)
		{
			ex.printStackTrace();
		}

但若是不使用synchronized来保证同步,而直接用Lock对象,系统中不存在隐式的同步监听器,也就不能使用 wait() , notify() , notifyAll() 方法进行线程通信了。
若是使用Lock对象,则需要引入Condition,使用方法condition.await(), condition.singalAll();

	// 显式定义Lock对象
	private final Lock lock = new ReentrantLock();
	// 获得指定Lock对象对应的Condition
	private final Condition cond  = lock.newCondition(); 
		// 加锁
		lock.lock();
		try
		{
			// 如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞
			if (!flag)
			{
				cond.wait();
			}
			else
			{
				// 执行取钱
				System.out.println(Thread.currentThread().getName() 
					+ " 取钱:" +  drawAmount);
				balance -= drawAmount;
				System.out.println("账户余额为:" + balance);
				// 将标识账户是否已有存款的旗标设为false。
				flag = false;
				// 唤醒其他线程
				cond.signalAll();
			}
		}
		catch (InterruptedException ex)
		{
			ex.printStackTrace();
		}
		// 使用finally块来释放锁
		finally
		{
			lock.unlock();
		}
以BlockingQueue对象来实现线程间通信。

		// 定义一个长度为2的阻塞队列
		BlockingQueue bq = new ArrayBlockingQueue<>(2);
		bq.put("Java");//与bq.add("Java"、bq.offer("Java")相同
		bq.put("Java");//与bq.add("Java"、bq.offer("Java")相同
		bq.put("Java");//① 阻塞线程。
java多线程_第1张图片

线程组和未处理的异常

一些未处理的异常可以由这个线程所在的线程组来统一处理。

要构造一个属于某个线程组的线程,调用构造器:

Thread(ThreadGroup group, Runnable target) ;
Thread(ThreadGroup group, Runnable target, String name)  ;
获得一个线程的线程组,直接调用 getThreadGroup() 方法。

线程组类ThreadGroup

class MyExHandler implements Thread.UncaughtExceptionHandler 
{
	//实现uncaughtException方法,该方法将处理线程的未处理异常
	public void uncaughtException(Thread t, Throwable e)
	{
		System.out.println(t + " 线程出现了异常:" + e);
	} 
}
public class ExHandler
{
	public static void main(String[] args) 
	{
		// 设置主线程的异常处理器
		Thread.currentThread().setUncaughtExceptionHandler
			(new MyExHandler());
		int a = 5 / 0;     //①
		System.out.println("程序正常结束!");
	}
}

线程池:

public class ThreadPoolTest
{
	public static void main(String[]args)
	{
		ExecutorService pool =Executors.newFixedThreadPool(10);
		pool.submit(new MyThread());
		pool.submit(new MyThread());
		pool.submit(new MyThread());
		pool.shutdown();
	}
}
class MyThread implements Runnable
{
	int i=0;
	public void run()
	{
		for(;i<100;++i)
		{
			System.out.println(Thread.currentThread().getName()+" "+i);
		}
	}

}

ForkJoinPool 是为了更好地利用多核处理器资源而设计的多线程类。

class PrintTask extends RecursiveAction
{
	// 每个“小任务”只最多只打印50个数
	private static final int THRESHOLD = 50;
	private int start;
	private int end;
	// 打印从start到end的任务
	public PrintTask(int start, int end)
	{
		this.start = start;
		this.end = end;
	}
	@Override
	protected void compute() 
	{
		// 当end与start之间的差小于THRESHOLD时,开始打印
		if(end - start < THRESHOLD)
		{
			for (int i = start ; i < end ; i++ )
			{
				System.out.println(Thread.currentThread().getName()
					+ "的i值:" + i);
			}
		}
		else
		{
			// 如果当end与start之间的差大于THRESHOLD时,即要打印的数超过50个
			// 将大任务分解成两个小任务。
			int middle = (start + end) /2;
			PrintTask left = new PrintTask(start, middle);
			PrintTask right = new PrintTask(middle, end);
			// 并行执行两个“小任务”
			left.fork();
			right.fork();
		}
	}
}
public class ForkJoinPoolTest
{
	public static void main(String[] args) 
		throws Exception
	{
		ForkJoinPool pool = new ForkJoinPool();
		// 提交可分解的PrintTask任务
		pool.submit(new PrintTask(0 , 300));
		pool.awaitTermination(2, TimeUnit.SECONDS);
		// 关闭线程池
		pool.shutdown();
	}
}
如果想要返回运算结果,则继承:RecursiveAction 就好,然后覆盖父类的compute()时,用返回值Integer替换void。

线程相关类:ThreadLocal类,隔离多个线程的数据共享,每个线程都拥有一份资源。

它可以保留线程局部变量,即在不同的线程,就算是同一个对象,也会持有不同的值。

class Account
{
	/* 定义一个ThreadLocal类型的变量,该变量将是一个线程局部变量
	每个线程都会保留该变量的一个副本 */
	private ThreadLocal name = new ThreadLocal<>();
	// 定义一个初始化name属性的构造器
	public Account(String str)
	{
		this.name.set(str);
		// 下面代码用于访问当前线程的name副本的值
		System.out.println("---" + this.name.get());
	}
	// name的setter和getter方法
	public String getName()
	{
		return name.get();
	}
	public void setName(String str)
	{
		this.name.set(str);
	}
}
class MyTest extends Thread
{
	// 定义一个Account属性
	private Account account;
	public MyTest(Account account, String name)
	{
		super(name);
		this.account = account;
	}
	public void run()
	{
		// 循环10次
		for (int i = 0 ; i < 10 ; i++)
		{
			// 当i == 6时输出将账户名替换成当前线程名
			if (i == 6)
			{
				account.setName(getName());
			}
			// 输出同一个账户的账户名和循环变量
			System.out.println(account.getName()
				+ " 账户的i值:" + i);
		}
	}
}
public class ThreadLocalTest
{
	public static void main(String[] args) 
	{
		// 启动两条线程,两条线程共享同一个Account
		Account at = new Account("初始名");
		/*
		虽然两条线程共享同一个账户,即只有一个账户名
		但由于账户名是ThreadLocal类型的,所以每条线程
		都完全拥有各自的账户名副本,所以从i == 6之后,将看到两条
		线程访问同一个账户时看到不同的账户名。
		*/
		new MyTest(at , "线程甲").start();
		new MyTest(at , "线程乙").start ();
	}
}

为了解决线程不安全的集合,只需要包装下。

调用Collections中的一系列静态方法即可 ,举个例子:便可以返回线程安全的 List,应该在创建后立刻包装。

  • public static  List synchronizedList(List list)

还有在 java.util.concurrent 包下提供了大量支持高效并发访问的集合接口和实现类。

类名以 Concurrent 开头的集合类,在并发写入时有较好的性能

类名以CopyOnWriter开头的集合类,在并发读取时有较好的性能

java多线程_第2张图片



你可能感兴趣的:(java)