1、在java中要实现多线程,有两种手段,一种是继承Thread类,另外一种是实现Runable接口
1)继承了Thread类
public class HelloThread extends Thread{
private String name;
public HelloThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0; i < 5; i++)
System.out.println( name + "运行第 " + i + " 次");
}
public static void main(String[] arags) {
HelloThread h1 = new HelloThread("线程A");
HelloThread h2 = new HelloThread("线程B");
h1.start();
h2.start();
}
}
2)实现了Runnable接口
public class HelloRunnable implements Runnable{
private String name;
public HelloRunnable(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0; i < 5; i++)
System.out.println( name + "运行第 " + i + " 次");
}
public static void main(String[] args) {
HelloRunnable h1 = new HelloRunnable("线程A");
Thread t1 = new Thread(h1);
HelloRunnable h2 = new HelloRunnable("线程B");
Thread t2 = new Thread(h2);
t1.start();
t2.start();
}
}
Thread其实也是实现了Runnable接口
结果:
①该对象没有被当前线程锁住,则调用wait方法报错:
结果:
结果:
结果:
class Thread implements Runnable {
//…
public void run() {
if (target != null) {
target.run();
}
}
}
Thread和Runnable的区别:
如果一个类继承Thread,则不适合资源共享,但用Runnable接口的类就可以实现资源共享。同时避免了单继承,所以推荐使用Runnable作为多线程的开启方法
public class MyThread implements Runnable{
//多个线程处理一个对象可实现资源共享
private int ticket = 5;
@Override
public void run() {
for(int i = 0; i <= 20; i++) {
if(ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在售票" +ticket--);
}
}
}
public static void main(String[] args) {
MyThread my = new MyThread();
new Thread(my, "一号窗口").start();
new Thread(my, "二号窗口").start();
new Thread(my, "三号窗口").start();
}
}
2、线程使用的方法有:
1)isAlive 判断线程是否还在运行
public static void main(String[] args) {
Thread t1 = new Thread();
System.out.println("线程启动前: " + t1.isAlive());
t1.start();
System.out.println("线程启动后: " + t1.isAlive());
}
2)join 在当前线程中调用另外一个线程的join方法,则会阻塞当前运行的线程直到调用join方法的线程执行完毕再继续执行,换句话说,如果调用join方法的线程没有执行完,则当前线程会一直等待,但其它无关线程不受阻碍。
一般是用于使用子线程执行耗时操作,当要使用子线程中的结果时调用该方法把结果执行完毕再往下执行。
public static void main(String[] argss) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
for(int i = 0; i < 3; i++) {
System.out.println("子线程: " +Thread.currentThread().getName());
Thread.sleep(1000); //sleep 1 秒
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
//开启线程
t1.start();
for(int i = 0; i < 50; i++) {
if(i > 8) {
try {
t1.join(); //当大于8时把t1线程执行完再往下执行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("主线程执行到第 " + i + "次");
}
}
结果:
可以看到当线程达到第8次时会先把子线程执行完在往下执行,你自己可以尝试一下。
3)wait 释放锁对象并等待,如果等待对象没有被锁住,则抛出异常IllegalMonitorStateException,换句话说,需要使用对象的wait方法,则需要在synchronized 语句块或方法体内使用。wait方法不是线程的方法,而是每一个对象的方法。所以针对的是访问该对象的当前线程的阻塞。
①该对象没有被当前线程锁住,则调用wait方法报错:
public static void main(String[] argss) {
Object object =new Object();
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
②使用synchronized语句块
public static void main(String[] argss) {
Object object =new Object();
synchronized (object) {
try {
object.wait(); //阻塞访问当前对象的线程,即主线程,所以下面这句话永远不会打印出来
System.out.println("当前线程为: " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4)notify 唤醒在等待该对象锁的线程,每次只唤醒一个线程,同时不能控制唤醒的是哪一个线程。一般与wait互用
我们还是使用上一个例子,但是因为当前线程已被阻塞,我想你不会在当前线程中使用objecty.notify来调用吧。我们需要另外一个线程中唤醒被阻塞的线程。
//将对象变成静态类变量实现两个线程之间共享
static Object object =new Object();
public static void main(String[] argss) {
//使用t1对主线程进行唤醒操作
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while(true) {
//因为不知道几时进行阻塞状态,所以需要不断循环
System.out.println("子线程运行中...");
synchronized (object) {
object.notify(); //唤醒主线程操作
System.out.println("唤醒后我会不会继续操作。。"); //调用notify之后不会阻塞当前线程,其它线程会等待当前线程执行完再争夺对象锁
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start(); //开启子线程
synchronized (object) {
try {
System.out.println("主线程进行wait等待中");
object.wait(); //阻塞访问当前对象的线程,即主线程
System.out.println("当前线程为: " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:
如果操作的对象不是同一个,wait和notify也就没什么意义了。
5)synchronized关键字 用于保证同一时刻最多只有一个线程执行该代码段,用于锁定方法或者代码块
①synchronized方法
如:
public synchronized void accessVal(int newVal); 即创建该方法的实例并调用改方法时,同时刻只会有一个线程进行操作,锁对象是该方法的实例。
如果是静态类方法,则获得的是类锁,类锁不是把所有该类对象实例都加锁,而是单单只是类信息,即Object.class对象。 如: public synchronized static void accessVal(int newVal);
②synchronized快
上面代码中也有用到,即synchronized(要加锁的对象),可以使用synchronized(this)锁住当前对象实例,或者锁住你需要的共享对象。用法比较简单。
6)interrupt 通过抛出一个中断信号,让线程获得InterruptException,从而退出阻塞状态,但不会中断一个正在运行的线程 。
如用Object.wait Thread.join 和 Thread.sleep阻塞,都可以使用interrupt方法提前退出阻塞。(个人觉得比较少用)
①.sleep() & interrupt()
public static void main(String[] args) {
//这里有两个线程threadA和threadB,将threadA调用sleep方法,并在threadB中调用threadA.interrupt()方法触发异常。
final Thread threadA = new Thread(new Runnable() {
public void run() {
try {
System.out.println("进入sleep状态");
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("捕获Exception后运行的一行。。");
}
});
Thread threadB = new Thread(new Runnable() {
public void run() {
threadA.interrupt();
}
});
threadA.start();
//避免那么快执行完,先sleep一下
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadB.start();
}
结果:
②join() & interrupt()
interrupt的调用者一定是当前被阻塞的线程,同时这个被阻塞的线程由其他线程调用。这样才能发挥功效
public static void main(String[] args) {
//这里有三个线程。
//线程A会在i = 8时调用线程C的join方法,即自己被阻塞,让线程C方法运行完再继续运行
//线程B会调用线程A的interrupt方法提前退出join方法
final Thread threadC = new Thread(new Runnable() {
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println("ThreadC 线程执行第: " + i + "次" );
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
final Thread threadA = new Thread(new Runnable() {
public void run() {
for(int i = 0; i < 30; i++) {
if(i == 8) {
try {
threadC.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("ThreadA 线程执行第: " + i + "次" );
}
}
});
Thread threadB = new Thread(new Runnable() {
public void run() {
while(true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadA.interrupt();
}
}
});
threadA.start();
threadB.start();
threadC.start();
}
结果:
③wait() & interrupt()
public static void main(String[] argss) {
final Object object = new Object();
final Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程A开始执行...");
synchronized (object) {
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A继续执行。。。");
}
}
});
threadA.start(); //开启子线程
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程B开始执行");
//如果太快是会先打印出线程A继续执行。。而不是线程B开始执行
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadA.interrupt();
}
});
threadB.start();
}
}
7) Thread.yield() 暂停线程的执行,给其它具有相同优先权的线程执行的集合,若没有其它线程执行,则线程继续执行。创建一个线程默认优先级为5,可以使用setPriority进行设置优先级。
补充:线程有4个状态
新建状态New 线程处于新建状态,还没调用start方法
就绪状态Runnable:线程对象创建后,其它线程调用了该对象的start方法,该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
运行状态Running:就绪状态的线程获得了CPU,执行程序代码。
阻塞状态 Blocked:阻塞状态时线程因为某种原因放弃CPU使用权,暂时停止运行。需要重新进入就绪状态,等待获得CPU使用时间片
(一)等待阻塞:运行的线程执行wait方法,JVM会把线程放入等待池
(二)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入池中
(三)其它阻塞:运行的线程执行sleep或join方法,或者发出了I/0请求时,JVM会把线程设为阻塞状态,当之前方法完毕之后,线程重新转入就绪状态。
死亡状态Dead:线程执行完或者异常退出,则该线程结束生命周期
8)suspend和resume。因为suspend容易发生死锁,调用suspend方法时,目标线程会停下来,但仍会拥有之前获得的锁。所以不建议使用。