可以使用 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");//① 阻塞线程。
一些未处理的异常可以由这个线程所在的线程组来统一处理。
要构造一个属于某个线程组的线程,调用构造器:
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);
}
}
}
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线程相关类: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 staticList synchronizedList(List list)
还有在 java.util.concurrent 包下提供了大量支持高效并发访问的集合接口和实现类。
类名以 Concurrent 开头的集合类,在并发写入时有较好的性能
类名以CopyOnWriter开头的集合类,在并发读取时有较好的性能