多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理” [1] 。
目录
一 初识多线程
1. 多线程创建
2获取线程名称
3.设置线程名称
4 Thread常用方法 ===sleep
5 创建多线程的第二种方法
6 匿名内部类方式实现线程的创建
二 线程安全
2.1 线程同步--锁对象
2.2 同步方法--解决线程安全问题
2.3 静态方法解决线程安全问题
2.4 Lock锁解决线程安全问题
三 线程等待唤醒案例
一 初识多线程
1. 多线程创建
package com.itliuchao.six.xiancheng.ThreadDome2;
//定义一个Thread的子类
public class MyThread1 extends Thread{
@Override
//重写Thread类中的run方法 设置线程任务
public void run() {
//获取线程名称
// String name = getName();
// System.out.println(name);
// Thread t = Thread.currentThread();
// System.out.println(t);
// String name = t.getName();
// System.out.println(name);
// 链式编程
System.out.println(Thread.currentThread().getName());
}
}
package com.itliuchao.six.xiancheng.ThreadDome2;
/*
获取线程名称:
1 使用Thread类中的getName()方法;
2 还可以获取当前执行线程,使用线程中的方法getName()获取线程名称
Thread.currentThread() 返回对当前正在执行线程对象的引用
线程名称:
主线程:main
新线程:Thread-0 Thread-1
*/
public class TestMyThread1 {
public static void main(String[] args) {
// 创建Thread类子类对象
MyThread1 myThread1 = new MyThread1();
//调用start方法 开启新线程 执行run方法
myThread1.start();
// 链式编程 获取主线程名称
System.out.println(Thread.currentThread().getName());
}
}
2获取线程名称
package com.itliuchao.six.xiancheng.ThreadDome2;
//定义一个Thread的子类
public class MyThread1 extends Thread{
@Override
//重写Thread类中的run方法 设置线程任务
public void run() {
//获取线程名称
// String name = getName();
// System.out.println(name);
// Thread t = Thread.currentThread();
// System.out.println(t);
// String name = t.getName();
// System.out.println(name);
// 链式编程
System.out.println(Thread.currentThread().getName());
}
}
package com.itliuchao.six.xiancheng.ThreadDome2;
/*
获取线程名称:
1 使用Thread类中的getName()方法;
2 还可以获取当前执行线程,使用线程中的方法getName()获取线程名称
Thread.currentThread() 返回对当前正在执行线程对象的引用
线程名称:
主线程:main
新线程:Thread-0 Thread-1
*/
public class TestMyThread1 {
public static void main(String[] args) {
// 创建Thread类子类对象
MyThread1 myThread1 = new MyThread1();
//调用start方法 开启新线程 执行run方法
myThread1.start();
// 链式编程 获取主线程名称
System.out.println(Thread.currentThread().getName());
}
}
3.设置线程名称
package com.itliuchao.six.xiancheng.ThreadDome2;
/*
设置线程名称(了解)
使用Theater中的setName方法
*/
public class TestMyThread2 {
public static void main(String[] args) {
//开启多线程
MyThread2 myThread2 = new MyThread2();
myThread2.setName("拖拉机");
myThread2.start();
//开启多线程
new MyThread2("收割机").start();
}
}
package com.itliuchao.six.xiancheng.ThreadDome2;
public class MyThread2 extends Thread{
public MyThread2(){
}
public MyThread2(String name){
super(name); //把线程名称传给父亲 让父类Thread给子线程起个名称
}
@Override
public void run() {
//获取线程名称
System.out.println(Thread.currentThread().getName());
}
}
Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。
4 Thread常用方法 ===sleep
package com.itliuchao.six.xiancheng.ThreadDome2;
// 使程序睡眠 设置时间运行
public class Threadsleep {
public static void main(String[] args) {
// 模拟秒表
for (int i = 0; i <10 ; i++) {
System.out.println(i);
// 使用Thread类的sleep方法 使线程睡眠一秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
5 创建多线程的第二种方法
package com.itliuchao.six.xiancheng.TheaterDome3;
/*
创建多线程程序的第二种方式:实现Runnable接口
Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法
Thread(Runnable target) 分配新的 Thread 对象。
Thread(Runnable target, String name) 分配新的 Thread 对象。
实现步骤:
1 创建一个Runnable接口的实现类
2 在实现类中重写Runnable接口的run方法 设置线程任务
3 创建一个Runnable接口实现类对象
4 创建Thread类对象 构造函数中传递Runnable接口实现类对象
5 调用Thread类中的start方法 开启新线程实现run方法
*/
public class MyRunnable {
public static void main(String[] args) {
// 3 创建一个Runnable接口实现类对象
MyRunnableImpl myRunnable = new MyRunnableImpl();
// 4 创建Thread类对象 构造函数中传递Runnable接口实现类对象
Thread thread = new Thread(myRunnable);
// 5 调用Thread类中的start方法 开启新线程实现run方法
thread.start();
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"---->"+i);
}
}
}
package com.itliuchao.six.xiancheng.TheaterDome3;
//1 创建一个Runnable接口的实现类
public class MyRunnableImpl implements Runnable{
//2 在实现类中重写Runnable接口的run方法 设置线程任务
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"---->"+i);
}
}
}
6 匿名内部类方式实现线程的创建
package com.itliuchao.six.xiancheng; // 匿名内部类实现线程的创建 /* 匿名内部类作用: 简化代码 把子类继承父类 重写父类方法 创建子类对象合一步完成 把实现类实现接口 重写接口中的方法 创建实现类对象合成一步完成 匿名内部类的最终产物 子类/实现类对象, 而这个类对象 格式: new 父类/接口(){ 重复父类 /接口中的方法 }; */ public class Dome2 { public static void main(String[] args) { new Thread() { @Override public void run() { for (int i = 0; i <2 ; i++) { System.out.println(Thread.currentThread().getName()+"刘老板"); } } }.start(); // 线程接口Runnable //Runnable r =new MyRunnableImpl(); //多态写法 Runnable runnable = new Runnable() { @Override public void run() { for (int i = 0; i <2 ; i++) { System.out.println(Thread.currentThread().getName()+"刘总"); } } }; new Thread(runnable).start(); //简化接口的方式 new Thread(new Runnable() { @Override //重写run方法 设置线程任务 public void run() { for (int i = 0; i <2 ; i++) { System.out.println(Thread.currentThread().getName()+"刘总裁"); } } }).start(); } }
二 线程安全
2.1 线程同步--锁对象
package com.itliuchao.six.xiancheng.anlitickets1;
/*
模拟买票案例出现了线程安全问题
卖出不存在合重复得票
接口线程安全问题的第一种方案: 使用同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全的代码 (访问了共享数据代码)
}
注意:
1 通过代码中的锁对象 可以使用任意对象
2 必须保证多个线程使用的锁对象是同一个
3 锁对象作用:
把同步代码块锁住 只让另一个线程在同步代码块中执行
*/
public class MyRunnableImpl implements Runnable {
//1 定义一个多线程共享资源
private int tickets =100;
// 创建一个锁对象
Object obj =new Object();
@Override
// 设置线程任务 买票
public void run() {
// 使用死循环 让买票操作重复执行
while (true){
synchronized (obj){
// 先判断票是否存在
if (tickets>0){
// 提高安全问题出现的概率 让程序睡眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖" + tickets + "张票");
tickets--;
}
}
}
}
}
-----------------------------------
package com.itliuchao.six.xiancheng.anlitickets1;
/*
模拟买票案例
创建三个线 同时开启 对共享票进行出售
*/
public class ThreadDome1 {
public static void main(String[] args) {
// 创建Runnable接口实现类对象
MyRunnableImpl mr = new MyRunnableImpl();
// 创建Theater类对象 构造方法中传递接口Runnable的实现类对象
Thread thread = new Thread(mr);
Thread thread1 = new Thread(mr);
Thread thread2 = new Thread(mr);
// 调用start开始多线程
thread.start();
thread1.start();
thread2.start();
}
}
2.2 同步方法--解决线程安全问题
package com.itliuchao.six.xiancheng.anlitickets2;
/*
买票案例出现了线程安全问题
卖出了不存在的票和重复得票
解决线程安全问题的第二种方式 使用同步方法
使用步骤:
1 把访问了共享数据的代码抽取出来 放到一个方法中
2 在方法上添加synchronized 修饰符
格式: 定义方法的格式:
修饰符 synchronized 返回值类型(参数列表){
可能会出现线程安全的代码 (访问了共享数据代码)
}
*/
public class MyRunnableImpl implements Runnable {
//1 定义一个多线程共享资源
private int tickets =100;
@Override
// 设置线程任务 买票
public void run() {
// 使用死循环 让买票操作重复执行
while (true) {
//调用下面的method方法
method();
}
}
/*
定义一个同步方法
同步方法 也会把方法内部的代码锁起来
只让一个线程执行
同步方法锁住的对象是实现类 new MyRunnableImpl
也就是 this
*/
public synchronized void method(){
// 先判断票是否存在
if (tickets>0){
// 提高安全问题出现的概率 让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖" + tickets + "张票");
tickets--;
}
}
}
package com.itliuchao.six.xiancheng.anlitickets2;
/*
模拟买票案例
创建三个线 同时开启 对共享票进行出售
*/
public class ThreadDome1 {
public static void main(String[] args) {
// 创建Runnable接口实现类对象
MyRunnableImpl mr = new MyRunnableImpl();
// 创建Theater类对象 构造方法中传递接口Runnable的实现类对象
Thread thread = new Thread(mr);
Thread thread1 = new Thread(mr);
Thread thread2 = new Thread(mr);
// 调用start开始多线程
thread.start();
thread1.start();
thread2.start();
}
}
2.3 静态方法解决线程安全问题
package com.itliuchao.six.xiancheng.anlitickets3;
/*
买票案例
静态方法解决线程安全问题(不是重点 了解一下就行)
*/
public class MyRunnableImpl implements Runnable {
//1 定义一个多线程共享资源
private static int tickets =100;
@Override
// 设置线程任务 买票
public void run() {
// 使用死循环 让买票操作重复执行
while (true) {
method();
}
}
public static synchronized void method(){
// 先判断票是否存在
if (tickets>0){
// 提高安全问题出现的概率 让程序睡眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖" + tickets + "张票");
tickets--;
}
}
}
package com.itliuchao.six.xiancheng.anlitickets3;
import com.itliuchao.six.xiancheng.anlitickets1.MyRunnableImpl;
/*
模拟买票案例
创建三个线 同时开启 对共享票进行出售
*/
public class ThreadDome1 {
public static void main(String[] args) {
// 创建Runnable接口实现类对象
MyRunnableImpl mr = new MyRunnableImpl();
// 创建Theater类对象 构造方法中传递接口Runnable的实现类对象
Thread thread = new Thread(mr);
Thread thread1 = new Thread(mr);
Thread thread2 = new Thread(mr);
// 调用start开始多线程
thread.start();
thread1.start();
thread2.start();
}
}
2.4 Lock锁解决线程安全问题
package com.itliuchao.six.xiancheng.anlitickets4;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
买票案例出现了线程安全问题
卖出了不存在的票和重复得票
解决线程安全问题的第三种方式 使用Lock方法*/
// Lock锁 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定=操作
// Lock中接口的方法
//void、lock()获取锁
//void unlock()释放锁
/*
使用步骤 :
1 在成员位置创建一个ReentrantLock对象
2 在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
3 在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
*/
public class MyRunnableImpl implements Runnable {
//1 定义一个多线程共享资源
private int tickets =100;
// 1 在成员位置创建一个ReentrantLock对象
Lock l= new ReentrantLock();
@Override
// 设置线程任务 买票
public void run() {
// 使用死循环 让买票操作重复执行
/*while (true){
// 2 在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
l.lock();
// 先判断票是否存在
if (tickets>0){
// 提高安全问题出现的概率 让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖" + tickets + "张票");
tickets--;
}
// 3 在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
l.unlock();
}*/
// 使用死循环 让买票操作重复执行
while (true){
// 2 在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
l.lock();
// 先判断票是否存在
if (tickets>0){
// 提高安全问题出现的概率 让程序睡眠
try {
Thread.sleep(10);
//正在买票 票减减
System.out.println(Thread.currentThread().getName()+"正在卖" + tickets + "张票");
tickets--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
// 3 在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
l.unlock(); // 放在finally中的好处 就是无论程序出现异常 都会把锁释放
}
}
}
}
}
package com.itliuchao.six.xiancheng.anlitickets4;
/*
模拟买票案例
创建三个线 同时开启 对共享票进行出售
*/
public class ThreadDome1 {
public static void main(String[] args) {
// 创建Runnable接口实现类对象
MyRunnableImpl mr = new MyRunnableImpl();
// 创建Theater类对象 构造方法中传递接口Runnable的实现类对象
Thread thread = new Thread(mr);
Thread thread1 = new Thread(mr);
Thread thread2 = new Thread(mr);
// 调用start开始多线程
thread.start();
thread1.start();
thread2.start();
}
}
三 线程等待唤醒案例
package com.itliuchao.six.xiancheng.Dome5WaitandNofity;
/*
等待唤醒案例:线程之间的通信
创建一个顾客线程(消费者) 告知老板要包子的数量 调用wait方法 放弃cpu的执行 进入到 WAITING状态(无线等待)
创建一个老板线程(生产者) 花了五秒钟做包子 做好包子后调用notify方法 唤醒顾客吃包子
注意:
顾客何老板线程必须使用同步代码包裹起来 保证等待和唤醒只能有一个执行
同步使用锁对象必须保证唯一
只有锁对象才能调用wait和notify方法
*/
public class Dome5WaitandNofity {
public static void main(String[] args) {
//创建锁对象 保证唯一
Object o = new Object();
//创建一个顾客线程
new Thread(){
@Override
public void run() {
//顾客一直要包子
while (true){
// 顾客何老板线程必须使用同步代码包裹起来 保证等待和唤醒只能有一个执行
synchronized (o){
System.out.println("老板 我要十八个包子 ,做快些~~");
// 调用wait方法 放弃cpu的执行 进入
try {
//调用wait方法 放弃cpu的执行 进入到 WAITING状态(无线等待)
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 唤醒后执行的代码
System.out.println("我拿到包子l 兄弟们开吃啊");
System.out.println("===========================");
}
}
}.start();
//创建一个线程老板 (生产者)
new Thread(){
@Override
public void run() {
//老板一直做包子
while (true){
// 花五秒做包子
try {
Thread.sleep(5000);//做包子五秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
// 保证等待唤醒的线程只能有一个执行 需要使用同步方法
synchronized (o){
System.out.println("老板开始做包子 等待五秒钟 包子做好了 请顾客品尝哦!!!");
// 做好包子之后调用notify方法 唤醒顾客吃包子
o.notify();
}
}
}
}.start();
}
}