Java中创建一个线程有三种方式:
public class NewThread {
//runnable接口实现类
private static class UseRunnable implements Runnable{
@Override
public void run() {//线程方法执行体
System.out.println("I am a runnable interface!");
}
}
//callable接口实现类
private static class UseCallable implements Callable{
@Override
public String call() {//线程方法执行体
System.out.println("I am a callable interface!");
return "callable";
}
}
//Thread类子类
private static class UseThread extends Thread{
@Override
public void run() {
System.out.println("I am extends Thread!");
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
UseRunnable useRunnable = new UseRunnable();
new Thread(useRunnable).start();//声明一个Thread实例,然后将runnable接口实例传入构造方法,调用start方法,即可执行线程
UseCallable useCallable = new UseCallable();//同runnable接口一样,new一个实例对象
FutureTask stringFutureTask = new FutureTask<>(useCallable);//因为callable接口是有返回值的,所以用FutureTask对象包装一下callable对象
new Thread(stringFutureTask).start();//然后将futuretask对象传入
System.out.println(stringFutureTask.get());//get方法是阻塞的
Thread useThread = new UseThread();
useThread.start();
}
}
通过继承Thread类的子类使用步骤
runnable接口的使用步骤
callable接口使用步骤:
Runnable和Callable在功能上基本是相似的,只是Callable接口中的call()方法是有返回值的,所以在使用Callable的时候需要用到FutureTask对象再对Callable对象包装一下,然后再传给Thread对象,可以通过FutureTask.get()方法来获得call()方法的返回值。
因为java是单继承的,但是可以多实现。
在Java中每一个线程都有一个优先级。
默认情况下,一个线程继承它的父线程的优先级。
可用setPriority()方法设置线程的优先级,Java中线程的优先级有1-10,即MIN_PRIORITY (1)- MAX_PRIORITY(10)。NORM_PRIORITY(5)。
Java中有俩类线程:User Thread(用户线程)、Daemon Thread(守护线程)
用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
这里有几点需要注意:
线程自然终止方式:
Java已不建议使用的方式
interrupt中断方式
中断方式有一点小问题,就是在线程执行过程中可能会抛出InterruptedException,而抛出了这个异常之后,线程的中断标志位会被复位成false,如果确实需要中断线程,要求我们自己在catch语句块里再次调用interrupt()方法,将中断标志位设为true,现在来看一下这个例子。
private static class TestInterruptException implements Runnable{
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while (!Thread.currentThread().isInterrupted()){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(threadName + "捕获到InterruptedException异常" + Thread.currentThread().isInterrupted());
e.printStackTrace();
// Thread.currentThread().interrupt();//中断请求
}
System.out.println("没有被中断!!!");
}
System.out.println("中断成功!!!");
}
}
public static void main(String[] args) throws InterruptedException {
TestInterruptException useRunnable = new TestInterruptException();
Thread thread = new Thread(useRunnable);
thread.start();//开启线程
Thread.sleep(500);//制造InterruptedException异常
thread.interrupt();//中断请求
}
没有被中断!!!
Thread-0捕获到InterruptedException异常false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.kk.线程基础.HasInterruptExcption$TestInterruptException.run(HasInterruptExcption.java:17)
at java.lang.Thread.run(Thread.java:748)
没有被中断!!!
没有被中断!!!
没有被中断!!!
我们故意制造了一个InterruptedException异常,然后通过打印结果发现线程没有被中断,所以它会一直运行下去,直到下一次中断请求。
现在我们将catch里面的注释打开。然后再运行一遍。
没有被中断!!!
Thread-0捕获到InterruptedException异常false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.kk.线程基础.HasInterruptExcption$TestInterruptException.run(HasInterruptExcption.java:17)
at java.lang.Thread.run(Thread.java:748)
没有被中断!!!
中断成功!!!
从打印结果可以看到,中断请求成功,线程执行完成。
那么InterruptedException异常是如何产生的呢?
synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的代码块或者方法在任意时刻只能有一个线程执行。
修饰实例方法:作用于当前对象实例,进入代码前要获得当前对象实例的锁。
public synchronized void test(){ // TODO }
修饰静态方法:也就是给当前类加锁,会作用于该类的所有实例对象。
public static synchronized void test(){ // TODO }
修饰代码块:指定加锁对象,进入代码块前要获得给定对象的锁
synchronized (Test.class) { // TODO }
对象锁:锁定的是当前对象,该对象只能给一个线程用,如果一个线程调用了这个对象的同步方法,那么其它线程只能等待得到对象的线程将对象释放后才能调用该对象的其它同步方法。非同步方法不影响。
类锁:即对Class对象上锁,每个类的Class对象在虚拟机种只有一个,所有该类的实例对象都共享这一个Class。所以类锁也只有一个。
在JDK1.2之前,Java的内存模型实现(即共享内存)读取变量是不需要特别注意的。而在当前的Java内存模型下,每一个线程可以把变量copy一份保存在自己的工作内存中,而不是直接在主存中读写,从而无法保证数据的可见性。
用法:volatile只能用来修饰变量而无法修饰方法以及代码块。
作用:
当只有一个线程可以修改字段的值,其它线程可以随意读取,那么把字段声明为volatile是合理的。
ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。
static ThreadLocal threadLaocl = new ThreadLocal(){
@Override
protected Integer initialValue() {
return 1;
}
};
/**
*类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会互相影响
*/
public static class TestThread implements Runnable{
int id;
public TestThread(int id){
this.id = id;
}
public void run() {
System.out.println(Thread.currentThread().getName()+":start");
Integer s = threadLaocl.get();//获得变量的值
s = s+id;
threadLaocl.set(s);
System.out.println(Thread.currentThread().getName()+":"
+threadLaocl.get());
//threadLaocl.remove();
}
}
public static void main(String[] args){
//启动三个线程
Thread[] runs = new Thread[3];
for(int i=0;i
首次接触,先了解一下,待以后再深入分析一下。
本段源自
Java线程在运行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一种状态(图源《Java 并发编程艺术》4.1.4 节)
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4 节):
上面那张可能看着比较混乱,下面这张是各个状态之间切换的简易版说明(图源《Java核心技术卷一》14.3.4节)
由上面可以看出:
操作系统隐藏 Java 虚拟机(JVM)中的 READY和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:HowToDoInJava:Java Thread Life Cycle and Thread States),所以 Java 系统一般将这两个状态统称为 RUNNABLE(运行中) 状态 。
下面我们来详细讲解一下,状态转换所需的方法。
线程创建之后它将处于 NEW(新建) 状态,调用 start() 方法后开始运行,线程这时候处于 READY(可运行) 状态。
但是Thread类还提供了一个run()方法,那么直接调用run方法和start方法有什么区别呢,我们通过一个经典的案例来演示一下。
public static void main(String[] args) {
Thread thread = new Thread(){
public void run() {
pong();
}
};
thread.run();
// thread.start();
System.out.println("Ping ");
}
static void pong() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Pong ");
}
执行程序后可以发现
调用run方法时,打印“ Pong Ping ”
调用start方法时,打印“Ping Pong”
这也就说明了,在调用run()方法时并没有开启线程,所以程序是顺序执行的,所以打印结果为“ Pong Ping ”。而调用start()方法,开启的子线程再休眠了100ms后才打印的“Pong”,所以打印结果为“Ping Pong”。
由上述可知Thread.yield() 调用后会将线程从RUNNING状态转变为READY状态
由于Java虚拟机将操作系统种的READY与RUNNING俩种状态统称为RUNNABLE状态,所以Thread.yield() 方法在Java层面来看是在RUNNABLE状态内完成的。
虽然该线程从RUNNING状态转变为了READY状态,但这并不意味着该线程就一定中断了(不执行了),而是该线程放弃了获得的时间片机会,cpu会重新从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。
所以说yield翻译为线程让步不太准确,有可能让了步,人家还不情愿,然后自己又死皮赖脸的继续执行了。
Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。
在ThreadA 的线程方法执行ThreadB.join()方法,ThreadA必须要等待ThreadB执行完之后,ThreadA才能继续自己的工作
thread.join()/thread.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程一般情况下进入RUNNABLE状态,也有可能进入BLOCKED状态(因为join是基于wait实现的)。
obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。
Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
应该尽量使用notifyAll() 方法,因为notify()有可能发生信号丢失的情况。
等待方:
通知方
public class Express {
public final static String CITY = "ShangHai";
private int km;/*快递运输里程数*/
private String site;/*快递到达地点*/
public Express() {
}
public Express(int km, String site) {
this.km = km;
this.site = site;
}
/* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理*/
//通知方
public synchronized void changeKm(){//synchronized 关键字获取锁
this.km = 101;//改变条件
notifyAll();//通知所有在等待该对象的线程,这里可改为notify()测试一下俩者的区别。
//其他的业务代码
}
/* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理*/
public synchronized void changeSite(){
this.site = "BeiJing";
notify();
}
public synchronized void waitKm(){//synchronized 关键字获取锁
while(this.km<=100) {//循环里判断条件是否满足,不满足调用wait方法
try {
wait();
System.out.println("check km thread["+Thread.currentThread().getId()
+"] is be notifed.");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("the km is"+this.km+",I will change db.");//条件满足执行业务逻辑
}
public synchronized void waitSite(){
while(CITY.equals(this.site)) {
try {
wait();
System.out.println("check site thread["+Thread.currentThread().getId()
+"] is be notifed.");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("the site is"+this.site+",I will call user.");
}
}
public class TestExpress {
private static Express express = new Express(0,Express.CITY);
/*检查里程数变化的线程,不满足条件,线程一直等待*/
private static class CheckKm extends Thread{
@Override
public void run() {
express.waitKm();
}
}
/*检查地点变化的线程,不满足条件,线程一直等待*/
private static class CheckSite extends Thread{
@Override
public void run() {
express.waitSite();
}
}
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<3;i++){//三个线程
new CheckSite().start();
}
for(int i=0;i<3;i++){//里程数的变化
new CheckKm().start();
}
Thread.sleep(1000);
express.changeKm();//快递地点变化
}
上述代码输出如下:
check km thread[17] is be notifed.
the km is101,I will change db.
check km thread[16] is be notifed.
the km is101,I will change db.
check km thread[15] is be notifed.
the km is101,I will change db.
check site thread[14] is be notifed.
check site thread[13] is be notifed.
check site thread[12] is be notifed.
用notifyAll()方法可唤醒在此对象监视器上等待的所有线程,代码正常运行。
如果用notify()方法的话,我们在main方法里改变了km的值,通知方调用notify()只唤醒了一个线程,但是我们启动了六个线程,三个监听km的,三个监听site的,所以notify只唤醒一个线程的话,是可能唤醒的线程就不是km的等待方,从而造成死锁。
由于上述,经典的等待/通知标准范式,无法做到超时等待,也就是说,消费者(等待方)在获得锁后,如果条件不满足,等待生产者改变条件之前会一直处于等待状态,在一些实际的应用中,这样会非常浪费资源,降低运行效率。
所以可对上述模式的消费者(等待方)做一下小改动:
long overtime = now+T; long remain = T;//等待的持续时间 while(result不满足条件&& remain>0){ wait(remain); remain = overtime – now;//等待剩下的持续时间 } return result;