Java多线程Thread
多线程实现原理
线程内存图
创建多线程的多种方法
自定义Thread子类
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(getName()+":"+i);
}
}
}
实现Runnable接口
实现Runnable接口java.Lang. Runnable
Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run 的无参数方法。java.Lang. Thread类的构造方法
Thread ( Runnable target)分配新的 Thread 对象。
Thread ( Runnable target,string name))分配新的 Thread 对象。
实现步骤:
- 创建一个Runnable接口的实现类
- 在实现类中重写Runnable接口的run方法,设置线程任务
- 创建一个Runnable接口的实现类对象
- 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
- 调用Thread类中的start方法,开启新的线程执行run方法
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+":"+i);
}
}
}
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
for (int i = 0; i < 100; i++) {
Thread thread2 = Thread.currentThread();
System.out.println(thread2.getName()+":"+i);
}
实现Runnable接口创建多线程程序的好处
实现Runnable接口创建多线程程序的好处:
- 避免了单继承的局限性
一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类实现了Runnable接口,还可以继承其他的类,实现其他的接口 - 增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)。
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:用来开启新线程
使用匿名内部类
匿名内部类方式实现线程的创建
匿名:没有名字
内部类:写在其他类内部的类匿名内部类
作用:简化代码
把子类继承父类,重写父类的方法,创建子类对象合一步完成
把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
格式:
new父类/接口(){
重复父类/接口中的方法
};
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+":"+i);
}
}
}.start();
或者使用Runnable
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+":"+i);
}
}
});
thread.start();
线程的方法
获取线程的名称
获取线程的名称:
- 使用Thread类中的方法getName( )
String getName()返回该线程的名称。
public class Demo02Thread {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
new MyThread().start();
new MyThread().start();
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(getName()+":"+i);
}
}
}
效果
交替运行
- 可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
static Thread currentThread()返回对当前正在执行的线程对象的引用。
public class Demo02Thread {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
MyThread myThread = new MyThread();
myThread.start();
new MyThread().start();
new MyThread().start();
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+":"+i);
}
}
}
currentThread()可以用于主线程中,因为主线程是没有getName这个方法的。
**注:**直接使用对象调用run方法是没有创建线程的。我们可以用getName来印证
MyThread myThread = new MyThread();
myThread.run();
myThread.start();
new MyThread().start();
new MyThread().start();
直接用run运行在main线程上的。
设置线程名称
设置线程的名称:(了解)
- 使用Thread类中的方法setName(名字)
void setName(String name)改变线程名称,使之与参数name 相同。 - 创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
Thread( string name)分配新的Thread 对象。
class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+":"+i);
}
}
}
主程序
MyThread myThread = new MyThread();
myThread.setName("诸葛亮");
myThread.start();
MyThread myThread1 = new MyThread("刘备");
MyThread myThread2 = new MyThread("张让");
myThread1.start();
myThread2.start();
效果图:
线程暂停(睡眠)方法 sleep
public static void sleep(Long millis):使当前正在执行的线程以指定的毫秒数暂停〈暂时停止执行)。毫秒数结束之后,线程继卖执行
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
因为InterruptedException 是编译期异常,所以我们需要处理。
多线程安全问题
主代码:
SellTicket sellTicket = new SellTicket();
Thread thread = new Thread(sellTicket);
Thread thread2 = new Thread(sellTicket);
Thread thread3 = new Thread(sellTicket);
thread.start();
thread2.start();
thread3.start();
实现Runnable接口类
public class SellTicket implements Runnable{
public int numberOfTicket = 100;
@Override
public void run() {
while (numberOfTicket>0){
System.out.println(Thread.currentThread().getName()+"正在卖第"+numberOfTicket+"张票;");
numberOfTicket--;
}
}
}
运行截图:
会出现售卖同一张票的情况,这个是异步造成的。
解决线程安全问题
同步代码块
解决线程安全问题的一种方案:使用同步代码块格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
public class SellTicketSynchronized implements Runnable{
//定义一个共享资源
public int numberOfTicket = 100;
//定义一个锁对象
Object object = new Object();
@Override
public void run() {
//同步代码块
synchronized (object){
while (numberOfTicket>0){
System.out.println(Thread.currentThread().getName()+"正在卖第"+numberOfTicket+"张票;");
numberOfTicket--;
}
}
}
}
同步技术的原理:
使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器3个线程一起抢夺cpu的执行权,谁抢到了谁执行run方法
t0抢到了cpu的执行权执行run方法,遇到synchronized代码块这时t0会检查synchronized代码块是否有锁对象,发现有,就会获取到锁对象,进入到同步中执行
t1抢到了cpu的执行权,执行run方法,遇到synchronized代码块这时t1会检查synchronized代码块是否有锁对象
发现没有,t1线程就会进入到阻塞状态,会一直等待t0线程归还锁对象。一直到t0线程执行完同步中的代码,会把锁对象归还给同步代码块t1才能获取到锁对象进入到同步中执行
总结: 同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步
注意:
- 通过代码块中的锁对象,可以使用任意的对象
- 但是必须保证多个线程使用的锁对象是同一个
- 锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
同步方法
解决线程安全问题的二种方案:
使用同步方法使用步骤:
- 把访问了共享数据的代码抽取出来,放到一个方法中
- 在方法上添加synchronized修饰符
// 格式:定义方法的格式
修饰符 synchronized 返回值类型方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码
}
public class SellTicketSynchronized02 implements Runnable{
//定义一个共享资源
public int numberOfTicket = 100;
@Override
public void run() {
payTicket();
}
private synchronized void payTicket() {
while (numberOfTicket>0){
System.out.println(Thread.currentThread().getName()+"正在卖第"+numberOfTicket+"张票;");
numberOfTicket--;
}
}
}
同步方法相当于,同步锁对象用的就是this对象
private void payTicket() {
synchronized(this) {
while (numberOfTicket>0){
System.out.println(Thread.currentThread().getName()+"正在卖第"+numberOfTicket+"张票;");
numberOfTicket--;
}
}
}
还可以使用静态的同步方法
静态的同步方法锁对象是谁?不能是this
this是创建对象之后产生的,静态方法优先于对象
静态方法的锁对象是本类的class属性–>class文件对象(反射)
public class SellTicketSynchronized03 implements Runnable{
//定义一个共享资源
public static int numberOfTicket = 100;
@Override
public void run() {
payTicket();
}
private static synchronized void payTicket() {
while (numberOfTicket>0){
System.out.println(Thread.currentThread().getName()+"正在卖第"+numberOfTicket+"张票;");
numberOfTicket--;
}
}
}
Lock锁
解决线程安全问题的三种方案: 使用Lock锁
java.util.concurrent.Locks. Lock接口
Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。
lock接口中的方法:
void lock() 获取锁。
void unlock() 释放锁。
java.util.concurrent.Locks.ReentrantLock implements Lock接口
使用步骤:
- 在成员位置创建一个ReentrantLock对象
- 在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
- 在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicketLock implements Runnable{
//定义一个共享资源
public int numberOfTicket = 100;
Lock l = new ReentrantLock();
@Override
public void run() {
payTicket();
}
private synchronized void payTicket() {
l.lock();
while (numberOfTicket>0){
System.out.println(Thread.currentThread().getName()+"正在卖第"+numberOfTicket+"张票;");
numberOfTicket--;
}
l.unlock();
}
}
两个线程之间通信
可以使用wait()和notify()
调用wait()和notify()的必须是锁对象
此时是两个不同的线程对同一资源访问,就需要挂起和唤醒操作
Pai
public class Pai {
public String bin;
public String xian;
public boolean flag = false;
}
PaiStore.java
public class PaiStore extends Thread{
Pai pai;
public PaiStore(Pai pai) {
this.pai = pai;
}
@Override
public void run() {
int catagroy = 0;
while (true){
synchronized (pai){
if (pai.flag){
try {
pai.wait(); //如果此时有派了,就不需要做,则进入等待序列
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 被唤醒后
if (catagroy%2 == 0){
pai.bin = "意大利";
pai.xian = "水果牛肉";
pai.flag = true;
}else if (catagroy%2 == 1){
pai.bin = "美式";
pai.xian = "榴莲";
pai.flag = true;
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("生产出了"+pai.bin+pai.xian);
catagroy++;
pai.notify();
}
}
}
}
}
Consumer.java
public class Consumer extends Thread{
Pai pai;
public Consumer(Pai pai) {
this.pai = pai;
}
@Override
public void run() {
while (true){
synchronized (pai){
if (pai.flag == false){
try {
pai.wait(); //如果此时没有派了,则进入等待序列
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("顾客吃了"+pai.bin+pai.xian);
pai.flag = false;
pai.xian = "";
pai.bin = "";
pai.notify();
}
}
}
}
main
Pai pai = new Pai();
PaiStore paiStore = new PaiStore(pai);
Consumer consumer = new Consumer(pai);
paiStore.start();
consumer.start();
obejct类中的方法
void wait()
在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待。
void notify ()
唤醒在此对象监视器上等待的单个线程。会继续执行wait方法之后的代码
此时只有生产出了派,顾客才能买来吃,没有则只能等待,等待店铺生产后被唤醒。有派的话,则店铺进入等待,等顾客购买后被唤醒,继续生产。
这里的wait和notify与操作系统里的PV操作类似
注: wait还有带参的方法和sleep的功能类似了。
进入到Timewaiting(计时等待)有两种方式
- 使用sLeep(Long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/BLocked状态
- 使用wait(Long m)方法, wait方法如果在毫秒值结束之后,还没有被notifyl唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
唤醒的方法:
void notify()唤醒在此对象监视器上等待的单个线程。void notifyALL()唤醒在此对象监视器上等待的所有线程。
线程之间的状态
线程池
线程池:JDK1.5之后提供的
java.util.concurrent.Executors:线程池的工厂类,用来生成线程池Executors类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads)创建一个可重用固定线程数的线程池
参数:
int nThreads:创建线程池中包含的线程数量
返回值:
ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
线程池的使用步骤:
- 使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的钱线程池
- 创建一个类,实现RunnabLe接口,重写run方法,设置线程任务
- 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行)