并发编程基础
一、创建新线程
1、继承Thread类
优点
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
缺点
线程类已经继承了Thread类,所以不能再继承其他父类。
public class Create extends Thread {
public Create(String name) {
this.setName(name);
}
@Override
public void run() {
for (int x = 0; x < 100; ++x)
System.out.println("Thread:" + this.getName() + " : " + x);
}
public static void main(String[] args) throws InterruptedException {
Create t1 = new Create("t1");
Create t2 = new Create("t2");
t1.start();
t2.start();
}
}
2、Runnable接口
优点
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
缺点
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
public class Create implements Runnable {
public Create(String name) {
Thread.currentThread().setName(name);
}
@Override
public void run() {
for (int x = 0; x < 100; ++x)
System.out.println("Thread:" + Thread.currentThread().getName() + " : " + x);
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Create("t1"));
Thread t2 = new Thread(new Create("t2"));
t1.start();
t2.start();
}
}
3、通过Callable和Future创建线程
4、Runnable和Callable的区别
1. Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()
2. Callable的任务执行后可返回值,而Runnable的任务是不能返回值的
3. call方法可以抛出异常,run方法不可以
4. 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果
二、终止进程
1.stop()方法(废弃)
stop()方法在结束进程时,会直接终止进程,并立即释放这个线程所持有的锁,而这些锁是用来维持对象一致性的,若此时写进程刚写入一半被强行终止,那么对象就会被破坏。而此时锁已被释放,另外一个进程就读到了这个不一致的对象,悲剧就会发生
2.置中断标记位
public class Create extends Thread {
static boolean stop = false;
@Override
public void run() {
int x = 0;
while (true) {
if (stop)
break;
System.out.println(x++);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Create();
t1.start();
sleep(1000);
stop = true;
}
}
3.interrupt()方法
interrupt()方法类似置中断标记位,但遇到类似wait()或sleep()方法这样的操作,则只能通过interrupt()来识别了
三个看起来很像的线程方法辨别
public void Thread.interrupt();//通知线程中断,设置中断标记位
public boolean Thread.isInterrupted();//判断是否被中断
public static boolean Thread.interrupted();//判断是否被中断,并清除当前中断状态
public class Create extends Thread {
@Override
public void run() {
int x = 0;
while (true) {
System.out.println(x++);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Create();
t1.start();
t1.interrupt();
}
}
//虽然调用了interrupt()方法,但是程序并无处理中断的逻辑,所以虽然线程被置为中断状态,这个中断不会发生任何作用
//正确方法
public class Create extends Thread {
@Override
public void run() {
int x = 0;
while (true) {
if (this.isInterrupted())
break;
System.out.println(x++);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Create();
t1.start();
sleep(1000);
t1.interrupt();
}
}
public class Create extends Thread {
@Override
public void run() {
int x = 0;
while (true) {
if (this.isInterrupted())
break;
try {
sleep(1000000);
} catch (InterruptedException e) {
this.interrupt();
}
System.out.println(x++);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Create();
t1.start();
sleep(1000);
t1.interrupt();
}
}
//在catch语句中我们已经捕获了中断可以立即退出线程,但是我们并没有这么做,因为在代码中我们还必须进行后续的处理来保证数据的一致性和完整性,因此,执行了interrupt()再次置上中断标记位,
//Thread.sleep()方法由于中断而抛出异常,此时他会清除中断标记位,如果不加处理,下次循环开始时,无法捕捉到这个中断。故而需要重新置标记位
三、wait和notify
public final void wait() throws InterruptedException;
public final native void notify();
当一个对象实例调用wait方法后,当前线程就等待,一直到其他线程调用notify方法为止。
当我们有4个线程t1,t2,t3,t4依次执行了wait方法,他们就会依次进入等待队列,但当有线程执行notify方法时,并不是按照wait的次序唤醒,而是从这四个线程中随机唤醒一个。
此外wait方法不能随意调用,必须包含在对应的synchronized ()语句中,无论是wait或notify都必须首先获得目标对象的一个监视器。
public class Create {
final static Object object = new Object();
public static class t1 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + "--t1 开始!");
try {
System.out.println(System.currentTimeMillis() + "-- t1 等待");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(System.currentTimeMillis() + "--t1 结束!");
}
}
public static class t2 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + "--t2 开始");
object.notify();
System.out.println(System.currentTimeMillis() + "--t2 通知!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println(System.currentTimeMillis() + "--t2 放锁!");
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new t1();
Thread t2 = new t2();
t1.start();
t2.start();
}
}
}
//运行后我们可以发现,在t2线程notify后,t1并未运行,而是在t2等待2s后释放object锁,t1获得锁后才继续运行
wait和sleep方法
这两个方法都能让线程等待若干时间,除wait方法可被唤醒之外,另一个区别是wait方法会释放目标对象的锁,而sleep方法不会释放任何资源
四、suspend(挂起)和resume(继续执行)--废弃
suspend方法在导致线程暂停的同时,并不会释放任何锁资源,而对于被挂起的线程,其运行状态仍然是Runnable
五、join(等待线程结束)和yeild(谦让)
join方法的本质是让调用线程wait在当前线程对象实例上
while(isAlive())
wait(0);
他让调用线程在当前线程对象上等待,当线程结束后,被等待的线程会在退出前调用notifyAll()方法通知所有的等待线程继续执行
yield方法一旦执行,当前线程会让出cpu,然后重新参与cpu资源的争夺,当我们觉得一个线程不那么重要,害怕其占用过多资源,就可以在适当的时候调用Thread.yield()方法,给予其他重要线程更多的工作机会。
六、volatile和JAVA内存模型(JMM)
使用volatile修饰变量
1.当变量被修改时,虚拟机会保证所有线程都能看到这个改动
2.虚拟机会保证变量被多线程修改时不会出错
public class Create {
volatile static boolean ready;
public static class t1 extends Thread {
@Override
public void run() {
while(!ready);
System.out.println("end");;
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new t1();
t1.start();
ready=true;
}
}
//若未使用volatile修饰ready,当虚拟机在Client模式下时,JIT无优化,在主线程修改ready后,线程能发现改动。但是在Server模式下,由于系统优化,线程可能永远无法发现变量改动,导致死循环
七、线程组
public class Create {
public static class thread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getThreadGroup().getName() + "--" + Thread.currentThread().getName());
}
}
public static void main(String[] args) throws InterruptedException {
ThreadGroup tg = new ThreadGroup("PrintGroup");
Thread t1 = new Thread(tg, new thread(), "t1");
Thread t2 = new Thread(tg, new thread(), "t2");
t1.start();
t2.start();
System.out.println(tg.activeCount());
tg.list();
}
}
常用方法
activeAccount()//活跃线程数
list()//活跃线程信息
stop()//停止线程组所有线程,存在普通线程Stop方法的相同问题,不推荐使用
八、守护线程
volatile static int x = 0;
public static class ReadDaemon implements Runnable {
@Override
public void run() {
while (true)
System.out.println(x);
}
}
public static class Modify implements Runnable {
@Override
public void run() {
for (x = 0; x < 100; ++x) {
try {
sleep(100);
} catch (InterruptedException e) {
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread rd = new Thread(new ReadDaemon());
Thread modify = new Thread(new Modify());
rd.setDaemon(true);
rd.start();
modify.start();
modify.join();
}
//设置守护线程必须在start前,当其他线程结束后,守护线程会随之自动结束
九、线程优先级
Thread thread = new Thread(new Modify());
thread.setPriority(Thread.MAX_PRIORITY);
thread.setPriority(8);
//优先级从低到高0-10,高优先级线程在竞争资源时会更有优势,但这只是概率问题,很有可能高优先级线程一直抢占失败,因此在要求严格的场合,还是需要自己在应用层解决线程调度问题
十、synchronized
实现线程间同步,对同步代码进行加锁,使得每一次只有一个线程进入同步块,从而保证线程间的安全性
使用方法
- 对象加锁: 对给定对象加锁,进入同步代码前需要获得给定对象的锁
- 作用于实例方法: 对当前实例加锁,进入同步代码前需要获得当前实例的锁
- 作用于静态方法:对当前类加锁,进入同步代码前需要获得当前类的锁
对象加锁
public static class Test implements Runnable {
static final Test test = new Test();
static int x = 0;
@Override
public void run() {
for (int i = 0; i < 100; ++i)
synchronized (test) {
++x;
}
}
}
实例方法加锁
public static class Test implements Runnable {
static int x = 0;
public synchronized void increase() {
++x;
}
@Override
public void run() {
for (int i = 0; i < 100; ++i)
increase();
}
}
实例方法错误加锁
public static class Test implements Runnable {
static int x = 0;
public synchronized void increase() {
++x;
}
@Override
public void run() {
for (int i = 0; i < 100; ++i)
increase();
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Test());
Thread t2 = new Thread(new Test());
t1.start();t2.start();
t1.join();t2.join();
System.out.println(x);
}
//虽然increase是一个同步方法,但是t1,t2指向不同的Test实例,所以这两个线程的实例并非同一个对象,因此结果会出错,修正方法就是下面的静态方法加锁
类加锁
public static class Test implements Runnable {
static int x = 0;
public static synchronized void increase() {
++x;
}
@Override
public void run() {
for (int i = 0; i < 100; ++i)
increase();
}
}
//使用static,即使两个线程指向不同的对象,但方法块请求的事当前类的锁,而非当前实例,因此可正常执行
十一、synchronized和Integer
public class Create implements Runnable {
public static Integer i = 0;
static Create instance = new Create();
@Override
public void run() {
for (int j = 0; j < 100000; ++j)
synchronized (i) {
++i;
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
//这个程序表面看起来没有问题,但实际运行会出错,因为Integer输入不变对象。一旦被创建就不能被修改,所以加锁的i对象每次都不是同一个Integer实例,加锁失败