Java虚拟机允许应用程序并发地运行多个线程。
以下为多线程的实现常用的2种方法
(1)继承Thread类,重写run()方法
Thread本质上也是实现了Runnable接口的一个实例,代表一个线程的实例。启用线程的唯一方法就是通过Thread类的start()方法。调用start()方法后并不是立即执行多线程代码,而是使得该线程变为可运行态(Running),什么时候运行是由操作系统决定的。
class MyThread extends Thread{//创建函数体
public void run(){
System.out.println("Thread body");//线程函数体
}
}
public class Test{
public static void main(String[] args){
MyThread thread = new MyThread();
thread.start();//线程开启
}
}
(2)实现Runnable接口,并实现该接口的run()方法
主要步骤
- 自定义类并实现Runable接口,实现run()方法。
- 创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象
- 调用Thread的start()方法
class MyThread implements Runnable{
public void run(){
System.out.println("Thread body");
}
}
public class Test{
public static void main(String []args){
MyThread thread = new MyThread();
Thread t = new Thread(thread);
t.start();//开启线程
}
}
这两种方法最终都是通过Thread 的对象的API来控制线程的。
线程状态:
-
NEW 新建状态
-
RUNNABLE 运行状态
-
BLOCK 受阻塞 线程具有CPU的执行资格
-
WAIT (无限期)等待 object的方法 notify(唤醒) 放弃CPU
-
TIMED_WAITING 休眠 sleep 放弃CPU
-
TERMINATED 死亡
线程池
-
节约开销
-
由线程池工厂创建的,再调用线程池中的方法获取线程,再通过线程去执行方法
-
在java.util.concurrent 中的Executors 类
-
实现线程池程序,使用供现场类Executors中的静态方法创建线程对象,指定线程个数
static ExectorService newFixedThreadPool(int 个数) 返回线程池对象
返回的是ExecutorService接口的实现类(线程池对象)
接口实现类对象,调用方法submit (Runnable r) 提交线程执行任务
-
-
run方法没有返回值,不能抛异常
线程开启虚拟栈空间,栈内存是线程私有的
创建线程的目的:为了建立程序单独的执行路径
Thread.currentThread();返回正在执行的线程对象
线程安全
-
单线程没有安全问题
-
多线程同时操作同一个共享数据,往往出现安全问题
-
解决:当一个线程进入数据操作的时候,无论是否休眠,其他线程只能等待。保证只有一个线程在操作
-
同步代码块:
-
synchronized(任意对象){ 线程要操作的共享数据 }
-
同步对象,任意对象。对象:同步锁,对象监视器 obj
-
同步保证安全性:没有锁的线程不能执行,只能等。加了同步后,线程进同步判断锁,获取锁,出同步释放锁,导致程序运行速度的下降。
-
线程遇到同步代码块后,线程判断同步锁还有没有。
-
如果有,获取锁,进入同步中,去执行,执行完毕后将锁对象还回去。在同步中线程,进行了休眠,此时另一个线程,会执行。
-
没有锁的线程,不能进入同步中执行,被阻挡在同步代码块的外面。在同步中的线程,不出去同步,不会释放锁。
-
-
-
同步方法:代码简洁,将线程共享数据,和同步,抽取到一个方法中。对象锁是本类对象引用this.如果方法为静态,锁是本类自己.class 属性
-
-
电影院卖票,排队上厕所原理
package ticket;
/**
* 多线程并发访问同一个数据资源
* 3 个线程,对一个票资源,出售
*/
public class ThreadDemo {
public static void main(String[] args) {
//创建Runnable接口实现类对象
Tickets t = new Tickets();
//创建3个Thread对象,传递Runnable接口实现类
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();
t1.start();
t2.start();
}
}
package ticket;
public class Tickets implements Runnable{
private int ticket = 100;
//同步代码块
// private Object obj = new Object();
// @Override
// public void run() {
// while (true){
// //对票数判断,大于0,可以出售
// synchronized (obj){//不写匿名对象是因为每次循环时,对象就变了
// if(ticket > 0){
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// }
// System.out.println(Thread.currentThread().getName()+"出售第"+ticket--);
// }
// }
// }
// }
//同步方法
@Override
public void run() {
while (true){
payTicket();
}
}
//对票数判断,大于0,可以出售
public synchronized void payTicket(){
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()+"出售第"+ticket--);
}
}
}
Lock
-
可以替代synchronized,比他更灵活
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Tickets implements Runnable{
private int ticket = 100;
//在类的成员位置,创建Lock接口实现类的对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
//对票数判断,大于0,可以出售
if(ticket > 0){
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"出售第"+ticket--);
} catch (InterruptedException e) {
}finally {
lock.unlock();
}}}}}
死锁
-
同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这是容易引发一种现象:程序出现无限等待,这种现象称为死锁。
synchronized(A 锁){
synchronized(B 锁){
}
}
-
前提:必须是多线程的,出现同步嵌套
-
线程进入同步获取锁,不出去同步,不会释放锁
public class Main {
public static void main(String[] args) {
DeadLock deadLock = new DeadLock();
Thread t0 = new Thread(deadLock);
Thread t1 = new Thread(deadLock);
t0.start();
t1.start();
}
}
public class DeadLock implements Runnable{
private int i = 0;
public void run(){
while(true){
if(i%2 == 0){
//先进入A同步,再进入B同步
synchronized (LockA.lockA){
System.out.println("if 。。。locka");
synchronized (LockB.lockB){
System.out.println("if。。。lockb");
}
}
}else{
//先进入B同步,再进入A同步
synchronized (LockB.lockB){
System.out.println("else。。lockb");
synchronized (LockA.lockA) {
System.out.println("else。。locka");
}
}
}
i++;
}
}
}
public class LockA {
private LockA(){}
public static final LockA lockA = new LockA();
}
public class LockB {
private LockB(){}
public static final LockB lockB = new LockB();
}
等待唤醒机制
-
线程之间通信:多个线程在处理同一个资源,单只处理的动作(线程任务)却不相同。通过一定的手段使各个线程能有效的利用资源。这种手段即——等待唤醒机制。
-
wait() 无限等
-
notify() 唤醒,一次只唤醒一个,任意的
-
notifyAll() 唤醒全部
//保证两个线程交替输入输出值
public class ThreadDemo {
public static void main(String[] args) {
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread tin = new Thread(in);
Thread tout = new Thread(out);
tin.start();
tout.start();
}
}
public class Resource {
public String name;
public String sex;
public boolean flag = false;
}
public class Input implements Runnable {
private Resource resource ;
public Input(Resource r){
resource = r;
}
//flag 为ture表示赋值完成,false为取值完成
@Override
public void run() {
int i = 0;
while(true){
synchronized (resource){
//标记是true,等待
if(resource.flag){
try{resource.wait();}catch (Exception e){}
}
if(i%2 ==0){
resource.name = "张";
resource.sex = "男";
}else{
resource.name = "li";
resource.sex = "nv";
}
//将对方线程唤醒,标记该为true
resource.flag = true;
resource.notify();
}
i++;
}
}
}
public class Output implements Runnable {
private Resource r ;//new对象之后输出对象默认值,因为main方法中的对象和这对象不是一个。
public Output (Resource r){
this.r = r;
}
@Override
public void run() {
while(true){
synchronized (r){
// 判断标记,是false,等待
if(!r.flag){
try {
r.wait();
}catch (Exception e){}
}
System.out.println(r.name+".."+r.sex);
//标记改成false,唤醒对方线程
r.flag = false;
r.notify();
}
}
}
}