备注:实现Runnable接口的方式,是较为常用的。将所需多线程实现的代码写在run()中;通过将runnable对象传入Thread构造方法中,并调用Thread对象的start()方法开启线程。
1)相比Thread,Runnabl适合多个相同代码的线程处理同一个资源(举个直观的例子:用Runnable时,可以在每一次new Thread(runnable)时,都传同一个Runnable对象,使n个Thread同时处理一个资源);
2)由于Java是单extends,多implements,显然以接口的方式去写更为方便(注:Thread其实是实现了Runnable接口)
实现方法:
public class RunnableTest {
public static void main(String[] args) {
Runnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("Hello Runnable " + Thread.currentThread().getName());
}
}
备注:实现Thread需继承Thread类,Thread中提供了run()和start()方法。将所需用多线程实现的代码复写在run()方法中;通过start()方法开启并执行线程。
实现方法:
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println("Hello Thread " + Thread.currentThread().getName());
}
}
备注:无论是用Runnable还是Thread都无法避免一个现实,那就是他们并不能返回结果;可是使用Callable+Future/FutureTask就可以很好地解决这个问题。
1)Callable和Runnable一样都是需要实现相应的接口,不同的是Callable所需复写的是call()方法,并且call()方法是有一个Object类型的返回值的。还有值得注意的是Callable是可以抛出异常的。
2)FutureTask:FutureTask实现RunnableFuture接口,RunnableFuture接口又继承了Runnable, Future两个接口。FutureTask中实现了run()方法,在run()
中会获取构造方法中传入的Callable对象所复写的call()方法的返回值。所以FutureTask对象进行.get()时可以获取Callable所返回的值。
实现方法(配合FutureTask):
public class CallableTest {
public static void main(String[] args) {
Callable callable = new MyCallable();
FutureTask futureTask = new FutureTask(callable);
new Thread(futureTask).start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable{
@Override
public Object call() throws Exception {
String str = "Hello Callable " + Thread.currentThread().getName() + "我被获取啦!";
System.out.println("Hello Callable");
return str;
}
}
备注:Callable相关见上
Future:包含get(),get(long timeout, TimeUnit unit)两种获取结果的方法,其中第一种如果取不到结果会一直阻塞到计算完成;后一种方法可以设置阻塞时间,超过阻塞时间没获取结果则抛出异常
线程池包含4种:newSingleThreadExecutor创建一个单线程池、newFixedThreadPool创建固定数量的线程池、newCachedThreadPool创建一个可缓存的线程池、newScheduledThreadPool创建一个大小无限的线程池。线程池两种开启方式submit()与excute()区别在于前者可接受runnable、callable后者只能接受runnable且前者可以有返回值,后者没有返回值。
实现方法:
public class CallableTest {
public static void main(String[] args) {
ExecutorService pool = Executors.newSingleThreadExecutor();
Callable callable = new MyCallable();
Future future = pool.submit(callable);
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable{
@Override
public Object call() throws Exception {
String str = "Hello Callable " + Thread.currentThread().getName() + "我被获取啦!";
System.out.println("Hello Callable");
return str;
}
}
注:当需要进行多结果收集(伴随阻塞主线程,待所有子线程完成后再唤醒)
请点击这里查看
**备注:**继承Thread类
public class ThreadTest {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
System.out.println("Hello Thread " + Thread.currentThread().getName());
}
}.start();
}
}
**备注:**实现Runnable接口
public class ThreadTest {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello Runnable " + Thread.currentThread().getName());
}
}).start();
}
}
线程的整个生命周期分5个状态:新建状态(New)、就绪状态(Runnable)、运行状态(Running)、阻塞状态(Block)、死亡状态(Terminated)。
thread.setPriority(Thread.MIN_PRIORITY);
thread.setPriority(Thread.MAX_PRIORITY);
thread.setPriority(Thread.NORM_PRIORITY);
thread.setPriority(10);
以毫秒为单位的线程阻塞
Thread.sleep(millis);
Thread.yield();
与sleep区别在于yield方法不会阻塞线程,只是将线程转换成就绪状态,让系统调度器重新调度一次。注意,这并不是等待或阻塞,也不能确保能使运行的线程立即转换成就绪状态。另外,当某一线程被yield后,只有与当前线程优先级相同或者更高的线程才能获得执行的机会。
线程A遇到另一个线程B.join()时,A会阻塞,等B执行完后再执行
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RunnableA());
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println("我是主线程,正在跑第" + i + "条");
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class RunnableA implements Runnable {
@Override
public void run() {
System.out.println("我RunnableA开始跑了");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我RunnableA现在跑完了");
}
}
输出结果:
我是主线程,正在跑第0条
我RunnableA开始跑了
我RunnableA现在跑完了
我是主线程,正在跑第1条
我是主线程,正在跑第2条
我是主线程,正在跑第3条
我是主线程,正在跑第4条
interrupt只会对受阻塞的线程进行中断,抛出InterruptedException 异常,对运行中的线程是没有作用的
public class ThreadTest {
static Object object = new Object();
public static void main(String[] args) throws Exception{
Thread thread= new MyThread();
thread.start();
thread.interrupt();
}
}
class MyThread extends Thread{
public void run(){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("我被终止啦");
}
}
}
输出结果
我被终止啦
备注:用到interrupt时记得预先对可能抛出的InterruptedException 异常做出处理
备注:由synchronized关键字修饰的代码块及方法在某一时刻只允许一个线程访问。
####同步代码块
lock是一个锁对象,当线程执行同步代码块时,首先会检查锁对象的标志位,默认为1,此时线程会执行同步代码块,并将锁对象的标志位为0。当其他线程执行到此,因为锁对象标志位为0,所以会出现阻塞,等当前线程执行完同步代码块后,锁对象的标志位会置1
Object lock = new Object();// 一般写为成员变量
synchronized(lock){
// 操作共享资源代码块
}
####同步方法
同步方法的锁就是当前调用该方法的对象,这样的好处便是同步方法被所有线程所共享,方法所在的对象对所有线程来说是唯一的
修饰符 synchronized 返回值类型 方法名 (参数1,参数2..)
例:public synchronized void methed(){}
A锁中用了B锁,B锁中用了A锁,互相都在等待对方的锁,所以造成死锁
public class ThreadTest {
public static void main(String[] args) throws Exception {
Thread threadA = new MyThread("A");
Thread threadB = new MyThread("B");
threadA.start();
threadB.start();
}
}
class MyThread extends Thread {
static Object key1 = new Object();
static Object key2 = new Object();
String houseOwner;
public MyThread(String houseOwner) {
this.houseOwner = houseOwner;
}
public void run() {
if ("A".equals(houseOwner)) {
synchronized (key1) {
System.out.println("我是A我用了1号锁");
synchronized (key2) {
System.out.println("我是A我用了2号锁");
}
}
} else {
synchronized (key2) {
System.out.println("我是B我用了2号锁");
synchronized (key1) {
System.out.println("我是B我用了1号锁");
}
}
}
}
}
输出结果:可见只输出了2个语句,并没有输出4个语句,因为后面都在等对方的锁
我是B我用了2号锁
我是A我用了1号锁
线程通信是通过对锁.wait()、.notify()来控制多个线程按一定顺序轮流执行
wait():使当前线程交出同步锁,并开始等待,直到其他线程调用此同步锁的notify()方法(调用某个对象的notify()时,当前线程也需要有这个对象锁,所以相应的要在当前线程的同步代码块中执行notify,同时也只有等当前线程中的同步代码块跑完了后才能将锁还回等待的线程并唤醒它,观察下面的返回结果就可得知)。
public class ThreadTest {
public static Object lock = new Object();
public static void main(String[] args) throws Exception {
Thread thread = new MyThread();
thread.start();
synchronized(lock){
System.out.println("主线程:开始wait");
lock.wait();
System.out.println("主线程:我终于可以活动了");
}
}
}
class MyThread extends Thread {
public void run() {
try {
System.out.println("子线程:开启子线程线程");
System.out.println("子线程:我要玩5秒再唤醒主线程");
Thread.sleep(5000);
System.out.println("子线程:我玩好了可以唤醒主线程了");
synchronized (ThreadTest.lock) {
System.out.println("子线程:唤醒主线程");
ThreadTest.lock.notify();
System.out.println("子线程:我再去玩5秒");
Thread.sleep(5000);
System.out.println("子线程:我再次玩好了");
}
} catch (InterruptedException e) {
}
}
}
输出结果:
主线程:开始wait
子线程:开启子线程线程
子线程:我要玩5秒再唤醒主线程
子线程:我玩好了可以唤醒主线程了
子线程:唤醒主线程
子线程:我再去玩5秒
子线程:我再次玩好了
主线程:我终于可以活动了
注意:线程自身是不能唤醒自己的,如下图所示
public class ThreadTest {
public static Object lock = new Object();
public static void main(String[] args) throws Exception {
synchronized(lock){
System.out.println("主线程:开始wait");
lock.wait();
System.out.println("主线程:我终于可以活动了");
lock.notify();
System.out.println("主线程:我终于可以活动了");
}
System.out.println("主线程:我终于可以活动了");
}
}
输出结果:后面的并没有输出。(只有等别人唤醒你,你自己无法唤醒自己)
主线程:开始wait
先说一下比较传统的集合
线程安全的list:Vector
线程安全的map:HashTable
附加-线程安全的String:StringBuffer
上述用法大家都很熟了就不再赘述,下面来几个性能更好的线程安全集合
CopyOnWriteArrayList
Collections.synchronizedList
Concurrenthashmap
结束语:对线程相关基础的一个小小的整理与总结,多为拾人牙慧如有错误或不足,万望各位看官能够不吝赐教~