一、什么是多线程
Thread类中有这样的明确定义:线程是程序中执行的线程,Java虚拟机允许程序同时运行多个执行线程。
1.线程有5种状态:新建,就绪,运行,阻塞,死亡。(网上有很多说有六种或者七种,其实都表达的一样,等待和睡眠都会导致阻塞)
新建:当使用new操作符创建新线程时,线程处于“新建“状态
就绪:调用start()方法
运行:执行run()方法
阻塞:当线程需要获得对象的内置锁,而该锁正在被其他线程拥有,或者当线程等待其他线程通知调度表可以运行时(即调用sleep()或者wait()方法)
终止:当run()方法运行完毕或调用stop()方法
二、简单的多线程Demo
public class ThreadTest implements Runnable{//或者extends Thread
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<3;i++){
System.out.println("这是线程:"+Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) throws InterruptedException {
//实例化类
ThreadTest test1=new ThreadTest();
//创建线程对象,访问同一对象
Thread t1=new Thread(test1);
Thread t2=new Thread(test1);
//启动线程
t1.start();
t2.start();
}
}
运行结果:
三、多线程的sleep方法
多线程的睡眠方法,调用时会一直持有锁资源,其它线程无法访问
1.sleep(long millis) 线程睡眠 millis 毫秒
2.sleep(long millis, int nanos) 线程睡眠 millis 毫秒 + nanos 纳秒
可以看到上面的运行结果是交替运行的
现在将上述main方法中加入sleep方法
public static void main(String[] args) throws InterruptedException {
//实例化类
ThreadTest test1=new ThreadTest();
//创建线程对象
Thread t1=new Thread(test1);
Thread t2=new Thread(test1);
//启动线程
t1.start();
//使线程睡眠2秒
t1.sleep(2000);
t2.start();
}
运行结果
t1线程执行完后等待两秒之后才执行t2线程
注意:Sleep是Thread的静态方法,是线程用来控制自身流程的。在main中可以直接调用,这里的t1.sleep等价于Thread.sleep,跟t1无关,它是让使用该方法的线程也就是main方法睡眠两秒。
三、多线程的join方法
join在线程里面意味着“插队”,哪个线程调用join代表哪个线程插队先执行——但是插谁的队是有讲究了,不是说你可以插到队头去做第一个吃螃蟹的人,而是插到在当前运行线程的前面,比如系统目前运行线程A,在线程A里面调用了线程B.join方法,则接下来线程B会抢先在线程A面前执行,等到线程B全部执行完后才继续执行线程A。
看例子
public class ThreadTest implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<3;i++){
System.out.println("这是线程:"+Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) throws InterruptedException {
//实例化类
ThreadTest test1=new ThreadTest();
//创建线程对象
Thread t1=new Thread(test1);
Thread t2=new Thread(test1);
//启动线程
t1.start();
//t1.join表示t1执行完才会执行t2
t1.join();
t2.start();
}
}
运行结果
本来的运行结果如第二大章中是混乱的,t1.join表示t1执行完才会执行t2,所以t2会等t1执行完再执行,这样的结果是不是和sleep睡眠之后有点像,但是更多时候还是建议用join特别是在不知道具体睡眠时间的时候。
四、多线程的synchronized关键字
在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在(也就是所谓实例锁。后面可以看到,其实还有可以对整个类上锁的全局锁)。而synchronized就是对象的一个同步锁。这个关键字的作用是,被这个关键字修饰的部分代码,称为互斥区。当某个线程在访问这段代码的时候,其他线程对这段代码的访问是互斥的,被称为临界区。简单的说就是用该关键字修饰的方法类变量被一个线程访问时,其它线程无法访问。
synchronized同步方式主要有3种,简单介绍如下:
1 synchronized同步普通方法,锁是当前对象实例。
2.synchronized同步静态方法,锁是当前类的Class对象。
3.synchronized同步代码块,锁是代码块中的括号里的对象。
- 1.修饰普通方法和修饰代码块,以下两种是等价
方法一修饰方法
public synchronized void run()
{
// todo
}
方法二修饰代码块
public void run()
{
synchronized(this) {
// todo
}
}
public class ThreadTest implements Runnable{
public synchronized void method(){
for(int i=0;i<3;i++){
System.out.println("这是线程:"+Thread.currentThread().getName()+" i=="+i);
}
/*也可以是synchronized (this) {
for(int i=0;i<3;i++){
System.out.println("这是线程:"+Thread.currentThread().getName()+" i=="+i);
}
}*/
}
@Override
public void run() {
method();
}
public static void main(String[] args) throws InterruptedException {
//实例化两个不同类
ThreadTest test1=new ThreadTest();
ThreadTest test2=new ThreadTest();
//创建线程对象
Thread t1=new Thread(test1);
Thread t2=new Thread(test2);
//启动线程
t1.start();
t2.start();
}
}
运行结果:
代码中,线程调用的是不同的类,此时代码运行结果不再同步,因为锁的是普通方法,也就是对象锁。两个对象不是同一把锁。
- 2.修饰静态方法
将method方法改为
public synchronized static void method(){
for(int i=0;i<3;i++){
System.out.println("这是线程:"+Thread.currentThread().getName()+" i=="+i);
}
}
运行结果:
这里就算属于不同类也能够同步运行,因为同步静态方法,锁是当前类的Class对象,两个对象是属于同一个类,是同一把锁。
五、多线程的wait方法
wait():等待,如果线程执行了wait方法,那么该线程会进入等待的状态,等待状态下的线程必须要被其他线程调用notify()方法才能唤醒。等待时会释放锁资源。
notify():唤醒,随机唤醒线程池等待线程其中的一个。
notifyAll():唤醒线程池所有等待线程。
注意:
wait与notify方法要注意的事项:
- wait方法与notify方法是属于Object对象的。
- wait方法与notify方法必须要在同步代码块或者是同步函数中才能使用。
- wait方法与notify方法必须要由所对象调用。
当在一个对象实例上调用wait()方法后,当前线程就会在这个对象上等待。直到另外的线程调用了notify()方法,出于等待的线程才得以继续进行。这样,多线程之间的协作就可以用这两个方法进行通信了。