Java多线程简单使用
继承Thread类
通过继承Thread,重写run方法
public class MyThread extends Thread{
@Override
public void run() {
super.run();
System.out.println("MyThread");
}
}
测试代码:
@Test
public void test1(){
MyThread myThread = new MyThread();
myThread.start();
System.out.println("运行结束");
}
因为java是单根继承,不支持多继承,在程序设计上是有局限的。
实现Runnable接口(推荐)
实现Runnale接口,重写run方法
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("running");
}
}
测试代码:
@Test
public void test2(){
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
System.out.println("ending");
}
- 注意,这里测试代码是实现Runnable接口后,通过Thread来接受Runnable接口对象,这样可以让Thread的start方法来调用Run方法,起到多线程的作用。
- 如果直接在主程序中调用run方法,就仅仅是程序的调用,无法起到多线程的作用。
java多线程的同步
synchronize实现同步
java中的每一个对象都可以作为锁,具体表现形式为下面三种形式:
- 对于普通同步方法,锁是当前实例对象
- 对于静态同步方法,锁是当前类的class对象
- 对于同步方法块,锁是Synchronize括号里配置的对象
上面三种形式默认synchronized相当于synchronized(this)锁this对象。
对于synchronized(非this对象x)这种情况,有下属三种结论:
- 当多个线程同时执行synchronized(x){}同步代码块呈现同步效果。
- 当其它线程执行x对象中synchronized同步方法呈现同步效果
- 当其它线程执行x对象方法里面的synchronized(this)代码块也呈现同步效果。
volatile关键字
关键字volatile主要作用是使变量在多个线程间可见
Java线程间通信
等待/通知机制
等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B
调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而
执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的
关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
public class WaitNotify {
static boolean flag = true;
static Object lock = new Object();
public static void main(String[] args) throws Exception {
Thread waitThread = new Thread(new Wait(), "WaitThread");
waitThread.start();
TimeUnit.SECONDS.sleep(1);
Thread notifyThread = new Thread(new Notify(), "NotifyThread");
notifyThread.start();
}
static class Wait implements Runnable {
public void run() {
// 加锁,拥有lock的Monitor
synchronized (lock) {
// 当条件不满足时,继续wait,同时释放了lock的锁
while (flag) {
try {
System.out.println(Thread.currentThread() + " flag is true. wait @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();
} catch (InterruptedException e) {
}
}
// 条件满足时,完成工作
System.out.println(Thread.currentThread() + " flag is false. running @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable {
public void run() {
// 加锁,拥有lock的Monitor
synchronized (lock) {
// 获取lock的锁,然后进行通知,通知时不会释放lock的锁,
// 直到当前线程释放了lock后,WaitThread才能从wait方法中返回
System.out.println(Thread.currentThread() + " hold lock. notify @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
SleepUtils.second(5);
}
// 再次加锁
synchronized (lock) {
System.out.println(Thread.currentThread() + " hold lock again. sleep @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
SleepUtils.second(5);
}
}
}
}
我们在使用这些方法需要注意下面几点:
1)使用wait()、notify()和notifyAll()时需要先对调用对象加锁。
即这些方法必须位于synchronize同步块内。
- 2)调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的
等待队列。 3)notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或
notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。即会将notify()或者notifAll()所在的同步块执行完才会调转到wait()所在的同步块。
- 4)notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()
方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为
BLOCKED。 - 5)从wait()方法返回的前提是获得了调用对象的锁。
管道输入/输出流
管道流主要用于线程之间的数据传输,传输的媒介是内存。java中主要包括了4种具体的实现:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。
public class Piped {
public static void main(String[] args) throws Exception {
PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
// 将输出流和输入流进行连接,否则在使用时会抛出IOException
out.connect(in);
Thread printThread = new Thread(new Print(in), "PrintThread");
printThread.start();
int receive = 0;
try {
while ((receive = System.in.read()) != -1) {
out.write(receive);
}
} finally {
out.close();
}
}
static class Print implements Runnable {
private PipedReader in;
public Print(PipedReader in) {
this.in = in;
}
public void run() {
int receive = 0;
try {
while ((receive = in.read()) != -1) {
System.out.print((char) receive);
}
} catch (IOException ex) {
}
}
}
}
join方法
- thread.join()当前线程等待thread线程终止后才继续执行。
- thread.join(long millis)具备超时特性,在上者情况下,如果在指定时间内进程没终止,将从超时方法返回。
thread.join(long millis,int nanos)
public class Join { public static void main(String[] args) throws Exception { Thread previous = Thread.currentThread(); for (int i = 0; i < 10; i++) { // 每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回 Thread thread = new Thread(new Domino(previous), String.valueOf(i)); thread.start(); previous = thread; } TimeUnit.SECONDS.sleep(5); System.out.println(Thread.currentThread().getName() + " terminate."); } static class Domino implements Runnable { private Thread thread; public Domino(Thread thread) { this.thread = thread; } public void run() { try { thread.join(); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + " terminate."); } } }
结果如下:
main terminate.
0 terminate.
1 terminate.
2 terminate.
3 terminate.
4 terminate.
5 terminate.
6 terminate.
7 terminate.
8 terminate.
9 terminate.
ThreadLocal的使用
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这
个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个
线程上的一个值。
可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。
public class Profiler {
// 第一次get()方法调用时会进行初始化(如果set方法没有调用),每个线程会调用一次
private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>() {
protected Long initialValue() {
return System.currentTimeMillis();}
};
public static final void begin() {
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
public static final long end() {
return System.currentTimeMillis() - TIME_THREADLOCAL.get();
}
public static void main(String[] args) throws Exception {
Profiler.begin();
TimeUnit.SECONDS.sleep(1);
System.out.println("Cost: " + Profiler.end() + " mills");
}
}
java多线程基础
线程优先级
在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10,在线
程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分
配时间片的数量要多于优先级低的线程。设置线程优先级时,针对频繁阻塞(休眠或者I/O操
作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较
低的优先级,确保处理器不会被独占。在不同的JVM以及操作系统上,线程规划会存在差异,
有些操作系统甚至会忽略对线程优先级的设定。
setPriority(int)
线程的状态
java线程在运行的生命周期可能处于下表的6中不同的状态,在给定的一个时刻,
线程只能处于其中的一个状态。
守护线程Daemon
Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这
意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。可以通过调
用Thread.setDaemon(true)将线程设置为Daemon线程。
Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时Daemon线程中的finally块
并不一定会执行。
- Daemon属性需要在启动线程之前设置,不能在启动线程之后设置
- 在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。
线程中断
使用interrupt()方法来停止线程,但interrupt()方法仅仅是在当前线程中刚打了一个断点。
java提供两种方法判断线程是否中断:
- this.interrupted()测试当前进程是否已经中断,执行后将具有状态标志清除为flase功能
- this.isInterrupted()测试线程是否已经中断,不清除状态标志
推荐使用异常法来停止线程:
public class MyThreadInterrupt extends Thread {
@Override
public void run() {
super.run();
try{
for(int i=0;i<500000;i++){
if(this.isInterrupted()){
System.out.println("线程终止,即将退出");
throw new InterruptedException();
}
System.out.println("i="+(i+1));
}
System.out.println("for循环完了,继续执行程序。。。");
}catch (InterruptedException e){
System.out.println("进入catch捕捉异常");
e.printStackTrace();
}
}
}
测试代码:
@Test
public void test3(){
try{
MyThreadInterrupt myThreadInterrupt = new MyThreadInterrupt();
myThreadInterrupt.start();
Thread.sleep(2000);
myThreadInterrupt.interrupt();
} catch (InterruptedException e){
System.out.println("mian catch");
e.printStackTrace();
}
}
如果不使用抛异常来终止线程,那么线程即使中断还是会for循环继续运行
或者使用标志位来控制是否需要停止任务并终止线程
public class Shutdown {
public static void main(String[] args) throws Exception {
Runner one = new Runner();
Thread countThread = new Thread(one, "CountThread");
countThread.start();
// 睡眠1秒,main线程对CountThread进行中断,使CountThread能够感知中断而结束
TimeUnit.SECONDS.sleep(1);
countThread.interrupt();
Runner two = new Runner();
countThread = new Thread(two, "CountThread");
countThread.start();
// 睡眠1秒,main线程对Runner two进行取消,使CountThread能够感知on为false而结束
TimeUnit.SECONDS.sleep(1);
two.cancel();
}
private static class Runner implements Runnable {
private long i;
private volatile boolean on = true;
@Override
public void run() {
while (on && !Thread.currentThread().isInterrupted()) {
i++;
}
System.out.println("Count i = " + i);
}
public void cancel() {
on = false;
}
}
}
注意: suspend(),resume(),stop()已过期
全文来自 java并发编程的艺术 和 java多线程编程核心技术