一. 多线程
笔直向前,说到做到。
1.1 Thread类
构造方法:
- public Thread() :分配一个新的线程对象。
- public Thread(String name) :分配一个指定名字的新的线程对象。
- public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
- public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
常用方法:
- public String getName() :获取当前线程名称。
- public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
- public void run() :此线程要执行的任务在此处定义代码。
- public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
- public static Thread currentThread() :返回对当前正在执行的线程对象的引用。
package com.itheima.demo;
class Mythread extends Thread{
private int num=6;
public Mythread(String name)
{
super(name);
}
@Override
public void run(){
while (true) {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "吃了第" + num-- + "个馍");
}
if (num == 0) {
break;
}
}
}
}
public class HelloWorld {
public static void main(String[] args) {
Mythread me = new Mythread("雷金鹏");
me.start();
for (int i=0;i<6;i++) {
System.out.println(i+"个馍了");
}
}
}
结果:
0个馍了
1个馍了
2个馍了
3个馍了
4个馍了
5个馍了
雷金鹏吃了第6个馍
雷金鹏吃了第5个馍
雷金鹏吃了第4个馍
雷金鹏吃了第3个馍
雷金鹏吃了第2个馍
雷金鹏吃了第1个馍
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的 start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行
1.2 Runnable接口
采用 java.lang.Runnable 也是非常常见的一种,我们只需要重写run方法即可。 步骤如下:
- 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正 的线程对象。
- 调用线程对象的start()方法来启动线程。
package com.itheima.demo;
class Mythread implements Runnable{
private int num=6;
@Override
public void run(){
while (true) {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "吃了第" + num-- + "个馍");
}
if (num == 0) {
break;
}
}
}
}
public class HelloWorld {
public static void main(String[] args) {
Mythread me = new Mythread();
Thread my1 = new Thread(me,"马新航");
my1.start();
}
}
结果:
马新航吃了第6个馍
马新航吃了第5个馍
马新航吃了第4个馍
马新航吃了第3个馍
马新航吃了第2个馍
马新航吃了第1个馍
1.3 Thread和Runnable的区别
实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一个资源。
- 可以避免java中的单继承的局限性。
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
- 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
扩充:在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用 java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进 程。
1.4 匿名内部类方式实现线程的创建
public class HelloWorld {
public static void main(String[] args) {
new Thread(new Runnable() {
private int num=6;
@Override
public void run() {
while (true) {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "吃了第" + num-- + "个馍");
}
if (num == 0) {
break;
}
}
}
},"范程").start();
}
}
结果:
范程吃了第6个馍
范程吃了第5个馍
范程吃了第4个馍
范程吃了第3个馍
范程吃了第2个馍
范程吃了第1个馍
二. 线程安全
2.1线程安全
package com.itheima.demo;
class Mythread implements Runnable{
private int num=6;
@Override
public void run(){
while (true) {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "吃了第" + num-- + "个馍");
}
if (num==0){
break;
}
}
}
}
public class HelloWorld {
public static void main(String[] args) {
//创建线程任务对象
Mythread ticket = new Mythread();
//创建三个对象
Thread t1 = new Thread(ticket, "吕布");
Thread t2 = new Thread(ticket, "高宠");
Thread t3 = new Thread(ticket, "李存孝");
//同时吃馍
t1.start();
t2.start();
t3.start();
}
}
结果:
吕布吃了第6个馍
李存孝吃了第5个馍
高宠吃了第4个馍
李存孝吃了第2个馍
吕布吃了第3个馍
高宠吃了第1个馍
这儿当次数少时是这样的,但当num值大的时候,就可能会出现
李存孝吃了第31个馍 吕布吃了第31个馍
或吕布吃了第-1个馍
的结果
在本代码中是因为当线程1执行到if时,此时CPU又随机选择到线程2,这样此时线程1处于堵塞状态,当线程2执行完后再进行刚才线程1的记录,这样数据就会重复执行。
这种问题,几个窗口(线程)数值不同步了,这种问题称为线程不安全。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写 操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步, 否则的话就可能影响线程安全。
2.2 线程同步
要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复与不存在问题,Java中提供了同步机制 (synchronized)来解决。
有三种方式完成同步操作:
- 同步代码块。
- 同步方法。
- 锁机制。
package com.itheima.demo;
class Mythread implements Runnable{
private int num=100;
Object s = new Object();
@Override
public void run(){
while (true) {
synchronized (s) {
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "吃了第" + num-- + "个馍");
}
if (num == 0) {
break;
}
}
}
}
}
public class HelloWorld {
public static void main(String[] args) {
//创建线程任务对象
Mythread ticket = new Mythread();
//创建三个对象
Thread t1 = new Thread(ticket, "吕布");
Thread t2 = new Thread(ticket, "高宠");
Thread t3 = new Thread(ticket, "李存孝");
//同时吃馍
t1.start();
t2.start();
t3.start();
}
}
2.3 Lock锁
java.util.concurrent.locks.Lock
机制提供了比synchronized
方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象
public void lock()
:加同步锁。public void unlock()
:释放同步锁。
package com.itheima.demo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Mythread implements Runnable{
private int num=100;
Lock s = new ReentrantLock();
@Override
public void run(){
while (true) {
s.lock();
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "吃了第" + num-- + "个馍");
}
if (num == 0) {
break;
}
s.unlock();
}
}
}
三. 线程状态
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操 作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状 态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限 等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个 状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting(计时 等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态 将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、 Object.wait。 |
Teminated(被 终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |